diff options
Diffstat (limited to 'src')
1028 files changed, 68913 insertions, 36886 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71efbb40d..1cfd3bbc9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,7 +32,6 @@ if (MSVC) # /Zc:inline - Let codegen omit inline functions in object files # /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null add_compile_options( - /W3 /MP /Zi /Zo @@ -43,6 +42,18 @@ if (MSVC) /Zc:externConstexpr /Zc:inline /Zc:throwingNew + + # Warnings + /W3 + /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled + /we4101 # 'identifier': unreferenced local variable + /we4265 # 'class': class has virtual functions, but destructor is not virtual + /we4388 # signed/unsigned mismatch + /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect + /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'? + /we4555 # Expression has no effect; expected expression with side-effect + /we4834 # Discarding return value of function with 'nodiscard' attribute + /we5038 # data member 'member1' will be initialized after data member 'member2' ) # /GS- - No stack buffer overflow checks @@ -53,12 +64,20 @@ if (MSVC) else() add_compile_options( -Wall + -Werror=array-bounds -Werror=implicit-fallthrough -Werror=missing-declarations + -Werror=missing-field-initializers -Werror=reorder + -Werror=switch + -Werror=uninitialized + -Werror=unused-function + -Werror=unused-result + -Werror=unused-variable -Wextra -Wmissing-declarations -Wno-attributes + -Wno-invalid-offsetof -Wno-unused-parameter ) @@ -113,7 +132,6 @@ add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(yuzu_cmd) - add_subdirectory(yuzu_tester) endif() if (ENABLE_QT) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5ef38a337..a0ae07752 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -12,22 +12,58 @@ add_library(audio_core STATIC buffer.h codec.cpp codec.h + command_generator.cpp + command_generator.h common.h + delay_line.cpp + delay_line.h + effect_context.cpp + effect_context.h + info_updater.cpp + info_updater.h + memory_pool.cpp + memory_pool.h + mix_context.cpp + mix_context.h null_sink.h sink.h + sink_context.cpp + sink_context.h sink_details.cpp sink_details.h sink_stream.h + splitter_context.cpp + splitter_context.h stream.cpp stream.h time_stretch.cpp time_stretch.h + voice_context.cpp + voice_context.h $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> ) create_target_directory_groups(audio_core) +if (NOT MSVC) + target_compile_options(audio_core PRIVATE + -Werror=conversion + -Werror=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=reorder + -Werror=sign-compare + -Werror=shadow + -Werror=unused-parameter + -Werror=unused-variable + + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> + + -Wno-sign-conversion + ) +endif() + target_link_libraries(audio_core PUBLIC common core) target_link_libraries(audio_core PRIVATE SoundTouch) diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp index f65bf64f7..01b8dff6b 100644 --- a/src/audio_core/algorithm/filter.cpp +++ b/src/audio_core/algorithm/filter.cpp @@ -31,8 +31,8 @@ Filter Filter::LowPass(double cutoff, double Q) { Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} -Filter::Filter(double a0, double a1, double a2, double b0, double b1, double b2) - : a1(a1 / a0), a2(a2 / a0), b0(b0 / a0), b1(b1 / a0), b2(b2 / a0) {} +Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_) + : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {} void Filter::Process(std::vector<s16>& signal) { const std::size_t num_frames = signal.size() / 2; @@ -55,7 +55,8 @@ void Filter::Process(std::vector<s16>& signal) { /// @param total_count The total number of biquads to be cascaded. /// @param index 0-index of the biquad to calculate the Q value for. static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { - const double pole = M_PI * (2 * index + 1) / (4.0 * total_count); + const auto pole = + M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count)); return 1.0 / (2.0 * std::cos(pole)); } @@ -68,7 +69,7 @@ CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size } CascadingFilter::CascadingFilter() = default; -CascadingFilter::CascadingFilter(std::vector<Filter> filters) : filters(std::move(filters)) {} +CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {} void CascadingFilter::Process(std::vector<s16>& signal) { for (auto& filter : filters) { diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h index 3546d149b..a291fe79b 100644 --- a/src/audio_core/algorithm/filter.h +++ b/src/audio_core/algorithm/filter.h @@ -25,7 +25,7 @@ public: /// Passthrough filter. Filter(); - Filter(double a0, double a1, double a2, double b0, double b1, double b2); + Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_); void Process(std::vector<s16>& signal); @@ -51,7 +51,7 @@ public: /// Passthrough. CascadingFilter(); - explicit CascadingFilter(std::vector<Filter> filters); + explicit CascadingFilter(std::vector<Filter> filters_); void Process(std::vector<s16>& signal); diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 49ab9d3e1..3b4144e21 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -146,7 +146,7 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, return {}; if (ratio <= 0) { - LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio); + LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio); return input; } @@ -164,7 +164,8 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, const std::size_t num_frames{input.size() / 2}; std::vector<s16> output; - output.reserve(static_cast<std::size_t>(input.size() / ratio + InterpolationState::taps)); + output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio + + InterpolationState::taps)); for (std::size_t frame{}; frame < num_frames; ++frame) { const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps}; @@ -197,4 +198,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, return output; } +void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) { + const std::array<s16, 512>& lut = [pitch] { + if (pitch > 0xaaaa) { + return curve_lut0; + } + if (pitch <= 0x8000) { + return curve_lut1; + } + return curve_lut2; + }(); + + std::size_t index{}; + + for (std::size_t i = 0; i < sample_count; i++) { + const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4}; + const auto l0 = lut[lut_index + 0]; + const auto l1 = lut[lut_index + 1]; + const auto l2 = lut[lut_index + 2]; + const auto l3 = lut[lut_index + 3]; + + const auto s0 = static_cast<s32>(input[index + 0]); + const auto s1 = static_cast<s32>(input[index + 1]); + const auto s2 = static_cast<s32>(input[index + 2]); + const auto s3 = static_cast<s32>(input[index + 3]); + + output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; + fraction += pitch; + index += (fraction >> 15); + fraction &= 0x7fff; + } +} + } // namespace AudioCore diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h index ab1a31754..d534077af 100644 --- a/src/audio_core/algorithm/interpolate.h +++ b/src/audio_core/algorithm/interpolate.h @@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> return Interpolate(state, std::move(input), ratio); } +/// Nintendo Switchs DSP resampling algorithm. Based on a single channel +void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count); + } // namespace AudioCore diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 8619a3f03..fe3a898ad 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -43,6 +43,10 @@ std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, return stream->GetTagsAndReleaseBuffers(max_count); } +std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) { + return stream->GetTagsAndReleaseBuffers(); +} + void AudioOut::StartStream(StreamPtr stream) { stream->Play(); } diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h index b07588287..6ce08cd0d 100644 --- a/src/audio_core/audio_out.h +++ b/src/audio_core/audio_out.h @@ -31,6 +31,9 @@ public: /// Returns a vector of recently released buffers specified by tag for the specified stream std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); + /// Returns a vector of all recently released buffers specified by tag for the specified stream + std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream); + /// Starts an audio stream for playback void StartStream(StreamPtr stream); diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index d64452617..d2ce8c814 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -2,95 +2,96 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "audio_core/algorithm/interpolate.h" +#include <limits> +#include <vector> + #include "audio_core/audio_out.h" #include "audio_core/audio_renderer.h" -#include "audio_core/codec.h" #include "audio_core/common.h" -#include "common/assert.h" +#include "audio_core/info_updater.h" +#include "audio_core/voice_context.h" #include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/writable_event.h" #include "core/memory.h" +#include "core/settings.h" -namespace AudioCore { +namespace { +[[nodiscard]] static constexpr s16 ClampToS16(s32 value) { + return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()}, + s32{std::numeric_limits<s16>::max()})); +} -constexpr u32 STREAM_SAMPLE_RATE{48000}; -constexpr u32 STREAM_NUM_CHANNELS{2}; -using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>; -class AudioRenderer::VoiceState { -public: - bool IsPlaying() const { - return is_in_use && info.play_state == PlayState::Started; - } +[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) { + // Mix 50% from left and 50% from right channel + constexpr float l_mix_amount = 50.0f / 100.0f; + constexpr float r_mix_amount = 50.0f / 100.0f; + return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) + + (static_cast<float>(r_channel) * r_mix_amount))); +} - const VoiceOutStatus& GetOutStatus() const { - return out_status; - } +[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(s16 fl_channel, s16 fr_channel, + s16 fc_channel, + [[maybe_unused]] s16 lf_channel, + s16 bl_channel, s16 br_channel) { + // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels + // are mixed to be 36.94% - const VoiceInfo& GetInfo() const { - return info; - } + constexpr float front_mix_amount = 36.94f / 100.0f; + constexpr float center_mix_amount = 26.12f / 100.0f; + constexpr float back_mix_amount = 36.94f / 100.0f; - VoiceInfo& GetInfo() { - return info; - } + // Mix 50% from left and 50% from right channel + const auto left = front_mix_amount * static_cast<float>(fl_channel) + + center_mix_amount * static_cast<float>(fc_channel) + + back_mix_amount * static_cast<float>(bl_channel); - void SetWaveIndex(std::size_t index); - std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources); - void UpdateState(); - void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources); - -private: - bool is_in_use{}; - bool is_refresh_pending{}; - std::size_t wave_index{}; - std::size_t offset{}; - Codec::ADPCMState adpcm_state{}; - InterpolationState interp_state{}; - std::vector<s16> samples; - VoiceOutStatus out_status{}; - VoiceInfo info{}; -}; - -class AudioRenderer::EffectState { -public: - const EffectOutStatus& GetOutStatus() const { - return out_status; - } + const auto right = front_mix_amount * static_cast<float>(fr_channel) + + center_mix_amount * static_cast<float>(fc_channel) + + back_mix_amount * static_cast<float>(br_channel); - const EffectInStatus& GetInfo() const { - return info; - } + return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; +} - EffectInStatus& GetInfo() { - return info; - } +[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients( + s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, + const std::array<float_le, 4>& coeff) { + const auto left = + static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + + static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[0]; + + const auto right = + static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + + static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[0]; - void UpdateState(Core::Memory::Memory& memory); + return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; +} -private: - EffectOutStatus out_status{}; - EffectInStatus info{}; -}; +} // namespace +namespace AudioCore { AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, - AudioRendererParameter params, - std::shared_ptr<Kernel::WritableEvent> buffer_event, + AudioCommon::AudioRendererParameter params, + Stream::ReleaseCallback&& release_callback, std::size_t instance_number) - : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), - voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} { + : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4), + voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), + sink_context(params.sink_count), splitter_context(), + voices(params.voice_count), memory{memory_}, + command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, + memory) { behavior_info.SetUserRevision(params.revision); + splitter_context.Initialize(behavior_info, params.splitter_count, + params.num_splitter_send_channels); + mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); audio_out = std::make_unique<AudioCore::AudioOut>(); - stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, - fmt::format("AudioRenderer-Instance{}", instance_number), - [=]() { buffer_event->Signal(); }); + stream = audio_out->OpenStream( + core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, + fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); audio_out->StartStream(stream); QueueMixedBuffer(0); QueueMixedBuffer(1); QueueMixedBuffer(2); + QueueMixedBuffer(3); } AudioRenderer::~AudioRenderer() = default; @@ -111,359 +112,210 @@ Stream::State AudioRenderer::GetStreamState() const { return stream->GetState(); } -ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { - // Copy UpdateDataHeader struct - UpdateDataHeader config{}; - std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader)); - u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4); +ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, + std::vector<u8>& output_params) { + + InfoUpdater info_updater{input_params, output_params, behavior_info}; - if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) { + if (!info_updater.UpdateBehaviorInfo(behavior_info)) { LOG_ERROR(Audio, "Failed to update behavior info input parameters"); - return Audren::ERR_INVALID_PARAMETERS; + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - // Copy MemoryPoolInfo structs - std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count); - std::memcpy(mem_pool_info.data(), - input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size, - memory_pool_count * sizeof(MemoryPoolInfo)); - - // Copy voice resources - const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size}; - std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset, - sizeof(VoiceResourceInformation) * voice_resources.size()); - - // Copy VoiceInfo structs - std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size + config.voice_resource_size}; - for (auto& voice : voices) { - std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo)); - voice_offset += sizeof(VoiceInfo); + if (!info_updater.UpdateMemoryPools(memory_pool_info)) { + LOG_ERROR(Audio, "Failed to update memory pool parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size + - config.memory_pools_size + config.voice_resource_size + - config.voices_size}; - for (auto& effect : effects) { - std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus)); - effect_offset += sizeof(EffectInStatus); + if (!info_updater.UpdateVoiceChannelResources(voice_context)) { + LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - // Update memory pool state - std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); - for (std::size_t index = 0; index < memory_pool.size(); ++index) { - if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { - memory_pool[index].state = MemoryPoolStates::Attached; - } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { - memory_pool[index].state = MemoryPoolStates::Detached; - } + if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { + LOG_ERROR(Audio, "Failed to update voice parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - // Update voices - for (auto& voice : voices) { - voice.UpdateState(); - if (!voice.GetInfo().is_in_use) { - continue; - } - if (voice.GetInfo().is_new) { - voice.SetWaveIndex(voice.GetInfo().wave_buffer_head); - } + // TODO(ogniK): Deal with stopped audio renderer but updates still taking place + if (!info_updater.UpdateEffects(effect_context, true)) { + LOG_ERROR(Audio, "Failed to update effect parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - for (auto& effect : effects) { - effect.UpdateState(memory); + if (behavior_info.IsSplitterSupported()) { + if (!info_updater.UpdateSplitterInfo(splitter_context)) { + LOG_ERROR(Audio, "Failed to update splitter parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } } - // Release previous buffers and queue next ones for playback - ReleaseAndQueueBuffers(); + const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, + splitter_context, effect_context); - // Copy output header - UpdateDataHeader response_data{worker_params}; - if (behavior_info.IsElapsedFrameCountSupported()) { - response_data.render_info = sizeof(RendererInfo); - response_data.total_size += sizeof(RendererInfo); + if (mix_result.IsError()) { + LOG_ERROR(Audio, "Failed to update mix parameters"); + return mix_result; } - std::vector<u8> output_params(response_data.total_size); - std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); - - // Copy output memory pool entries - std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(), - response_data.memory_pools_size); - - // Copy output voice status - std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size}; - for (const auto& voice : voices) { - std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(), - sizeof(VoiceOutStatus)); - voice_out_status_offset += sizeof(VoiceOutStatus); + // TODO(ogniK): Sinks + if (!info_updater.UpdateSinks(sink_context)) { + LOG_ERROR(Audio, "Failed to update sink parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - std::size_t effect_out_status_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.voice_resource_size}; - for (const auto& effect : effects) { - std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(), - sizeof(EffectOutStatus)); - effect_out_status_offset += sizeof(EffectOutStatus); + // TODO(ogniK): Performance buffer + if (!info_updater.UpdatePerformanceBuffer()) { + LOG_ERROR(Audio, "Failed to update performance buffer parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - // Update behavior info output - const std::size_t behavior_out_status_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.effects_size + response_data.sinks_size + - response_data.performance_manager_size}; - - if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) { - LOG_ERROR(Audio, "Failed to update behavior info output parameters"); - return Audren::ERR_INVALID_PARAMETERS; + if (!info_updater.UpdateErrorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update error info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } if (behavior_info.IsElapsedFrameCountSupported()) { - const std::size_t renderer_info_offset{ - sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + - response_data.effects_size + response_data.sinks_size + - response_data.performance_manager_size + response_data.behavior_size}; - RendererInfo renderer_info{}; - renderer_info.elasped_frame_count = elapsed_frame_count; - std::memcpy(output_params.data() + renderer_info_offset, &renderer_info, - sizeof(RendererInfo)); - } - - return MakeResult(output_params); -} - -void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) { - wave_index = index & 3; - is_refresh_pending = true; -} - -std::vector<s16> AudioRenderer::VoiceState::DequeueSamples( - std::size_t sample_count, Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources) { - if (!IsPlaying()) { - return {}; - } - - if (is_refresh_pending) { - RefreshBuffer(memory, voice_resources); - } - - const std::size_t max_size{samples.size() - offset}; - const std::size_t dequeue_offset{offset}; - std::size_t size{sample_count * STREAM_NUM_CHANNELS}; - if (size > max_size) { - size = max_size; - } - - out_status.played_sample_count += size / STREAM_NUM_CHANNELS; - offset += size; - - const auto& wave_buffer{info.wave_buffer[wave_index]}; - if (offset == samples.size()) { - offset = 0; - - if (!wave_buffer.is_looping && wave_buffer.buffer_sz) { - SetWaveIndex(wave_index + 1); - } - - if (wave_buffer.buffer_sz) { - out_status.wave_buffer_consumed++; - } - - if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) { - info.play_state = PlayState::Paused; + if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { + LOG_ERROR(Audio, "Failed to update renderer info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } } + // TODO(ogniK): Statistics - return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; -} - -void AudioRenderer::VoiceState::UpdateState() { - if (is_in_use && !info.is_in_use) { - // No longer in use, reset state - is_refresh_pending = true; - wave_index = 0; - offset = 0; - out_status = {}; + if (!info_updater.WriteOutputHeader()) { + LOG_ERROR(Audio, "Failed to write output header"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - is_in_use = info.is_in_use; -} -void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory, - const VoiceChannelHolder& voice_resources) { - const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr; - const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz; - std::vector<s16> new_samples(wave_buffer_size / sizeof(s16)); - memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size); - - switch (static_cast<Codec::PcmFormat>(info.sample_format)) { - case Codec::PcmFormat::Int16: { - // PCM16 is played as-is - break; - } - case Codec::PcmFormat::Adpcm: { - // Decode ADPCM to PCM16 - Codec::ADPCM_Coeff coeffs; - memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff)); - new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()), - new_samples.size() * sizeof(s16), coeffs, adpcm_state); - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format); - break; - } - - switch (info.channel_count) { - case 1: { - // 1 channel is upsampled to 2 channel - samples.resize(new_samples.size() * 2); + // TODO(ogniK): Check when all sections are implemented - for (std::size_t index = 0; index < new_samples.size(); ++index) { - auto sample = static_cast<float>(new_samples[index]); - if (voice_resources[0]->in_use) { - sample *= voice_resources[0]->mix_volumes[0]; - } - - samples[index * 2] = static_cast<s16>(sample * info.volume); - samples[index * 2 + 1] = static_cast<s16>(sample * info.volume); - } - break; + if (!info_updater.CheckConsumedSize()) { + LOG_ERROR(Audio, "Audio buffers were not consumed!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - case 2: { - // 2 channel is played as is - samples = std::move(new_samples); - const std::size_t sample_count = (samples.size() / 2); - for (std::size_t index = 0; index < sample_count; ++index) { - const std::size_t index_l = index * 2; - const std::size_t index_r = index * 2 + 1; - - auto sample_l = static_cast<float>(samples[index_l]); - auto sample_r = static_cast<float>(samples[index_r]); - - if (voice_resources[0]->in_use) { - sample_l *= voice_resources[0]->mix_volumes[0]; - } - - if (voice_resources[1]->in_use) { - sample_r *= voice_resources[1]->mix_volumes[1]; - } - samples[index_l] = static_cast<s16>(sample_l * info.volume); - samples[index_r] = static_cast<s16>(sample_r * info.volume); - } - break; - } - case 6: { - samples.resize((new_samples.size() / 6) * 2); - const std::size_t sample_count = samples.size() / 2; - - for (std::size_t index = 0; index < sample_count; ++index) { - auto FL = static_cast<float>(new_samples[index * 6]); - auto FR = static_cast<float>(new_samples[index * 6 + 1]); - auto FC = static_cast<float>(new_samples[index * 6 + 2]); - auto BL = static_cast<float>(new_samples[index * 6 + 4]); - auto BR = static_cast<float>(new_samples[index * 6 + 5]); - - if (voice_resources[0]->in_use) { - FL *= voice_resources[0]->mix_volumes[0]; - } - if (voice_resources[1]->in_use) { - FR *= voice_resources[1]->mix_volumes[1]; - } - if (voice_resources[2]->in_use) { - FC *= voice_resources[2]->mix_volumes[2]; - } - if (voice_resources[4]->in_use) { - BL *= voice_resources[4]->mix_volumes[4]; - } - if (voice_resources[5]->in_use) { - BR *= voice_resources[5]->mix_volumes[5]; - } - - samples[index * 2] = - static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume); - samples[index * 2 + 1] = - static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume); - } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count); - break; - } - - // Only interpolate when necessary, expensive. - if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { - samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, - STREAM_SAMPLE_RATE); - } - - is_refresh_pending = false; -} - -void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) { - if (info.is_new) { - out_status.state = EffectStatus::New; - } else { - if (info.type == Effect::Aux) { - ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0, - "Aux buffers tried to update"); - ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0, - "Aux buffers tried to update"); - } - } -} + ReleaseAndQueueBuffers(); -static constexpr s16 ClampToS16(s32 value) { - return static_cast<s16>(std::clamp(value, -32768, 32767)); + return RESULT_SUCCESS; } void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { - constexpr std::size_t BUFFER_SIZE{512}; + command_generator.PreCommand(); + // Clear mix buffers before our next operation + command_generator.ClearMixBuffers(); + + // If the splitter is not in use, sort our mixes + if (!splitter_context.UsingSplitter()) { + mix_context.SortInfo(); + } + // Sort our voices + voice_context.SortInfo(); + + // Handle samples + command_generator.GenerateVoiceCommands(); + command_generator.GenerateSubMixCommands(); + command_generator.GenerateFinalMixCommands(); + + command_generator.PostCommand(); + // Base sample size + std::size_t BUFFER_SIZE{worker_params.sample_count}; + // Samples std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); - - for (auto& voice : voices) { - if (!voice.IsPlaying()) { - continue; - } - VoiceChannelHolder resources{}; - for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) { - const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel]; - resources[channel] = &voice_resources[channel_resource_id]; + // Make sure to clear our samples + std::memset(buffer.data(), 0, buffer.size() * sizeof(s16)); + + if (sink_context.InUse()) { + const auto stream_channel_count = stream->GetNumChannels(); + const auto buffer_offsets = sink_context.OutputBuffers(); + const auto channel_count = buffer_offsets.size(); + const auto& final_mix = mix_context.GetFinalMixInfo(); + const auto& in_params = final_mix.GetInParams(); + std::vector<s32*> mix_buffers(channel_count); + for (std::size_t i = 0; i < channel_count; i++) { + mix_buffers[i] = + command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); } - std::size_t offset{}; - s64 samples_remaining{BUFFER_SIZE}; - while (samples_remaining > 0) { - const std::vector<s16> samples{ - voice.DequeueSamples(samples_remaining, memory, resources)}; - - if (samples.empty()) { - break; - } - - samples_remaining -= samples.size() / stream->GetNumChannels(); - - for (const auto& sample : samples) { - const s32 buffer_sample{buffer[offset]}; - buffer[offset++] = - ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume)); + for (std::size_t i = 0; i < BUFFER_SIZE; i++) { + if (channel_count == 1) { + const auto sample = ClampToS16(mix_buffers[0][i]); + + // Place sample in all channels + for (u32 channel = 0; channel < stream_channel_count; channel++) { + buffer[i * stream_channel_count + channel] = sample; + } + + if (stream_channel_count == 6) { + // Output stream has a LF channel, mute it! + buffer[i * stream_channel_count + 3] = 0; + } + + } else if (channel_count == 2) { + const auto l_sample = ClampToS16(mix_buffers[0][i]); + const auto r_sample = ClampToS16(mix_buffers[1][i]); + if (stream_channel_count == 1) { + buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample); + } else if (stream_channel_count == 2) { + buffer[i * stream_channel_count + 0] = l_sample; + buffer[i * stream_channel_count + 1] = r_sample; + } else if (stream_channel_count == 6) { + buffer[i * stream_channel_count + 0] = l_sample; + buffer[i * stream_channel_count + 1] = r_sample; + + // Combine both left and right channels to the center channel + buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample); + + buffer[i * stream_channel_count + 4] = l_sample; + buffer[i * stream_channel_count + 5] = r_sample; + } + + } else if (channel_count == 6) { + const auto fl_sample = ClampToS16(mix_buffers[0][i]); + const auto fr_sample = ClampToS16(mix_buffers[1][i]); + const auto fc_sample = ClampToS16(mix_buffers[2][i]); + const auto lf_sample = ClampToS16(mix_buffers[3][i]); + const auto bl_sample = ClampToS16(mix_buffers[4][i]); + const auto br_sample = ClampToS16(mix_buffers[5][i]); + + if (stream_channel_count == 1) { + // Games seem to ignore the center channel half the time, we use the front left + // and right channel for mixing as that's where majority of the audio goes + buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); + } else if (stream_channel_count == 2) { + // Mix all channels into 2 channels + if (sink_context.HasDownMixingCoefficients()) { + const auto [left, right] = Mix6To2WithCoefficients( + fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, + sink_context.GetDownmixCoefficients()); + buffer[i * stream_channel_count + 0] = left; + buffer[i * stream_channel_count + 1] = right; + } else { + const auto [left, right] = Mix6To2(fl_sample, fr_sample, fc_sample, + lf_sample, bl_sample, br_sample); + buffer[i * stream_channel_count + 0] = left; + buffer[i * stream_channel_count + 1] = right; + } + } else if (stream_channel_count == 6) { + // Pass through + buffer[i * stream_channel_count + 0] = fl_sample; + buffer[i * stream_channel_count + 1] = fr_sample; + buffer[i * stream_channel_count + 2] = fc_sample; + buffer[i * stream_channel_count + 3] = lf_sample; + buffer[i * stream_channel_count + 4] = bl_sample; + buffer[i * stream_channel_count + 5] = br_sample; + } } } } + audio_out->QueueBuffer(stream, tag, std::move(buffer)); elapsed_frame_count++; + voice_context.UpdateStateByDspShared(); } void AudioRenderer::ReleaseAndQueueBuffers() { - const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)}; + const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; for (const auto& tag : released_buffers) { QueueMixedBuffer(tag); } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index f0b691a86..18567f618 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -9,261 +9,64 @@ #include <vector> #include "audio_core/behavior_info.h" +#include "audio_core/command_generator.h" #include "audio_core/common.h" +#include "audio_core/effect_context.h" +#include "audio_core/memory_pool.h" +#include "audio_core/mix_context.h" +#include "audio_core/sink_context.h" +#include "audio_core/splitter_context.h" #include "audio_core/stream.h" +#include "audio_core/voice_context.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/hle/kernel/object.h" #include "core/hle/result.h" namespace Core::Timing { class CoreTiming; } -namespace Kernel { -class WritableEvent; -} - namespace Core::Memory { class Memory; } namespace AudioCore { +using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>; class AudioOut; -enum class PlayState : u8 { - Started = 0, - Stopped = 1, - Paused = 2, -}; - -enum class Effect : u8 { - None = 0, - Aux = 2, -}; - -enum class EffectStatus : u8 { - None = 0, - New = 1, -}; - -struct AudioRendererParameter { - u32_le sample_rate; - u32_le sample_count; - u32_le mix_buffer_count; - u32_le submix_count; - u32_le voice_count; - u32_le sink_count; - u32_le effect_count; - u32_le performance_frame_count; - u8 is_voice_drop_enabled; - u8 unknown_21; - u8 unknown_22; - u8 execution_mode; - u32_le splitter_count; - u32_le num_splitter_send_channels; - u32_le unknown_30; - u32_le revision; -}; -static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); - -enum class MemoryPoolStates : u32 { // Should be LE - Invalid = 0x0, - Unknown = 0x1, - RequestDetach = 0x2, - Detached = 0x3, - RequestAttach = 0x4, - Attached = 0x5, - Released = 0x6, -}; - -struct MemoryPoolEntry { - MemoryPoolStates state; - u32_le unknown_4; - u32_le unknown_8; - u32_le unknown_c; -}; -static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); - -struct MemoryPoolInfo { - u64_le pool_address; - u64_le pool_size; - MemoryPoolStates pool_state; - INSERT_PADDING_WORDS(3); // Unknown -}; -static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); -struct BiquadFilter { - u8 enable; - INSERT_PADDING_BYTES(1); - std::array<s16_le, 3> numerator; - std::array<s16_le, 2> denominator; -}; -static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); - -struct WaveBuffer { - u64_le buffer_addr; - u64_le buffer_sz; - s32_le start_sample_offset; - s32_le end_sample_offset; - u8 is_looping; - u8 end_of_stream; - u8 sent_to_server; - INSERT_PADDING_BYTES(5); - u64 context_addr; - u64 context_sz; - INSERT_PADDING_BYTES(8); -}; -static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); - -struct VoiceResourceInformation { - s32_le id{}; - std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{}; - bool in_use{}; - INSERT_PADDING_BYTES(11); -}; -static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size"); - -struct VoiceInfo { - u32_le id; - u32_le node_id; - u8 is_new; - u8 is_in_use; - PlayState play_state; - u8 sample_format; - u32_le sample_rate; - u32_le priority; - u32_le sorting_order; - u32_le channel_count; - float_le pitch; - float_le volume; - std::array<BiquadFilter, 2> biquad_filter; - u32_le wave_buffer_count; - u32_le wave_buffer_head; - INSERT_PADDING_WORDS(1); - u64_le additional_params_addr; - u64_le additional_params_sz; - u32_le mix_id; - u32_le splitter_info_id; - std::array<WaveBuffer, 4> wave_buffer; - std::array<u32_le, 6> voice_channel_resource_ids; - INSERT_PADDING_BYTES(24); -}; -static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); - -struct VoiceOutStatus { - u64_le played_sample_count; - u32_le wave_buffer_consumed; - u32_le voice_drops_count; -}; -static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); - -struct AuxInfo { - std::array<u8, 24> input_mix_buffers; - std::array<u8, 24> output_mix_buffers; - u32_le mix_buffer_count; - u32_le sample_rate; // Stored in the aux buffer currently - u32_le sample_count; - u64_le send_buffer_info; - u64_le send_buffer_base; - - u64_le return_buffer_info; - u64_le return_buffer_base; -}; -static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); - -struct EffectInStatus { - Effect type; - u8 is_new; - u8 is_enabled; - INSERT_PADDING_BYTES(1); - u32_le mix_id; - u64_le buffer_base; - u64_le buffer_sz; - s32_le priority; - INSERT_PADDING_BYTES(4); - union { - std::array<u8, 0xa0> raw; - AuxInfo aux_info; - }; -}; -static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); - -struct EffectOutStatus { - EffectStatus state; - INSERT_PADDING_BYTES(0xf); -}; -static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); - -struct RendererInfo { - u64_le elasped_frame_count{}; - INSERT_PADDING_WORDS(2); -}; -static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); - -struct UpdateDataHeader { - UpdateDataHeader() {} - - explicit UpdateDataHeader(const AudioRendererParameter& config) { - revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision - behavior_size = 0xb0; - memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10; - voices_size = config.voice_count * 0x10; - voice_resource_size = 0x0; - effects_size = config.effect_count * 0x10; - mixes_size = 0x0; - sinks_size = config.sink_count * 0x20; - performance_manager_size = 0x10; - render_info = 0; - total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + - effects_size + sinks_size + performance_manager_size; - } - - u32_le revision{}; - u32_le behavior_size{}; - u32_le memory_pools_size{}; - u32_le voices_size{}; - u32_le voice_resource_size{}; - u32_le effects_size{}; - u32_le mixes_size{}; - u32_le sinks_size{}; - u32_le performance_manager_size{}; - u32_le splitter_size{}; - u32_le render_info{}; - INSERT_PADDING_WORDS(4); - u32_le total_size{}; -}; -static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); - class AudioRenderer { public: AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, - AudioRendererParameter params, - std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number); + AudioCommon::AudioRendererParameter params, + Stream::ReleaseCallback&& release_callback, std::size_t instance_number); ~AudioRenderer(); - ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params); + [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, + std::vector<u8>& output_params); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); - u32 GetSampleRate() const; - u32 GetSampleCount() const; - u32 GetMixBufferCount() const; - Stream::State GetStreamState() const; + [[nodiscard]] u32 GetSampleRate() const; + [[nodiscard]] u32 GetSampleCount() const; + [[nodiscard]] u32 GetMixBufferCount() const; + [[nodiscard]] Stream::State GetStreamState() const; private: - class EffectState; - class VoiceState; BehaviorInfo behavior_info{}; - AudioRendererParameter worker_params; - std::shared_ptr<Kernel::WritableEvent> buffer_event; + AudioCommon::AudioRendererParameter worker_params; + std::vector<ServerMemoryPoolInfo> memory_pool_info; + VoiceContext voice_context; + EffectContext effect_context; + MixContext mix_context; + SinkContext sink_context; + SplitterContext splitter_context; std::vector<VoiceState> voices; - std::vector<VoiceResourceInformation> voice_resources; - std::vector<EffectState> effects; std::unique_ptr<AudioOut> audio_out; StreamPtr stream; Core::Memory::Memory& memory; + CommandGenerator command_generator; std::size_t elapsed_frame_count{}; }; diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp index 94b7a3bf1..3c2e3e6f1 100644 --- a/src/audio_core/behavior_info.cpp +++ b/src/audio_core/behavior_info.cpp @@ -9,39 +9,11 @@ namespace AudioCore { -BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} +BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} BehaviorInfo::~BehaviorInfo() = default; -bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) { - if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - InParams params{}; - std::memcpy(¶ms, buffer.data() + offset, sizeof(InParams)); - - if (!IsValidRevision(params.revision)) { - LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision); - return false; - } - - if (user_revision != params.revision) { - LOG_ERROR(Audio, - "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", - user_revision, params.revision); - return false; - } - - ClearError(); - UpdateFlags(params.flags); - - // TODO(ogniK): Check input params size when InfoUpdater is used - - return true; -} - bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { - if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { + if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { LOG_ERROR(Audio, "Buffer is an invalid size!"); return false; } @@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) { user_revision = revision; } +u32_le BehaviorInfo::GetUserRevision() const { + return user_revision; +} + +u32_le BehaviorInfo::GetProcessRevision() const { + return process_revision; +} + bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { - return IsRevisionSupported(2, user_revision); + return AudioCommon::IsRevisionSupported(2, user_revision); } bool BehaviorInfo::IsSplitterSupported() const { - return IsRevisionSupported(2, user_revision); + return AudioCommon::IsRevisionSupported(2, user_revision); } bool BehaviorInfo::IsLongSizePreDelaySupported() const { - return IsRevisionSupported(3, user_revision); + return AudioCommon::IsRevisionSupported(3, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { - return IsRevisionSupported(5, user_revision); +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { - return IsRevisionSupported(4, user_revision); +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { + return AudioCommon::IsRevisionSupported(4, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { - return IsRevisionSupported(1, user_revision); +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { + return AudioCommon::IsRevisionSupported(1, user_revision); } bool BehaviorInfo::IsElapsedFrameCountSupported() const { - return IsRevisionSupported(5, user_revision); + return AudioCommon::IsRevisionSupported(5, user_revision); } bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { return (flags & 1) != 0; } +bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { + return AudioCommon::IsRevisionSupported(7, user_revision); +} + +bool BehaviorInfo::IsSplitterBugFixed() const { + return AudioCommon::IsRevisionSupported(5, user_revision); +} + +void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { + dst.error_count = static_cast<u32>(error_count); + std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); +} + } // namespace AudioCore diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h index c5e91ab39..5a96bf75e 100644 --- a/src/audio_core/behavior_info.h +++ b/src/audio_core/behavior_info.h @@ -14,30 +14,6 @@ namespace AudioCore { class BehaviorInfo { public: - explicit BehaviorInfo(); - ~BehaviorInfo(); - - bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset); - bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); - - void ClearError(); - void UpdateFlags(u64_le dest_flags); - void SetUserRevision(u32_le revision); - - bool IsAdpcmLoopContextBugFixed() const; - bool IsSplitterSupported() const; - bool IsLongSizePreDelaySupported() const; - bool IsAudioRenererProcessingTimeLimit80PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit75PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; - bool IsElapsedFrameCountSupported() const; - bool IsMemoryPoolForceMappingEnabled() const; - -private: - u32_le process_revision{}; - u32_le user_revision{}; - u64_le flags{}; - struct ErrorInfo { u32_le result{}; INSERT_PADDING_WORDS(1); @@ -45,9 +21,6 @@ private: }; static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); - std::array<ErrorInfo, 10> errors{}; - std::size_t error_count{}; - struct InParams { u32_le revision{}; u32_le padding{}; @@ -61,6 +34,39 @@ private: INSERT_PADDING_BYTES(12); }; static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); + + explicit BehaviorInfo(); + ~BehaviorInfo(); + + bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); + + void ClearError(); + void UpdateFlags(u64_le dest_flags); + void SetUserRevision(u32_le revision); + [[nodiscard]] u32_le GetUserRevision() const; + [[nodiscard]] u32_le GetProcessRevision() const; + + [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const; + [[nodiscard]] bool IsSplitterSupported() const; + [[nodiscard]] bool IsLongSizePreDelaySupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; + [[nodiscard]] bool IsElapsedFrameCountSupported() const; + [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const; + [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const; + [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; + [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const; + [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const; + [[nodiscard]] bool IsSplitterBugFixed() const; + void CopyErrorInfo(OutParams& dst); + +private: + u32_le process_revision{}; + u32_le user_revision{}; + u64_le flags{}; + std::array<ErrorInfo, 10> errors{}; + std::size_t error_count{}; }; } // namespace AudioCore diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h index 5ee09e9aa..ccc46ef82 100644 --- a/src/audio_core/buffer.h +++ b/src/audio_core/buffer.h @@ -18,7 +18,7 @@ class Buffer { public: using Tag = u64; - Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {} + Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {} /// Returns the raw audio data for the buffer std::vector<s16>& GetSamples() { diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp index c5a0d98ce..2fb91c13a 100644 --- a/src/audio_core/codec.cpp +++ b/src/audio_core/codec.cpp @@ -16,8 +16,9 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM constexpr std::size_t FRAME_LEN = 8; constexpr std::size_t SAMPLES_PER_FRAME = 14; - constexpr std::array<int, 16> SIGNED_NIBBLES = { - {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; + static constexpr std::array<int, 16> SIGNED_NIBBLES{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; const std::size_t ret_size = diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h index ef2ce01a8..9507abb1b 100644 --- a/src/audio_core/codec.h +++ b/src/audio_core/codec.h @@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array<s16, 16>; * @param state ADPCM state, this is updated with new state * @return Decoded stereo signed PCM16 data, sample_count in length */ -std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, +std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, ADPCMState& state); }; // namespace AudioCore::Codec diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp new file mode 100644 index 000000000..437cc5ccd --- /dev/null +++ b/src/audio_core/command_generator.cpp @@ -0,0 +1,1308 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cmath> +#include <numbers> +#include "audio_core/algorithm/interpolate.h" +#include "audio_core/command_generator.h" +#include "audio_core/effect_context.h" +#include "audio_core/mix_context.h" +#include "audio_core/voice_context.h" +#include "core/memory.h" + +namespace AudioCore { +namespace { +constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; +constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; +using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; + +constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; +constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; +constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; +constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; +constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ + 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, + 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, + 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; +constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ + 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, + 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, + 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; + +template <std::size_t N> +void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { + for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) { + for (std::size_t j = 0; j < N; j++) { + output[i + j] += + static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15); + } + } +} + +s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) { + s32 x = 0; + for (s32 i = 0; i < sample_count; i++) { + x = static_cast<s32>(static_cast<float>(input[i]) * gain); + output[i] += x; + gain += delta; + } + return x; +} + +void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) { + for (s32 i = 0; i < sample_count; i++) { + output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); + gain += delta; + } +} + +void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) { + for (s32 i = 0; i < sample_count; i++) { + output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15); + } +} + +s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) { + const bool positive = first_sample > 0; + auto final_sample = std::abs(first_sample); + for (s32 i = 0; i < sample_count; i++) { + final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15); + if (positive) { + output[i] += final_sample; + } else { + output[i] -= final_sample; + } + } + if (positive) { + return final_sample; + } else { + return -final_sample; + } +} + +float Pow10(float x) { + if (x >= 0.0f) { + return 1.0f; + } else if (x <= -5.3f) { + return 0.0f; + } + return std::pow(10.0f, x); +} + +float SinD(float degrees) { + return std::sin(degrees * std::numbers::pi_v<float> / 180.0f); +} + +float CosD(float degrees) { + return std::cos(degrees * std::numbers::pi_v<float> / 180.0f); +} + +float ToFloat(s32 sample) { + return static_cast<float>(sample) / 65536.f; +} + +s32 ToS32(float sample) { + constexpr auto min = -8388608.0f; + constexpr auto max = 8388607.f; + float rescaled_sample = sample * 65536.0f; + if (rescaled_sample < min) { + rescaled_sample = min; + } + if (rescaled_sample > max) { + rescaled_sample = max; + } + return static_cast<s32>(rescaled_sample); +} + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +template <std::size_t CHANNEL_COUNT> +void ApplyReverbGeneric(I3dl2ReverbState& state, + const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input, + const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output, + s32 sample_count) { + + auto GetTapLookup = []() { + if constexpr (CHANNEL_COUNT == 1) { + return REVERB_TAP_INDEX_1CH; + } else if constexpr (CHANNEL_COUNT == 2) { + return REVERB_TAP_INDEX_2CH; + } else if constexpr (CHANNEL_COUNT == 4) { + return REVERB_TAP_INDEX_4CH; + } else if constexpr (CHANNEL_COUNT == 6) { + return REVERB_TAP_INDEX_6CH; + } + }; + + const auto& tap_index_lut = GetTapLookup(); + for (s32 sample = 0; sample < sample_count; sample++) { + std::array<f32, CHANNEL_COUNT> out_samples{}; + std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; + std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; + std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; + + // Mix everything into a single sample + s32 temp_mixed_sample = 0; + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + temp_mixed_sample += input[i][sample]; + } + const auto current_sample = ToFloat(temp_mixed_sample); + const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); + + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { + const auto tapped_samp = + state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; + out_samples[tap_index_lut[i]] += tapped_samp; + + if constexpr (CHANNEL_COUNT == 6) { + // handle lfe + out_samples[5] += tapped_samp; + } + } + + state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; + state.early_delay_line.Tick(state.lowpass_0); + + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + out_samples[i] *= state.early_gain; + } + + // Two channel seems to apply a latet gain, we require to save this + f32 filter{}; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + filter = state.fdn_delay_line[i].GetOutputSample(); + const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; + state.shelf_filter[i] = + filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; + fsamp[i] = computed; + } + + // Mixing matrix + mixed[0] = fsamp[1] + fsamp[2]; + mixed[1] = -fsamp[0] - fsamp[3]; + mixed[2] = fsamp[0] - fsamp[3]; + mixed[3] = fsamp[1] - fsamp[2]; + + if constexpr (CHANNEL_COUNT == 2) { + for (auto& mix : mixed) { + mix *= (filter * state.late_gain); + } + } + + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + const auto late = early_tap * state.late_gain; + osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); + osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); + state.fdn_delay_line[i].Tick(osamp[i]); + } + + if constexpr (CHANNEL_COUNT == 1) { + output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + + (out_samples[0] + osamp[0] + osamp[1])); + } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + output[i][sample] = + ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); + } + } else if constexpr (CHANNEL_COUNT == 6) { + const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); + for (std::size_t i = 0; i < 4; i++) { + output[i][sample] = + ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); + } + output[4][sample] = + ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); + output[5][sample] = + ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); + } + } +} + +} // namespace + +CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, + VoiceContext& voice_context_, MixContext& mix_context_, + SplitterContext& splitter_context_, + EffectContext& effect_context_, Core::Memory::Memory& memory_) + : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_), + splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_), + mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * + worker_params.sample_count), + sample_buffer(MIX_BUFFER_SIZE), + depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * + worker_params.sample_count) {} +CommandGenerator::~CommandGenerator() = default; + +void CommandGenerator::ClearMixBuffers() { + std::fill(mix_buffer.begin(), mix_buffer.end(), 0); + std::fill(sample_buffer.begin(), sample_buffer.end(), 0); + // std::fill(depop_buffer.begin(), depop_buffer.end(), 0); +} + +void CommandGenerator::GenerateVoiceCommands() { + if (dumping_frame) { + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands"); + } + // Grab all our voices + const auto voice_count = voice_context.GetVoiceCount(); + for (std::size_t i = 0; i < voice_count; i++) { + auto& voice_info = voice_context.GetSortedInfo(i); + // Update voices and check if we should queue them + if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { + continue; + } + + // Queue our voice + GenerateVoiceCommand(voice_info); + } + // Update our splitters + splitter_context.UpdateInternalState(); +} + +void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { + auto& in_params = voice_info.GetInParams(); + const auto channel_count = in_params.channel_count; + + for (s32 channel = 0; channel < channel_count; channel++) { + const auto resource_id = in_params.voice_channel_resource_id[channel]; + auto& dsp_state = voice_context.GetDspSharedState(resource_id); + auto& channel_resource = voice_context.GetChannelResource(resource_id); + + // Decode our samples for our channel + GenerateDataSourceCommand(voice_info, dsp_state, channel); + + if (in_params.should_depop) { + in_params.last_volume = 0.0f; + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || + in_params.mix_id != AudioCommon::NO_MIX) { + // Apply a biquad filter if needed + GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, + worker_params.mix_buffer_count, channel); + // Base voice volume ramping + GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, + in_params.node_id); + in_params.last_volume = in_params.volume; + + if (in_params.mix_id != AudioCommon::NO_MIX) { + // If we're using a mix id + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + const auto& dest_mix_params = mix_info.GetInParams(); + + // Voice Mixing + GenerateVoiceMixCommand( + channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + + // Update last mix volumes + channel_resource.UpdateLastMixVolumes(); + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + s32 base = channel; + while (auto* destination_data = + GetDestinationData(in_params.splitter_info_id, base)) { + base += channel_count; + + if (!destination_data->IsConfigured()) { + continue; + } + if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) { + continue; + } + + const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_mix_params = mix_info.GetInParams(); + GenerateVoiceMixCommand( + destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + destination_data->MarkDirty(); + } + } + // Update biquad filter enabled states + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; + } + } + } +} + +void CommandGenerator::GenerateSubMixCommands() { + const auto mix_count = mix_context.GetCount(); + for (std::size_t i = 0; i < mix_count; i++) { + auto& mix_info = mix_context.GetSortedInfo(i); + const auto& in_params = mix_info.GetInParams(); + if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { + continue; + } + GenerateSubMixCommand(mix_info); + } +} + +void CommandGenerator::GenerateFinalMixCommands() { + GenerateFinalMixCommand(); +} + +void CommandGenerator::PreCommand() { + if (!dumping_frame) { + return; + } + for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { + const auto& base = splitter_context.GetInfo(i); + std::string graph = fmt::format("b[{}]", i); + const auto* head = base.GetHead(); + while (head != nullptr) { + graph += fmt::format("->{}", head->GetMixId()); + head = head->GetNextDestination(); + } + LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); + } +} + +void CommandGenerator::PostCommand() { + if (!dumping_frame) { + return; + } + dumping_frame = false; +} + +void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 channel) { + const auto& in_params = voice_info.GetInParams(); + const auto depop = in_params.should_depop; + + if (depop) { + if (in_params.mix_id != AudioCommon::NO_MIX) { + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + const auto& mix_in = mix_info.GetInParams(); + GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + s32 index{}; + while (const auto* destination = + GetDestinationData(in_params.splitter_info_id, index++)) { + if (!destination->IsConfigured()) { + continue; + } + auto& mix_info = mix_context.GetInfo(destination->GetMixId()); + const auto& mix_in = mix_info.GetInParams(); + GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); + } + } + } else { + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + case SampleFormat::Adpcm: + ASSERT(channel == 0 && in_params.channel_count == 1); + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + } +} + +void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, + VoiceState& dsp_state, + [[maybe_unused]] s32 mix_buffer_count, + [[maybe_unused]] s32 channel) { + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + const auto& in_params = voice_info.GetInParams(); + auto& biquad_filter = in_params.biquad_filter[i]; + // Check if biquad filter is actually used + if (!biquad_filter.enabled) { + continue; + } + + // Reinitialize our biquad filter state if it was enabled previously + if (!in_params.was_biquad_filter_enabled[i]) { + dsp_state.biquad_filter_state.fill(0); + } + + // Generate biquad filter + // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, + // dsp_state.biquad_filter_state, + // mix_buffer_count + channel, mix_buffer_count + channel, + // worker_params.sample_count, voice_info.GetInParams().node_id); + } +} + +void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id, + const BiquadFilterParameter& params, + std::array<s64, 2>& state, + std::size_t input_offset, + std::size_t output_offset, s32 sample_count, + s32 node_id) { + if (dumping_frame) { + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " + "input_mix_buffer={}, output_mix_buffer={}", + node_id, input_offset, output_offset); + } + const auto* input = GetMixBuffer(input_offset); + auto* output = GetMixBuffer(output_offset); + + // Biquad filter parameters + const auto [n0, n1, n2] = params.numerator; + const auto [d0, d1] = params.denominator; + + // Biquad filter states + auto [s0, s1] = state; + + constexpr s64 int32_min = std::numeric_limits<s32>::min(); + constexpr s64 int32_max = std::numeric_limits<s32>::max(); + + for (int i = 0; i < sample_count; ++i) { + const auto sample = static_cast<s64>(input[i]); + const auto f = (sample * n0 + s0 + 0x4000) >> 15; + const auto y = std::clamp(f, int32_min, int32_max); + s0 = sample * n1 + y * d0 + s1; + s1 = sample * n2 + y * d1; + output[i] = static_cast<s32>(y); + } + + state = {s0, s1}; +} + +void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state, + std::size_t mix_buffer_count, + std::size_t mix_buffer_offset) { + for (std::size_t i = 0; i < mix_buffer_count; i++) { + auto& sample = dsp_state.previous_samples[i]; + if (sample != 0) { + depop_buffer[mix_buffer_offset + i] += sample; + sample = 0; + } + } +} + +void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, + std::size_t mix_buffer_offset, + s32 sample_rate) { + const std::size_t end_offset = + std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount()); + const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB; + for (std::size_t i = mix_buffer_offset; i < end_offset; i++) { + if (depop_buffer[i] == 0) { + continue; + } + + depop_buffer[i] = + ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count); + } +} + +void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { + const std::size_t effect_count = effect_context.GetCount(); + const auto buffer_offset = mix_info.GetInParams().buffer_offset; + for (std::size_t i = 0; i < effect_count; i++) { + const auto index = mix_info.GetEffectOrder(i); + if (index == AudioCommon::NO_EFFECT_ORDER) { + break; + } + auto* info = effect_context.GetInfo(index); + const auto type = info->GetType(); + + // TODO(ogniK): Finish remaining effects + switch (type) { + case EffectType::Aux: + GenerateAuxCommand(buffer_offset, info, info->IsEnabled()); + break; + case EffectType::I3dl2Reverb: + GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled()); + break; + case EffectType::BiquadFilter: + GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled()); + break; + default: + break; + } + + info->UpdateForCommandGeneration(); + } +} + +void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, + bool enabled) { + auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); + const auto& params = reverb->GetParams(); + auto& state = reverb->GetState(); + const auto channel_count = params.channel_count; + + if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { + return; + } + + std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{}; + + const auto status = params.status; + for (s32 i = 0; i < channel_count; i++) { + input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); + output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); + } + + if (enabled) { + if (status == ParameterStatus::Initialized) { + InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); + } else if (status == ParameterStatus::Updating) { + UpdateI3dl2Reverb(reverb->GetParams(), state, false); + } + } + + if (enabled) { + switch (channel_count) { + case 1: + ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); + break; + case 2: + ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); + break; + case 4: + ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); + break; + case 6: + ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); + break; + } + } else { + for (s32 i = 0; i < channel_count; i++) { + // Only copy if the buffer input and output do not match! + if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { + std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); + } + } + } +} + +void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, + bool enabled) { + if (!enabled) { + return; + } + const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams(); + const auto channel_count = params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + // TODO(ogniK): Actually implement biquad filter + if (params.input[i] != params.output[i]) { + const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); + auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); + ApplyMix<1>(output, input, 32768, worker_params.sample_count); + } + } +} + +void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { + auto* aux = dynamic_cast<EffectAuxInfo*>(info); + const auto& params = aux->GetParams(); + if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { + const auto max_channels = params.count; + u32 offset{}; + for (u32 channel = 0; channel < max_channels; channel++) { + u32 write_count = 0; + if (channel == (max_channels - 1)) { + write_count = offset + worker_params.sample_count; + } + + const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset; + const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset; + + if (enabled) { + AuxInfoDSP send_info{}; + AuxInfoDSP recv_info{}; + memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); + memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); + + WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count, + GetMixBuffer(input_index), worker_params.sample_count, offset, + write_count); + memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); + + const auto samples_read = ReadAuxBuffer( + recv_info, aux->GetRecvBuffer(), params.sample_count, + GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); + memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); + + if (samples_read != static_cast<int>(worker_params.sample_count) && + samples_read <= params.sample_count) { + std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read); + } + } else { + AuxInfoDSP empty{}; + memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP)); + memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP)); + if (output_index != input_index) { + std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index), + worker_params.sample_count * sizeof(s32)); + } + } + + offset += worker_params.sample_count; + } + } +} + +ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { + if (splitter_id == AudioCommon::NO_SPLITTER) { + return nullptr; + } + return splitter_context.GetDestinationData(splitter_id, index); +} + +s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, + const s32* data, u32 sample_count, u32 write_offset, + u32 write_count) { + if (max_samples == 0) { + return 0; + } + u32 offset = dsp_info.write_offset + write_offset; + if (send_buffer == 0 || offset > max_samples) { + return 0; + } + + std::size_t data_offset{}; + u32 remaining = sample_count; + while (remaining > 0) { + // Get position in buffer + const auto base = send_buffer + (offset * sizeof(u32)); + const auto samples_to_grab = std::min(max_samples - offset, remaining); + // Write to output + memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32)); + offset = (offset + samples_to_grab) % max_samples; + remaining -= samples_to_grab; + data_offset += samples_to_grab; + } + + if (write_count != 0) { + dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples; + } + return sample_count; +} + +s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, + s32* out_data, u32 sample_count, u32 read_offset, + u32 read_count) { + if (max_samples == 0) { + return 0; + } + + u32 offset = recv_info.read_offset + read_offset; + if (recv_buffer == 0 || offset > max_samples) { + return 0; + } + + u32 remaining = sample_count; + while (remaining > 0) { + const auto base = recv_buffer + (offset * sizeof(u32)); + const auto samples_to_grab = std::min(max_samples - offset, remaining); + std::vector<s32> buffer(samples_to_grab); + memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32)); + std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32)); + out_data += samples_to_grab; + offset = (offset + samples_to_grab) % max_samples; + remaining -= samples_to_grab; + } + + if (read_count != 0) { + recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples; + } + return sample_count; +} + +void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + std::vector<u8>& work_buffer) { + // Reset state + state.lowpass_0 = 0.0f; + state.lowpass_1 = 0.0f; + state.lowpass_2 = 0.0f; + + state.early_delay_line.Reset(); + state.early_tap_steps.fill(0); + state.early_gain = 0.0f; + state.late_gain = 0.0f; + state.early_to_late_taps = 0; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + state.fdn_delay_line[i].Reset(); + state.decay_delay_line0[i].Reset(); + state.decay_delay_line1[i].Reset(); + } + state.last_reverb_echo = 0.0f; + state.center_delay_line.Reset(); + for (auto& coef : state.lpf_coefficients) { + coef.fill(0.0f); + } + state.shelf_filter.fill(0.0f); + state.dry_gain = 0.0f; + + const auto sample_rate = info.sample_rate / 1000; + f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); + + s32 delay_samples{}; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); + state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); + state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); + state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + } + delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); + state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); + state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); + + UpdateI3dl2Reverb(info, state, true); +} + +void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + bool should_clear) { + + state.dry_gain = info.dry_gain; + state.shelf_filter.fill(0.0f); + state.lowpass_0 = 0.0f; + state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); + state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); + + const auto sample_rate = info.sample_rate / 1000; + const f32 hf_gain = Pow10(info.room_hf / 2000.0f); + if (hf_gain >= 1.0f) { + state.lowpass_2 = 1.0f; + state.lowpass_1 = 0.0f; + } else { + const auto a = 1.0f - hf_gain; + const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / + static_cast<f32>(info.sample_rate))); + const auto c = std::sqrt(b * b - 4.0f * a * a); + + state.lowpass_1 = (b - c) / (2.0f * a); + state.lowpass_2 = 1.0f - state.lowpass_1; + } + state.early_to_late_taps = AudioCommon::CalculateDelaySamples( + sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); + + state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + const auto length = + FDN_MIN_DELAY_LINE_TIMES[i] + + (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); + state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); + + const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + + state.decay_delay_line0[i].GetDelay() + + state.decay_delay_line1[i].GetDelay(); + + float a = (-60.0f * static_cast<f32>(delay_sample_counts)) / + (info.decay_time * static_cast<f32>(info.sample_rate)); + float b = a / info.hf_decay_ratio; + float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) / + SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)); + float d = Pow10((b - a) / 40.0f); + float e = Pow10((b + a) / 40.0f) * 0.7071f; + + state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); + state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); + state.lpf_coefficients[2][i] = (c - d) / (c + d); + + state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); + state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); + } + + if (should_clear) { + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + state.fdn_delay_line[i].Clear(); + state.decay_delay_line0[i].Clear(); + state.decay_delay_line1[i].Clear(); + } + state.early_delay_line.Clear(); + state.center_delay_line.Clear(); + } + + const auto max_early_delay = state.early_delay_line.GetMaxDelay(); + const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); + for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { + const auto length = AudioCommon::CalculateDelaySamples( + sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); + state.early_tap_steps[tap] = std::min(length, max_early_delay); + } +} + +void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, + s32 channel, s32 node_id) { + const auto last = static_cast<s32>(last_volume * 32768.0f); + const auto current = static_cast<s32>(current_volume * 32768.0f); + const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) / + static_cast<float>(worker_params.sample_count)); + + if (dumping_frame) { + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " + "last_volume={}, current_volume={}", + node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel), + last_volume, current_volume); + } + // Apply generic gain on samples + ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, + worker_params.sample_count); +} + +void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, + const MixVolumeBuffer& last_mix_volumes, + VoiceState& dsp_state, s32 mix_buffer_offset, + s32 mix_buffer_count, s32 voice_index, s32 node_id) { + // Loop all our mix buffers + for (s32 i = 0; i < mix_buffer_count; i++) { + if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { + const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) / + static_cast<float>(worker_params.sample_count); + + if (dumping_frame) { + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " + "output={}, last_volume={}, current_volume={}", + node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], + mix_volumes[i]); + } + + dsp_state.previous_samples[i] = + ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), + last_mix_volumes[i], delta, worker_params.sample_count); + } else { + dsp_state.previous_samples[i] = 0; + } + } +} + +void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { + if (dumping_frame) { + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); + } + const auto& in_params = mix_info.GetInParams(); + GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, + in_params.sample_rate); + + GenerateEffectCommand(mix_info); + + GenerateMixCommands(mix_info); +} + +void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { + if (!mix_info.HasAnyConnection()) { + return; + } + const auto& in_params = mix_info.GetInParams(); + if (in_params.dest_mix_id != AudioCommon::NO_MIX) { + const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); + const auto& dest_in_params = dest_mix.GetInParams(); + + const auto buffer_count = in_params.buffer_count; + + for (s32 i = 0; i < buffer_count; i++) { + for (s32 j = 0; j < dest_in_params.buffer_count; j++) { + const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + j, + in_params.buffer_offset + i, mixed_volume, + in_params.node_id); + } + } + } + } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { + s32 base{}; + while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { + if (!destination_data->IsConfigured()) { + continue; + } + + const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_in_params = dest_mix.GetInParams(); + const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; + for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count); + i++) { + const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, + in_params.node_id); + } + } + } + } +} + +void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, + float volume, s32 node_id) { + + if (dumping_frame) { + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", + node_id, input_offset, output_offset, volume); + } + + auto* output = GetMixBuffer(output_offset); + const auto* input = GetMixBuffer(input_offset); + + const s32 gain = static_cast<s32>(volume * 32768.0f); + // Mix with loop unrolling + if (worker_params.sample_count % 4 == 0) { + ApplyMix<4>(output, input, gain, worker_params.sample_count); + } else if (worker_params.sample_count % 2 == 0) { + ApplyMix<2>(output, input, gain, worker_params.sample_count); + } else { + ApplyMix<1>(output, input, gain, worker_params.sample_count); + } +} + +void CommandGenerator::GenerateFinalMixCommand() { + if (dumping_frame) { + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); + } + auto& mix_info = mix_context.GetFinalMixInfo(); + const auto& in_params = mix_info.GetInParams(); + + GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, + in_params.sample_rate); + + GenerateEffectCommand(mix_info); + + for (s32 i = 0; i < in_params.buffer_count; i++) { + const s32 gain = static_cast<s32>(in_params.volume * 32768.0f); + if (dumping_frame) { + LOG_DEBUG( + Audio, + "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", + in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, + in_params.volume); + } + ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), + GetMixBuffer(in_params.buffer_offset + i), gain, + worker_params.sample_count); + } +} + +s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, s32 channel, std::size_t mix_offset) { + const auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto start_offset = + ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * + sizeof(s16); + const auto buffer_pos = wave_buffer.buffer_address + start_offset; + const auto samples_processed = std::min(sample_count, samples_remaining); + + if (in_params.channel_count == 1) { + std::vector<s16> buffer(samples_processed); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + for (std::size_t i = 0; i < buffer.size(); i++) { + sample_buffer[mix_offset + i] = buffer[i]; + } + } else { + const auto channel_count = in_params.channel_count; + std::vector<s16> buffer(samples_processed * channel_count); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + + for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) { + sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; + } + } + + return samples_processed; +} + +s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, [[maybe_unused]] s32 channel, + std::size_t mix_offset) { + const auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + + static constexpr std::array<int, 16> SIGNED_NIBBLES{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; + + constexpr std::size_t FRAME_LEN = 8; + constexpr std::size_t NIBBLES_PER_SAMPLE = 16; + constexpr std::size_t SAMPLES_PER_FRAME = 14; + + auto frame_header = dsp_state.context.header; + s32 idx = (frame_header >> 4) & 0xf; + s32 scale = frame_header & 0xf; + s16 yn1 = dsp_state.context.yn1; + s16 yn2 = dsp_state.context.yn2; + + Codec::ADPCM_Coeff coeffs; + memory.ReadBlock(in_params.additional_params_address, coeffs.data(), + sizeof(Codec::ADPCM_Coeff)); + + s32 coef1 = coeffs[idx * 2]; + s32 coef2 = coeffs[idx * 2 + 1]; + + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto samples_processed = std::min(sample_count, samples_remaining); + const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset; + + const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; + auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + + samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0); + + const auto decode_sample = [&](const int nibble) -> s16 { + const int xn = nibble * (1 << scale); + // We first transform everything into 11 bit fixed point, perform the second order + // digital filter, then transform back. + // 0x400 == 0.5 in 11 bit fixed point. + // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] + int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; + // Clamp to output range. + val = std::clamp<s32>(val, -32768, 32767); + // Advance output feedback. + yn2 = yn1; + yn1 = static_cast<s16>(val); + return yn1; + }; + + std::size_t buffer_offset{}; + std::vector<u8> buffer( + std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN)); + memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(), + buffer.size()); + std::size_t cur_mix_offset = mix_offset; + + auto remaining_samples = samples_processed; + while (remaining_samples > 0) { + if (position_in_frame % NIBBLES_PER_SAMPLE == 0) { + // Read header + frame_header = buffer[buffer_offset++]; + idx = (frame_header >> 4) & 0xf; + scale = frame_header & 0xf; + coef1 = coeffs[idx * 2]; + coef2 = coeffs[idx * 2 + 1]; + position_in_frame += 2; + + // Decode entire frame + if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) { + for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { + // Sample 1 + const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; + const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; + const s16 sample_1 = decode_sample(s0); + const s16 sample_2 = decode_sample(s1); + sample_buffer[cur_mix_offset++] = sample_1; + sample_buffer[cur_mix_offset++] = sample_2; + } + remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME); + position_in_frame += SAMPLES_PER_FRAME; + continue; + } + } + // Decode mid frame + s32 current_nibble = buffer[buffer_offset]; + if (position_in_frame++ & 0x1) { + current_nibble &= 0xf; + buffer_offset++; + } else { + current_nibble >>= 4; + } + const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]); + sample_buffer[cur_mix_offset++] = sample; + remaining_samples--; + } + + dsp_state.context.header = frame_header; + dsp_state.context.yn1 = yn1; + dsp_state.context.yn2 = yn2; + + return samples_processed; +} + +s32* CommandGenerator::GetMixBuffer(std::size_t index) { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { + return worker_params.mix_buffer_count + channel; +} + +std::size_t CommandGenerator::GetTotalMixBufferCount() const { + return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; +} + +s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, + VoiceState& dsp_state, s32 channel, + s32 target_sample_rate, s32 sample_count, + s32 node_id) { + const auto& in_params = voice_info.GetInParams(); + if (dumping_frame) { + LOG_DEBUG(Audio, + "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " + "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", + node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, + in_params.mix_id, in_params.splitter_info_id); + } + ASSERT_OR_EXECUTE(output != nullptr, { return; }); + + const auto resample_rate = static_cast<s32>( + static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) * + static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f))); + if (dsp_state.fraction + sample_count * resample_rate > + static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) { + return; + } + + auto min_required_samples = + std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); + if (min_required_samples >= sample_count) { + min_required_samples = sample_count; + } + + std::size_t temp_mix_offset{}; + bool is_buffer_completed{false}; + auto samples_remaining = sample_count; + while (samples_remaining > 0 && !is_buffer_completed) { + const auto samples_to_output = std::min(samples_remaining, min_required_samples); + const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; + + if (!in_params.behavior_flags.is_pitch_and_src_skipped) { + // Append sample histtory for resampler + for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { + sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; + } + temp_mix_offset += 4; + } + + s32 samples_read{}; + while (samples_read < samples_to_read) { + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + // No more data can be read + if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { + is_buffer_completed = true; + break; + } + + if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && + wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { + // TODO(ogniK): ADPCM loop context + } + + s32 samples_decoded{0}; + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + case SampleFormat::Adpcm: + samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + + temp_mix_offset += samples_decoded; + samples_read += samples_decoded; + dsp_state.offset += samples_decoded; + dsp_state.played_sample_count += samples_decoded; + + if (dsp_state.offset >= + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) || + samples_decoded == 0) { + // Reset our sample offset + dsp_state.offset = 0; + if (wave_buffer.is_looping) { + if (samples_decoded == 0) { + // End of our buffer + is_buffer_completed = true; + break; + } + + if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { + dsp_state.played_sample_count = 0; + } + } else { + + // Update our wave buffer states + dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; + dsp_state.wave_buffer_consumed++; + dsp_state.wave_buffer_index = + (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + if (wave_buffer.end_of_stream) { + dsp_state.played_sample_count = 0; + } + } + } + } + + if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { + // No need to resample + std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); + } else { + std::fill(sample_buffer.begin() + temp_mix_offset, + sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read), + 0); + AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction, + samples_to_output); + // Resample + for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { + dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; + } + } + output += samples_to_output; + samples_remaining -= samples_to_output; + } +} + +} // namespace AudioCore diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h new file mode 100644 index 000000000..2ebb755b0 --- /dev/null +++ b/src/audio_core/command_generator.h @@ -0,0 +1,107 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "audio_core/common.h" +#include "audio_core/voice_context.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore { +class MixContext; +class SplitterContext; +class ServerSplitterDestinationData; +class ServerMixInfo; +class EffectContext; +class EffectBase; +struct AuxInfoDSP; +struct I3dl2ReverbParams; +struct I3dl2ReverbState; +using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; + +class CommandGenerator { +public: + explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, + VoiceContext& voice_context_, MixContext& mix_context_, + SplitterContext& splitter_context_, EffectContext& effect_context_, + Core::Memory::Memory& memory_); + ~CommandGenerator(); + + void ClearMixBuffers(); + void GenerateVoiceCommands(); + void GenerateVoiceCommand(ServerVoiceInfo& voice_info); + void GenerateSubMixCommands(); + void GenerateFinalMixCommands(); + void PreCommand(); + void PostCommand(); + + [[nodiscard]] s32* GetChannelMixBuffer(s32 channel); + [[nodiscard]] const s32* GetChannelMixBuffer(s32 channel) const; + [[nodiscard]] s32* GetMixBuffer(std::size_t index); + [[nodiscard]] const s32* GetMixBuffer(std::size_t index) const; + [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; + + [[nodiscard]] std::size_t GetTotalMixBufferCount() const; + +private: + void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); + void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 mix_buffer_count, s32 channel); + void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, + s32 node_id); + void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, + const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state, + s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index, + s32 node_id); + void GenerateSubMixCommand(ServerMixInfo& mix_info); + void GenerateMixCommands(ServerMixInfo& mix_info); + void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume, + s32 node_id); + void GenerateFinalMixCommand(); + void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, + std::array<s64, 2>& state, std::size_t input_offset, + std::size_t output_offset, s32 sample_count, s32 node_id); + void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count, + std::size_t mix_buffer_offset); + void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, + std::size_t mix_buffer_offset, s32 sample_rate); + void GenerateEffectCommand(ServerMixInfo& mix_info); + void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); + void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); + void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); + [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); + + s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data, + u32 sample_count, u32 write_offset, u32 write_count); + s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, + u32 sample_count, u32 read_offset, u32 read_count); + + void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + std::vector<u8>& work_buffer); + void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); + // DSP Code + s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, + s32 channel, std::size_t mix_offset); + s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, + s32 channel, std::size_t mix_offset); + void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, + s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); + + AudioCommon::AudioRendererParameter& worker_params; + VoiceContext& voice_context; + MixContext& mix_context; + SplitterContext& splitter_context; + EffectContext& effect_context; + Core::Memory::Memory& memory; + std::vector<s32> mix_buffer{}; + std::vector<s32> sample_buffer{}; + std::vector<s32> depop_buffer{}; + bool dumping_frame{false}; +}; +} // namespace AudioCore diff --git a/src/audio_core/common.h b/src/audio_core/common.h index 7bb145c53..fe546c55d 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -3,18 +3,59 @@ // Refer to the license.txt file included. #pragma once + #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" #include "core/hle/result.h" -namespace AudioCore { +namespace AudioCommon { namespace Audren { constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; -} +constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; +} // namespace Audren constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); constexpr std::size_t MAX_MIX_BUFFERS = 24; +constexpr std::size_t MAX_BIQUAD_FILTERS = 2; +constexpr std::size_t MAX_CHANNEL_COUNT = 6; +constexpr std::size_t MAX_WAVE_BUFFERS = 4; +constexpr std::size_t MAX_SAMPLE_HISTORY = 4; +constexpr u32 STREAM_SAMPLE_RATE = 48000; +constexpr u32 STREAM_NUM_CHANNELS = 2; +constexpr s32 NO_SPLITTER = -1; +constexpr s32 NO_MIX = 0x7fffffff; +constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); +constexpr s32 FINAL_MIX = 0; +constexpr s32 NO_EFFECT_ORDER = -1; +constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant +// Any size checks seem to take the sample history into account +// and our const ends up being 0x3f04, the 4 bytes are most +// likely the sample history +constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; +constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; +constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; +constexpr std::size_t I3DL2REVERB_TAPS = 20; +constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; +using Fractional = s32; + +template <typename T> +constexpr Fractional ToFractional(T x) { + return static_cast<Fractional>(x * static_cast<T>(0x4000)); +} + +constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { + return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); +} + +constexpr s32 FractionalToFixed(Fractional x) { + const auto s = x & (1 << 13); + return static_cast<s32>(x >> 14) + s; +} + +constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { + return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); +} static constexpr u32 VersionFromRevision(u32_le rev) { // "REV7" -> 7 @@ -45,4 +86,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std return true; } -} // namespace AudioCore +struct UpdateDataSizes { + u32_le behavior{}; + u32_le memory_pool{}; + u32_le voice{}; + u32_le voice_channel_resource{}; + u32_le effect{}; + u32_le mixer{}; + u32_le sink{}; + u32_le performance{}; + u32_le splitter{}; + u32_le render_info{}; + INSERT_PADDING_WORDS(4); +}; +static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); + +struct UpdateDataHeader { + u32_le revision{}; + UpdateDataSizes size{}; + u32_le total_size{}; +}; +static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); + +struct AudioRendererParameter { + u32_le sample_rate; + u32_le sample_count; + u32_le mix_buffer_count; + u32_le submix_count; + u32_le voice_count; + u32_le sink_count; + u32_le effect_count; + u32_le performance_frame_count; + u8 is_voice_drop_enabled; + u8 unknown_21; + u8 unknown_22; + u8 execution_mode; + u32_le splitter_count; + u32_le num_splitter_send_channels; + u32_le unknown_30; + u32_le revision; +}; +static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); + +} // namespace AudioCommon diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index c27df946c..043447eaa 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -21,16 +21,27 @@ namespace AudioCore { class CubebSinkStream final : public SinkStream { public: - CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, + CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, - num_channels} { + : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, + num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; params.channels = num_channels; params.format = CUBEB_SAMPLE_S16NE; - params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO; + params.prefs = CUBEB_STREAM_PREF_PERSIST; + switch (num_channels) { + case 1: + params.layout = CUBEB_LAYOUT_MONO; + break; + case 2: + params.layout = CUBEB_LAYOUT_STEREO; + break; + case 6: + params.layout = CUBEB_LAYOUT_3F2_LFE; + break; + } u32 minimum_latency{}; if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { @@ -83,8 +94,10 @@ public: constexpr s32 clev{707}; // center mixing level coefficient constexpr s32 slev{707}; // surround mixing level coefficient - buf.push_back(left + (clev * center / 1000) + (slev * surround_left / 1000)); - buf.push_back(right + (clev * center / 1000) + (slev * surround_right / 1000)); + buf.push_back(static_cast<s16>(left + (clev * center / 1000) + + (slev * surround_left / 1000))); + buf.push_back(static_cast<s16>(right + (clev * center / 1000) + + (slev * surround_right / 1000))); } queue.Push(buf); return; @@ -180,10 +193,11 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, return *sink_streams.back(); } -long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, - void* output_buffer, long num_frames) { - CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data); - u8* buffer = reinterpret_cast<u8*>(output_buffer); +long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, + [[maybe_unused]] const void* input_buffer, void* output_buffer, + long num_frames) { + auto* impl = static_cast<CubebSinkStream*>(user_data); + auto* buffer = static_cast<u8*>(output_buffer); if (!impl) { return {}; @@ -193,6 +207,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const const std::size_t samples_to_write = num_channels * num_frames; std::size_t samples_written; + /* if (Settings::values.enable_audio_stretching.GetValue()) { const std::vector<s16> in{impl->queue.Pop()}; const std::size_t num_in{in.size() / num_channels}; @@ -207,7 +222,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const } } else { samples_written = impl->queue.Pop(buffer, samples_to_write); - } + }*/ + samples_written = impl->queue.Pop(buffer, samples_to_write); if (samples_written >= num_channels) { std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), @@ -222,7 +238,9 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const return num_frames; } -void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} +void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream, + [[maybe_unused]] void* user_data, + [[maybe_unused]] cubeb_state state) {} std::vector<std::string> ListCubebSinkDevices() { std::vector<std::string> device_list; diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp new file mode 100644 index 000000000..f4e4dd8d2 --- /dev/null +++ b/src/audio_core/delay_line.cpp @@ -0,0 +1,104 @@ +#include <cstring> +#include "audio_core/delay_line.h" + +namespace AudioCore { +DelayLineBase::DelayLineBase() = default; +DelayLineBase::~DelayLineBase() = default; + +void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { + buffer = src_buffer; + buffer_end = buffer + max_delay_; + max_delay = max_delay_; + output = buffer; + SetDelay(max_delay_); + Clear(); +} + +void DelayLineBase::SetDelay(s32 new_delay) { + if (max_delay < new_delay) { + return; + } + delay = new_delay; + input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); +} + +s32 DelayLineBase::GetDelay() const { + return delay; +} + +s32 DelayLineBase::GetMaxDelay() const { + return max_delay; +} + +f32 DelayLineBase::TapOut(s32 last_sample) { + const float* ptr = input - (last_sample + 1); + if (ptr < buffer) { + ptr += (max_delay + 1); + } + + return *ptr; +} + +f32 DelayLineBase::Tick(f32 sample) { + *(input++) = sample; + const auto out_sample = *(output++); + + if (buffer_end < input) { + input = buffer; + } + + if (buffer_end < output) { + output = buffer; + } + + return out_sample; +} + +float* DelayLineBase::GetInput() { + return input; +} + +const float* DelayLineBase::GetInput() const { + return input; +} + +f32 DelayLineBase::GetOutputSample() const { + return *output; +} + +void DelayLineBase::Clear() { + std::memset(buffer, 0, sizeof(float) * max_delay); +} + +void DelayLineBase::Reset() { + buffer = nullptr; + buffer_end = nullptr; + max_delay = 0; + input = nullptr; + output = nullptr; + delay = 0; +} + +DelayLineAllPass::DelayLineAllPass() = default; +DelayLineAllPass::~DelayLineAllPass() = default; + +void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { + DelayLineBase::Initialize(delay_, src_buffer); + SetCoefficient(coeffcient_); +} + +void DelayLineAllPass::SetCoefficient(float coeffcient_) { + coefficient = coeffcient_; +} + +f32 DelayLineAllPass::Tick(f32 sample) { + const auto temp = sample - coefficient * *output; + return coefficient * temp + DelayLineBase::Tick(temp); +} + +void DelayLineAllPass::Reset() { + coefficient = 0.0f; + DelayLineBase::Reset(); +} + +} // namespace AudioCore diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h new file mode 100644 index 000000000..cafddd432 --- /dev/null +++ b/src/audio_core/delay_line.h @@ -0,0 +1,46 @@ +#pragma once + +#include "common/common_types.h" + +namespace AudioCore { + +class DelayLineBase { +public: + DelayLineBase(); + ~DelayLineBase(); + + void Initialize(s32 max_delay_, float* src_buffer); + void SetDelay(s32 new_delay); + s32 GetDelay() const; + s32 GetMaxDelay() const; + f32 TapOut(s32 last_sample); + f32 Tick(f32 sample); + float* GetInput(); + const float* GetInput() const; + f32 GetOutputSample() const; + void Clear(); + void Reset(); + +protected: + float* buffer{nullptr}; + float* buffer_end{nullptr}; + s32 max_delay{}; + float* input{nullptr}; + float* output{nullptr}; + s32 delay{}; +}; + +class DelayLineAllPass final : public DelayLineBase { +public: + DelayLineAllPass(); + ~DelayLineAllPass(); + + void Initialize(u32 delay, float coeffcient_, f32* src_buffer); + void SetCoefficient(float coeffcient_); + f32 Tick(f32 sample); + void Reset(); + +private: + float coefficient{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp new file mode 100644 index 000000000..89e4573c7 --- /dev/null +++ b/src/audio_core/effect_context.cpp @@ -0,0 +1,321 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "audio_core/effect_context.h" + +namespace AudioCore { +namespace { +bool ValidChannelCountForEffect(s32 channel_count) { + return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; +} +} // namespace + +EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) { + effects.reserve(effect_count); + std::generate_n(std::back_inserter(effects), effect_count, + [] { return std::make_unique<EffectStubbed>(); }); +} +EffectContext::~EffectContext() = default; + +std::size_t EffectContext::GetCount() const { + return effect_count; +} + +EffectBase* EffectContext::GetInfo(std::size_t i) { + return effects.at(i).get(); +} + +EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { + switch (effect) { + case EffectType::Invalid: + effects[i] = std::make_unique<EffectStubbed>(); + break; + case EffectType::BufferMixer: + effects[i] = std::make_unique<EffectBufferMixer>(); + break; + case EffectType::Aux: + effects[i] = std::make_unique<EffectAuxInfo>(); + break; + case EffectType::Delay: + effects[i] = std::make_unique<EffectDelay>(); + break; + case EffectType::Reverb: + effects[i] = std::make_unique<EffectReverb>(); + break; + case EffectType::I3dl2Reverb: + effects[i] = std::make_unique<EffectI3dl2Reverb>(); + break; + case EffectType::BiquadFilter: + effects[i] = std::make_unique<EffectBiquadFilter>(); + break; + default: + UNREACHABLE_MSG("Unimplemented effect {}", effect); + effects[i] = std::make_unique<EffectStubbed>(); + } + return GetInfo(i); +} + +const EffectBase* EffectContext::GetInfo(std::size_t i) const { + return effects.at(i).get(); +} + +EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {} +EffectStubbed::~EffectStubbed() = default; + +void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {} +void EffectStubbed::UpdateForCommandGeneration() {} + +EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {} +EffectBase::~EffectBase() = default; + +UsageState EffectBase::GetUsage() const { + return usage; +} + +EffectType EffectBase::GetType() const { + return effect_type; +} + +bool EffectBase::IsEnabled() const { + return enabled; +} + +s32 EffectBase::GetMixID() const { + return mix_id; +} + +s32 EffectBase::GetProcessingOrder() const { + return processing_order; +} + +std::vector<u8>& EffectBase::GetWorkBuffer() { + return work_buffer; +} + +const std::vector<u8>& EffectBase::GetWorkBuffer() const { + return work_buffer; +} + +EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} +EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; + +void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { + auto& params = GetParams(); + const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data()); + if (!ValidChannelCountForEffect(reverb_params->max_channels)) { + UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels); + return; + } + + const auto last_status = params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channel_count)) { + params.channel_count = params.max_channels; + } + enabled = in_params.is_enabled; + if (last_status != ParameterStatus::Updated) { + params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + if (!skipped) { + auto& cur_work_buffer = GetWorkBuffer(); + // Has two buffers internally + cur_work_buffer.resize(in_params.buffer_size * 2); + std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); + } + } +} + +void EffectI3dl2Reverb::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +I3dl2ReverbState& EffectI3dl2Reverb::GetState() { + return state; +} + +const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { + return state; +} + +EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} +EffectBiquadFilter::~EffectBiquadFilter() = default; + +void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { + auto& params = GetParams(); + const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data()); + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + params = *biquad_params; + enabled = in_params.is_enabled; +} + +void EffectBiquadFilter::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {} +EffectAuxInfo::~EffectAuxInfo() = default; + +void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { + const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data()); + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + GetParams() = *aux_params; + enabled = in_params.is_enabled; + + if (in_params.is_new || skipped) { + skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0; + if (skipped) { + return; + } + + // There's two AuxInfos which are an identical size, the first one is managed by the cpu, + // the second is managed by the dsp. All we care about is managing the DSP one + send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP); + send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2); + + recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP); + recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2); + } +} + +void EffectAuxInfo::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } +} + +VAddr EffectAuxInfo::GetSendInfo() const { + return send_info; +} + +VAddr EffectAuxInfo::GetSendBuffer() const { + return send_buffer; +} + +VAddr EffectAuxInfo::GetRecvInfo() const { + return recv_info; +} + +VAddr EffectAuxInfo::GetRecvBuffer() const { + return recv_buffer; +} + +EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {} +EffectDelay::~EffectDelay() = default; + +void EffectDelay::Update(EffectInfo::InParams& in_params) { + const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data()); + auto& params = GetParams(); + if (!ValidChannelCountForEffect(delay_params->max_channels)) { + return; + } + + const auto last_status = params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + params = *delay_params; + if (!ValidChannelCountForEffect(delay_params->channels)) { + params.channels = params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectDelay::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {} +EffectBufferMixer::~EffectBufferMixer() = default; + +void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data()); + enabled = in_params.is_enabled; +} + +void EffectBufferMixer::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } +} + +EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {} +EffectReverb::~EffectReverb() = default; + +void EffectReverb::Update(EffectInfo::InParams& in_params) { + const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data()); + auto& params = GetParams(); + if (!ValidChannelCountForEffect(reverb_params->max_channels)) { + return; + } + + const auto last_status = params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channels)) { + params.channels = params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectReverb::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +} // namespace AudioCore diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h new file mode 100644 index 000000000..5e0655dd7 --- /dev/null +++ b/src/audio_core/effect_context.h @@ -0,0 +1,350 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <vector> +#include "audio_core/common.h" +#include "audio_core/delay_line.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { +enum class EffectType : u8 { + Invalid = 0, + BufferMixer = 1, + Aux = 2, + Delay = 3, + Reverb = 4, + I3dl2Reverb = 5, + BiquadFilter = 6, +}; + +enum class UsageStatus : u8 { + Invalid = 0, + New = 1, + Initialized = 2, + Used = 3, + Removed = 4, +}; + +enum class UsageState { + Invalid = 0, + Initialized = 1, + Running = 2, + Stopped = 3, +}; + +enum class ParameterStatus : u8 { + Initialized = 0, + Updating = 1, + Updated = 2, +}; + +struct BufferMixerParams { + std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{}; + std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{}; + std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{}; + s32_le count{}; +}; +static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); + +struct AuxInfoDSP { + u32_le read_offset{}; + u32_le write_offset{}; + u32_le remaining{}; + INSERT_PADDING_WORDS(13); +}; +static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); + +struct AuxInfo { + std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{}; + std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{}; + u32_le count{}; + s32_le sample_rate{}; + s32_le sample_count{}; + s32_le mix_buffer_count{}; + u64_le send_buffer_info{}; + u64_le send_buffer_base{}; + + u64_le return_buffer_info{}; + u64_le return_buffer_base{}; +}; +static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); + +struct I3dl2ReverbParams { + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; + u16_le max_channels{}; + u16_le channel_count{}; + INSERT_PADDING_BYTES(1); + u32_le sample_rate{}; + f32 room_hf{}; + f32 hf_reference{}; + f32 decay_time{}; + f32 hf_decay_ratio{}; + f32 room{}; + f32 reflection{}; + f32 reverb{}; + f32 diffusion{}; + f32 reflection_delay{}; + f32 reverb_delay{}; + f32 density{}; + f32 dry_gain{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); + +struct BiquadFilterParams { + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; + std::array<s16_le, 3> numerator; + std::array<s16_le, 2> denominator; + s8 channel_count{}; + ParameterStatus status{}; +}; +static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); + +struct DelayParams { + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; + u16_le max_channels{}; + u16_le channels{}; + s32_le max_delay{}; + s32_le delay{}; + s32_le sample_rate{}; + s32_le gain{}; + s32_le feedback_gain{}; + s32_le out_gain{}; + s32_le dry_gain{}; + s32_le channel_spread{}; + s32_le low_pass{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); + +struct ReverbParams { + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; + u16_le max_channels{}; + u16_le channels{}; + s32_le sample_rate{}; + s32_le mode0{}; + s32_le mode0_gain{}; + s32_le pre_delay{}; + s32_le mode1{}; + s32_le mode1_gain{}; + s32_le decay{}; + s32_le hf_decay_ratio{}; + s32_le coloration{}; + s32_le reverb_gain{}; + s32_le out_gain{}; + s32_le dry_gain{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); + +class EffectInfo { +public: + struct InParams { + EffectType type{}; + u8 is_new{}; + u8 is_enabled{}; + INSERT_PADDING_BYTES(1); + s32_le mix_id{}; + u64_le buffer_address{}; + u64_le buffer_size{}; + s32_le processing_order{}; + INSERT_PADDING_BYTES(4); + union { + std::array<u8, 0xa0> raw; + }; + }; + static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size"); + + struct OutParams { + UsageStatus status{}; + INSERT_PADDING_BYTES(15); + }; + static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); +}; + +struct AuxAddress { + VAddr send_dsp_info{}; + VAddr send_buffer_base{}; + VAddr return_dsp_info{}; + VAddr return_buffer_base{}; +}; + +class EffectBase { +public: + explicit EffectBase(EffectType effect_type_); + virtual ~EffectBase(); + + virtual void Update(EffectInfo::InParams& in_params) = 0; + virtual void UpdateForCommandGeneration() = 0; + [[nodiscard]] UsageState GetUsage() const; + [[nodiscard]] EffectType GetType() const; + [[nodiscard]] bool IsEnabled() const; + [[nodiscard]] s32 GetMixID() const; + [[nodiscard]] s32 GetProcessingOrder() const; + [[nodiscard]] std::vector<u8>& GetWorkBuffer(); + [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; + +protected: + UsageState usage{UsageState::Invalid}; + EffectType effect_type{}; + s32 mix_id{}; + s32 processing_order{}; + bool enabled = false; + std::vector<u8> work_buffer{}; +}; + +template <typename T> +class EffectGeneric : public EffectBase { +public: + explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {} + + T& GetParams() { + return internal_params; + } + + const T& GetParams() const { + return internal_params; + } + +private: + T internal_params{}; +}; + +class EffectStubbed : public EffectBase { +public: + explicit EffectStubbed(); + ~EffectStubbed() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +struct I3dl2ReverbState { + f32 lowpass_0{}; + f32 lowpass_1{}; + f32 lowpass_2{}; + + DelayLineBase early_delay_line{}; + std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; + f32 early_gain{}; + f32 late_gain{}; + + u32 early_to_late_taps{}; + std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; + std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; + std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; + f32 last_reverb_echo{}; + DelayLineBase center_delay_line{}; + std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; + std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; + f32 dry_gain{}; +}; + +class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { +public: + explicit EffectI3dl2Reverb(); + ~EffectI3dl2Reverb() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + + I3dl2ReverbState& GetState(); + const I3dl2ReverbState& GetState() const; + +private: + bool skipped = false; + I3dl2ReverbState state{}; +}; + +class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { +public: + explicit EffectBiquadFilter(); + ~EffectBiquadFilter() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectAuxInfo : public EffectGeneric<AuxInfo> { +public: + explicit EffectAuxInfo(); + ~EffectAuxInfo() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + [[nodiscard]] VAddr GetSendInfo() const; + [[nodiscard]] VAddr GetSendBuffer() const; + [[nodiscard]] VAddr GetRecvInfo() const; + [[nodiscard]] VAddr GetRecvBuffer() const; + +private: + VAddr send_info{}; + VAddr send_buffer{}; + VAddr recv_info{}; + VAddr recv_buffer{}; + bool skipped = false; + AuxAddress addresses{}; +}; + +class EffectDelay : public EffectGeneric<DelayParams> { +public: + explicit EffectDelay(); + ~EffectDelay() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectBufferMixer : public EffectGeneric<BufferMixerParams> { +public: + explicit EffectBufferMixer(); + ~EffectBufferMixer() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectReverb : public EffectGeneric<ReverbParams> { +public: + explicit EffectReverb(); + ~EffectReverb() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectContext { +public: + explicit EffectContext(std::size_t effect_count_); + ~EffectContext(); + + [[nodiscard]] std::size_t GetCount() const; + [[nodiscard]] EffectBase* GetInfo(std::size_t i); + [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect); + [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const; + +private: + std::size_t effect_count{}; + std::vector<std::unique_ptr<EffectBase>> effects; +}; +} // namespace AudioCore diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp new file mode 100644 index 000000000..d3ac90827 --- /dev/null +++ b/src/audio_core/info_updater.cpp @@ -0,0 +1,516 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/effect_context.h" +#include "audio_core/info_updater.h" +#include "audio_core/memory_pool.h" +#include "audio_core/mix_context.h" +#include "audio_core/sink_context.h" +#include "audio_core/splitter_context.h" +#include "audio_core/voice_context.h" +#include "common/logging/log.h" + +namespace AudioCore { + +InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, + BehaviorInfo& behavior_info_) + : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { + ASSERT( + AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); + std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); + output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); +} + +InfoUpdater::~InfoUpdater() = default; + +bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { + if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { + LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", + sizeof(BehaviorInfo::InParams), input_header.size.behavior); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, + sizeof(BehaviorInfo::InParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + BehaviorInfo::InParams behavior_in{}; + std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); + input_offset += sizeof(BehaviorInfo::InParams); + + // Make sure it's an audio revision we can actually support + if (!AudioCommon::IsValidRevision(behavior_in.revision)) { + LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); + return false; + } + + // Make sure that our behavior info revision matches the input + if (in_behavior_info.GetUserRevision() != behavior_in.revision) { + LOG_ERROR(Audio, + "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", + in_behavior_info.GetUserRevision(), behavior_in.revision); + return false; + } + + // Update behavior info flags + in_behavior_info.ClearError(); + in_behavior_info.UpdateFlags(behavior_in.flags); + + return true; +} + +bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) { + const auto memory_pool_count = memory_pool_info.size(); + const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; + const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; + + if (input_header.size.memory_pool != total_memory_pool_in) { + LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_memory_pool_in, input_header.size.memory_pool); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count); + std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count); + + std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); + input_offset += total_memory_pool_in; + + // Update our memory pools + for (std::size_t i = 0; i < memory_pool_count; i++) { + if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { + LOG_ERROR(Audio, "Failed to update memory pool {}!", i); + return false; + } + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, + sizeof(BehaviorInfo::InParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); + output_offset += total_memory_pool_out; + output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out); + return true; +} + +bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { + const auto voice_count = voice_context.GetVoiceCount(); + const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); + std::vector<VoiceChannelResource::InParams> resources_in(voice_count); + + if (input_header.size.voice_channel_resource != voice_size) { + LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", + voice_size, input_header.size.voice_channel_resource); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); + input_offset += voice_size; + + // Update our channel resources + for (std::size_t i = 0; i < voice_count; i++) { + // Grab our channel resource + auto& resource = voice_context.GetChannelResource(i); + resource.Update(resources_in[i]); + } + + return true; +} + +bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, + [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info, + [[maybe_unused]] VAddr audio_codec_dsp_addr) { + const auto voice_count = voice_context.GetVoiceCount(); + std::vector<VoiceInfo::InParams> voice_in(voice_count); + std::vector<VoiceInfo::OutParams> voice_out(voice_count); + + const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); + const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); + + if (input_header.size.voice != voice_in_size) { + LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", + voice_in_size, input_header.size.voice); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); + input_offset += voice_in_size; + + // Set all voices to not be in use + for (std::size_t i = 0; i < voice_count; i++) { + voice_context.GetInfo(i).GetInParams().in_use = false; + } + + // Update our voices + for (std::size_t i = 0; i < voice_count; i++) { + auto& voice_in_params = voice_in[i]; + const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count); + // Skip if it's not currently in use + if (!voice_in_params.is_in_use) { + continue; + } + // Voice states for each channel + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{}; + ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count); + + // Grab our current voice info + auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id)); + + ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); + + // Get all our channel voice states + for (std::size_t channel = 0; channel < channel_count; channel++) { + voice_states[channel] = + &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]); + } + + if (voice_in_params.is_new) { + // Default our values for our voice + voice_info.Initialize(); + if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) { + continue; + } + + // Zero out our voice states + for (std::size_t channel = 0; channel < channel_count; channel++) { + std::memset(voice_states[channel], 0, sizeof(VoiceState)); + } + } + + // Update our voice + voice_info.UpdateParameters(voice_in_params, behavior_info); + // TODO(ogniK): Handle mapping errors with behavior info based on in params response + + // Update our wave buffers + voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info); + voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states); + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size); + output_offset += voice_out_size; + output_header.size.voice = static_cast<u32>(voice_out_size); + return true; +} + +bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { + const auto effect_count = effect_context.GetCount(); + std::vector<EffectInfo::InParams> effect_in(effect_count); + std::vector<EffectInfo::OutParams> effect_out(effect_count); + + const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); + const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); + + if (input_header.size.effect != total_effect_in) { + LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_effect_in, input_header.size.effect); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); + input_offset += total_effect_in; + + // Update effects + for (std::size_t i = 0; i < effect_count; i++) { + auto* info = effect_context.GetInfo(i); + if (effect_in[i].type != info->GetType()) { + info = effect_context.RetargetEffect(i, effect_in[i].type); + } + + info->Update(effect_in[i]); + + if ((!is_active && info->GetUsage() != UsageState::Initialized) || + info->GetUsage() == UsageState::Stopped) { + effect_out[i].status = UsageStatus::Removed; + } else { + effect_out[i].status = UsageStatus::Used; + } + } + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out); + output_offset += total_effect_out; + output_header.size.effect = static_cast<u32>(total_effect_out); + + return true; +} + +bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { + std::size_t start_offset = input_offset; + std::size_t bytes_read{}; + // Update splitter context + if (!splitter_context.Update(in_params, input_offset, bytes_read)) { + LOG_ERROR(Audio, "Failed to update splitter context!"); + return false; + } + + const auto consumed = input_offset - start_offset; + + if (input_header.size.splitter != consumed) { + LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}", + bytes_read, input_header.size.splitter); + return false; + } + + return true; +} + +ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, + SplitterContext& splitter_context, + EffectContext& effect_context) { + std::vector<MixInfo::InParams> mix_in_params; + + if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + // If we're not dirty, get ALL mix in parameters + const auto context_mix_count = mix_context.GetCount(); + const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams); + if (input_header.size.mixer != total_mix_in) { + LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", + total_mix_in, input_header.size.mixer); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + mix_in_params.resize(context_mix_count); + std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in); + + input_offset += total_mix_in; + } else { + // Only update the "dirty" mixes + MixInfo::DirtyHeader dirty_header{}; + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, + sizeof(MixInfo::DirtyHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader)); + input_offset += sizeof(MixInfo::DirtyHeader); + + const auto total_mix_in = + dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader); + + if (input_header.size.mixer != total_mix_in) { + LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", + total_mix_in, input_header.size.mixer); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + if (dirty_header.mixer_count != 0) { + mix_in_params.resize(dirty_header.mixer_count); + std::memcpy(mix_in_params.data(), in_params.data() + input_offset, + mix_in_params.size() * sizeof(MixInfo::InParams)); + input_offset += mix_in_params.size() * sizeof(MixInfo::InParams); + } + } + + // Get our total input count + const auto mix_count = mix_in_params.size(); + + if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + // Only verify our buffer count if we're not dirty + std::size_t total_buffer_count{}; + for (std::size_t i = 0; i < mix_count; i++) { + const auto& in = mix_in_params[i]; + total_buffer_count += in.buffer_count; + if (static_cast<std::size_t>(in.dest_mix_id) > mix_count && + in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) { + LOG_ERROR( + Audio, + "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", + in.mix_id, in.dest_mix_id, mix_buffer_count); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + + if (total_buffer_count > mix_buffer_count) { + LOG_ERROR(Audio, + "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}", + mix_buffer_count, total_buffer_count); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + } + + if (mix_buffer_count == 0) { + LOG_ERROR(Audio, "No mix buffers!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; + } + + bool should_sort = false; + for (std::size_t i = 0; i < mix_count; i++) { + const auto& mix_in = mix_in_params[i]; + std::size_t target_mix{}; + if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { + target_mix = mix_in.mix_id; + } else { + // Non dirty supported games just use i instead of the actual mix_id + target_mix = i; + } + auto& mix_info = mix_context.GetInfo(target_mix); + auto& mix_info_params = mix_info.GetInParams(); + if (mix_info_params.in_use != mix_in.in_use) { + mix_info_params.in_use = mix_in.in_use; + mix_info.ResetEffectProcessingOrder(); + should_sort = true; + } + + if (mix_in.in_use) { + should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, + splitter_context, effect_context); + } + } + + if (should_sort && behavior_info.IsSplitterSupported()) { + // Sort our splitter data + if (!mix_context.TsortInfo(splitter_context)) { + return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED; + } + } + + // TODO(ogniK): Sort when splitter is suppoorted + + return RESULT_SUCCESS; +} + +bool InfoUpdater::UpdateSinks(SinkContext& sink_context) { + const auto sink_count = sink_context.GetCount(); + std::vector<SinkInfo::InParams> sink_in_params(sink_count); + const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); + + if (input_header.size.sink != total_sink_in) { + LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", + total_sink_in, input_header.size.effect); + return false; + } + + if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); + input_offset += total_sink_in; + + // TODO(ogniK): Properly update sinks + if (!sink_in_params.empty()) { + sink_context.UpdateMainSink(sink_in_params[0]); + } + + output_header.size.sink = static_cast<u32>(0x20 * sink_count); + output_offset += 0x20 * sink_count; + return true; +} + +bool InfoUpdater::UpdatePerformanceBuffer() { + output_header.size.performance = 0x10; + output_offset += 0x10; + return true; +} + +bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) { + const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); + + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + + BehaviorInfo::OutParams behavior_info_out{}; + behavior_info.CopyErrorInfo(behavior_info_out); + + std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out); + output_offset += total_beahvior_info_out; + output_header.size.behavior = total_beahvior_info_out; + + return true; +} + +struct RendererInfo { + u64_le elasped_frame_count{}; + INSERT_PADDING_WORDS(2); +}; +static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); + +bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) { + const auto total_renderer_info_out = sizeof(RendererInfo); + if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + RendererInfo out{}; + out.elasped_frame_count = elapsed_frame_count; + std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out); + output_offset += total_renderer_info_out; + output_header.size.render_info = total_renderer_info_out; + + return true; +} + +bool InfoUpdater::CheckConsumedSize() const { + if (output_offset != out_params.size()) { + LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining", + output_offset, out_params.size(), out_params.size() - output_offset); + return false; + } + /*if (input_offset != in_params.size()) { + LOG_ERROR(Audio, "Input is not consumed!"); + return false; + }*/ + return true; +} + +bool InfoUpdater::WriteOutputHeader() { + if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0, + sizeof(AudioCommon::UpdateDataHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION; + const auto& sz = output_header.size; + output_header.total_size += sz.behavior + sz.memory_pool + sz.voice + + sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink + + sz.performance + sz.splitter + sz.render_info; + + std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader)); + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h new file mode 100644 index 000000000..d315c91ed --- /dev/null +++ b/src/audio_core/info_updater.h @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "audio_core/common.h" +#include "common/common_types.h" + +namespace AudioCore { + +class BehaviorInfo; +class ServerMemoryPoolInfo; +class VoiceContext; +class EffectContext; +class MixContext; +class SinkContext; +class SplitterContext; + +class InfoUpdater { +public: + // TODO(ogniK): Pass process handle when we support it + InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, + BehaviorInfo& behavior_info_); + ~InfoUpdater(); + + bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); + bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info); + bool UpdateVoiceChannelResources(VoiceContext& voice_context); + bool UpdateVoices(VoiceContext& voice_context, + std::vector<ServerMemoryPoolInfo>& memory_pool_info, + VAddr audio_codec_dsp_addr); + bool UpdateEffects(EffectContext& effect_context, bool is_active); + bool UpdateSplitterInfo(SplitterContext& splitter_context); + ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, + SplitterContext& splitter_context, EffectContext& effect_context); + bool UpdateSinks(SinkContext& sink_context); + bool UpdatePerformanceBuffer(); + bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); + bool UpdateRendererInfo(std::size_t elapsed_frame_count); + bool CheckConsumedSize() const; + + bool WriteOutputHeader(); + +private: + const std::vector<u8>& in_params; + std::vector<u8>& out_params; + BehaviorInfo& behavior_info; + + AudioCommon::UpdateDataHeader input_header{}; + AudioCommon::UpdateDataHeader output_header{}; + + std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; + std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; +}; + +} // namespace AudioCore diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp new file mode 100644 index 000000000..6b6908d26 --- /dev/null +++ b/src/audio_core/memory_pool.cpp @@ -0,0 +1,61 @@ + +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/memory_pool.h" +#include "common/logging/log.h" + +namespace AudioCore { + +ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; +ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; + +bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) { + // Our state does not need to be changed + if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) { + return true; + } + + // Address or size is null + if (in_params.address == 0 || in_params.size == 0) { + LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}", + in_params.address, in_params.size); + return false; + } + + // Address or size is not aligned + if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) { + LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}", + in_params.address, in_params.size); + return false; + } + + if (in_params.state == State::RequestAttach) { + cpu_address = in_params.address; + size = in_params.size; + used = true; + out_params.state = State::Attached; + } else { + // Unexpected address + if (cpu_address != in_params.address) { + LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}", + cpu_address, in_params.address); + return false; + } + + if (size != in_params.size) { + LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size, + in_params.size); + return false; + } + + cpu_address = 0; + size = 0; + used = false; + out_params.state = State::Detached; + } + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h new file mode 100644 index 000000000..3e9e777ae --- /dev/null +++ b/src/audio_core/memory_pool.h @@ -0,0 +1,52 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { + +class ServerMemoryPoolInfo { +public: + ServerMemoryPoolInfo(); + ~ServerMemoryPoolInfo(); + + enum class State : u32_le { + Invalid = 0x0, + Aquired = 0x1, + RequestDetach = 0x2, + Detached = 0x3, + RequestAttach = 0x4, + Attached = 0x5, + Released = 0x6, + }; + + struct InParams { + u64_le address{}; + u64_le size{}; + State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size"); + + struct OutParams { + State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size"); + + bool Update(const InParams& in_params, OutParams& out_params); + +private: + // There's another entry here which is the DSP address, however since we're not talking to the + // DSP we can just use the same address provided by the guest without needing to remap + u64_le cpu_address{}; + u64_le size{}; + bool used{}; +}; + +} // namespace AudioCore diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp new file mode 100644 index 000000000..4bca72eb0 --- /dev/null +++ b/src/audio_core/mix_context.cpp @@ -0,0 +1,296 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/common.h" +#include "audio_core/effect_context.h" +#include "audio_core/mix_context.h" +#include "audio_core/splitter_context.h" + +namespace AudioCore { +MixContext::MixContext() = default; +MixContext::~MixContext() = default; + +void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, + std::size_t effect_count) { + info_count = mix_count; + infos.resize(info_count); + auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); + final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; + sorted_info.reserve(infos.size()); + for (auto& info : infos) { + sorted_info.push_back(&info); + } + + for (auto& info : infos) { + info.SetEffectCount(effect_count); + } + + // Only initialize our edge matrix and node states if splitters are supported + if (behavior_info.IsSplitterSupported()) { + node_states.Initialize(mix_count); + edge_matrix.Initialize(mix_count); + } +} + +void MixContext::UpdateDistancesFromFinalMix() { + // Set all distances to be invalid + for (std::size_t i = 0; i < info_count; i++) { + GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; + } + + for (std::size_t i = 0; i < info_count; i++) { + auto& info = GetInfo(i); + auto& in_params = info.GetInParams(); + // Populate our sorted info + sorted_info[i] = &info; + + if (!in_params.in_use) { + continue; + } + + auto mix_id = in_params.mix_id; + // Needs to be referenced out of scope + s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; + for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) { + if (mix_id == AudioCommon::FINAL_MIX) { + // If we're at the final mix, we're done + break; + } else if (mix_id == AudioCommon::NO_MIX) { + // If we have no more mix ids, we're done + distance_to_final_mix = AudioCommon::NO_FINAL_MIX; + break; + } else { + const auto& dest_mix = GetInfo(mix_id); + const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; + + if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { + // If our current mix isn't pointing to a final mix, follow through + mix_id = dest_mix.GetInParams().dest_mix_id; + } else { + // Our current mix + 1 = final distance + distance_to_final_mix = dest_mix_distance + 1; + break; + } + } + } + + // If we're out of range for our distance, mark it as no final mix + if (distance_to_final_mix >= static_cast<s32>(info_count)) { + distance_to_final_mix = AudioCommon::NO_FINAL_MIX; + } + + in_params.final_mix_distance = distance_to_final_mix; + } +} + +void MixContext::CalcMixBufferOffset() { + s32 offset{}; + for (std::size_t i = 0; i < info_count; i++) { + auto& info = GetSortedInfo(i); + auto& in_params = info.GetInParams(); + if (in_params.in_use) { + // Only update if in use + in_params.buffer_offset = offset; + offset += in_params.buffer_count; + } + } +} + +void MixContext::SortInfo() { + // Get the distance to the final mix + UpdateDistancesFromFinalMix(); + + // Sort based on the distance to the final mix + std::sort(sorted_info.begin(), sorted_info.end(), + [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { + return lhs->GetInParams().final_mix_distance > + rhs->GetInParams().final_mix_distance; + }); + + // Calculate the mix buffer offset + CalcMixBufferOffset(); +} + +bool MixContext::TsortInfo(SplitterContext& splitter_context) { + // If we're not using mixes, just calculate the mix buffer offset + if (!splitter_context.UsingSplitter()) { + CalcMixBufferOffset(); + return true; + } + // Sort our node states + if (!node_states.Tsort(edge_matrix)) { + return false; + } + + // Get our sorted list + const auto sorted_list = node_states.GetIndexList(); + std::size_t info_id{}; + for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { + // Set our sorted info + sorted_info[info_id++] = &GetInfo(*itr); + } + + // Calculate the mix buffer offset + CalcMixBufferOffset(); + return true; +} + +std::size_t MixContext::GetCount() const { + return info_count; +} + +ServerMixInfo& MixContext::GetInfo(std::size_t i) { + ASSERT(i < info_count); + return infos.at(i); +} + +const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { + ASSERT(i < info_count); + return infos.at(i); +} + +ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { + ASSERT(i < info_count); + return *sorted_info.at(i); +} + +const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { + ASSERT(i < info_count); + return *sorted_info.at(i); +} + +ServerMixInfo& MixContext::GetFinalMixInfo() { + return infos.at(AudioCommon::FINAL_MIX); +} + +const ServerMixInfo& MixContext::GetFinalMixInfo() const { + return infos.at(AudioCommon::FINAL_MIX); +} + +EdgeMatrix& MixContext::GetEdgeMatrix() { + return edge_matrix; +} + +const EdgeMatrix& MixContext::GetEdgeMatrix() const { + return edge_matrix; +} + +ServerMixInfo::ServerMixInfo() { + Cleanup(); +} +ServerMixInfo::~ServerMixInfo() = default; + +const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { + return in_params; +} + +ServerMixInfo::InParams& ServerMixInfo::GetInParams() { + return in_params; +} + +bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + BehaviorInfo& behavior_info, SplitterContext& splitter_context, + EffectContext& effect_context) { + in_params.volume = mix_in.volume; + in_params.sample_rate = mix_in.sample_rate; + in_params.buffer_count = mix_in.buffer_count; + in_params.in_use = mix_in.in_use; + in_params.mix_id = mix_in.mix_id; + in_params.node_id = mix_in.node_id; + for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { + std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), + in_params.mix_volume[i].begin()); + } + + bool require_sort = false; + + if (behavior_info.IsSplitterSupported()) { + require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); + } else { + in_params.dest_mix_id = mix_in.dest_mix_id; + in_params.splitter_id = AudioCommon::NO_SPLITTER; + } + + ResetEffectProcessingOrder(); + const auto effect_count = effect_context.GetCount(); + for (std::size_t i = 0; i < effect_count; i++) { + auto* effect_info = effect_context.GetInfo(i); + if (effect_info->GetMixID() == in_params.mix_id) { + effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i); + } + } + + // TODO(ogniK): Update effect processing order + return require_sort; +} + +bool ServerMixInfo::HasAnyConnection() const { + return in_params.splitter_id != AudioCommon::NO_SPLITTER || + in_params.mix_id != AudioCommon::NO_MIX; +} + +void ServerMixInfo::Cleanup() { + in_params.volume = 0.0f; + in_params.sample_rate = 0; + in_params.buffer_count = 0; + in_params.in_use = false; + in_params.mix_id = AudioCommon::NO_MIX; + in_params.node_id = 0; + in_params.buffer_offset = 0; + in_params.dest_mix_id = AudioCommon::NO_MIX; + in_params.splitter_id = AudioCommon::NO_SPLITTER; + std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); +} + +void ServerMixInfo::SetEffectCount(std::size_t count) { + effect_processing_order.resize(count); + ResetEffectProcessingOrder(); +} + +void ServerMixInfo::ResetEffectProcessingOrder() { + for (auto& order : effect_processing_order) { + order = AudioCommon::NO_EFFECT_ORDER; + } +} + +s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { + return effect_processing_order.at(i); +} + +bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + SplitterContext& splitter_context) { + // Mixes are identical + if (in_params.dest_mix_id == mix_in.dest_mix_id && + in_params.splitter_id == mix_in.splitter_id && + ((in_params.splitter_id == AudioCommon::NO_SPLITTER) || + !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { + return false; + } + // Remove current edges for mix id + edge_matrix.RemoveEdges(in_params.mix_id); + if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { + // If we have a valid destination mix id, set our edge matrix + edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); + } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { + // Recurse our splitter linked and set our edges + auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); + const auto length = splitter_info.GetLength(); + for (s32 i = 0; i < length; i++) { + const auto* splitter_destination = + splitter_context.GetDestinationData(mix_in.splitter_id, i); + if (splitter_destination == nullptr) { + continue; + } + if (splitter_destination->ValidMixId()) { + edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); + } + } + } + in_params.dest_mix_id = mix_in.dest_mix_id; + in_params.splitter_id = mix_in.splitter_id; + return true; +} + +} // namespace AudioCore diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h new file mode 100644 index 000000000..68bc673c6 --- /dev/null +++ b/src/audio_core/mix_context.h @@ -0,0 +1,114 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include "audio_core/common.h" +#include "audio_core/splitter_context.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace AudioCore { +class BehaviorInfo; +class EffectContext; + +class MixInfo { +public: + struct DirtyHeader { + u32_le magic{}; + u32_le mixer_count{}; + INSERT_PADDING_BYTES(0x18); + }; + static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size"); + + struct InParams { + float_le volume{}; + s32_le sample_rate{}; + s32_le buffer_count{}; + bool in_use{}; + INSERT_PADDING_BYTES(3); + s32_le mix_id{}; + s32_le effect_count{}; + u32_le node_id{}; + INSERT_PADDING_WORDS(2); + std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> + mix_volume{}; + s32_le dest_mix_id{}; + s32_le splitter_id{}; + INSERT_PADDING_WORDS(1); + }; + static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); +}; + +class ServerMixInfo { +public: + struct InParams { + float volume{}; + s32 sample_rate{}; + s32 buffer_count{}; + bool in_use{}; + s32 mix_id{}; + u32 node_id{}; + std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> + mix_volume{}; + s32 dest_mix_id{}; + s32 splitter_id{}; + s32 buffer_offset{}; + s32 final_mix_distance{}; + }; + ServerMixInfo(); + ~ServerMixInfo(); + + [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const; + [[nodiscard]] ServerMixInfo::InParams& GetInParams(); + + bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + BehaviorInfo& behavior_info, SplitterContext& splitter_context, + EffectContext& effect_context); + [[nodiscard]] bool HasAnyConnection() const; + void Cleanup(); + void SetEffectCount(std::size_t count); + void ResetEffectProcessingOrder(); + [[nodiscard]] s32 GetEffectOrder(std::size_t i) const; + +private: + std::vector<s32> effect_processing_order; + InParams in_params{}; + bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + SplitterContext& splitter_context); +}; + +class MixContext { +public: + MixContext(); + ~MixContext(); + + void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, + std::size_t effect_count); + void SortInfo(); + bool TsortInfo(SplitterContext& splitter_context); + + [[nodiscard]] std::size_t GetCount() const; + [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i); + [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const; + [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i); + [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const; + [[nodiscard]] ServerMixInfo& GetFinalMixInfo(); + [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const; + [[nodiscard]] EdgeMatrix& GetEdgeMatrix(); + [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const; + +private: + void CalcMixBufferOffset(); + void UpdateDistancesFromFinalMix(); + + NodeStates node_states{}; + EdgeMatrix edge_matrix{}; + std::size_t info_count{}; + std::vector<ServerMixInfo> infos{}; + std::vector<ServerMixInfo*> sorted_info{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp new file mode 100644 index 000000000..a69543696 --- /dev/null +++ b/src/audio_core/sink_context.cpp @@ -0,0 +1,45 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/sink_context.h" + +namespace AudioCore { +SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {} +SinkContext::~SinkContext() = default; + +std::size_t SinkContext::GetCount() const { + return sink_count; +} + +void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { + ASSERT(in.type == SinkTypes::Device); + + has_downmix_coefs = in.device.down_matrix_enabled; + if (has_downmix_coefs) { + downmix_coefficients = in.device.down_matrix_coef; + } + in_use = in.in_use; + use_count = in.device.input_count; + buffers = in.device.input; +} + +bool SinkContext::InUse() const { + return in_use; +} + +std::vector<u8> SinkContext::OutputBuffers() const { + std::vector<u8> buffer_ret(use_count); + std::memcpy(buffer_ret.data(), buffers.data(), use_count); + return buffer_ret; +} + +bool SinkContext::HasDownMixingCoefficients() const { + return has_downmix_coefs; +} + +const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { + return downmix_coefficients; +} + +} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h new file mode 100644 index 000000000..66ee4e8a0 --- /dev/null +++ b/src/audio_core/sink_context.h @@ -0,0 +1,96 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "audio_core/common.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { + +using DownmixCoefficients = std::array<float_le, 4>; + +enum class SinkTypes : u8 { + Invalid = 0, + Device = 1, + Circular = 2, +}; + +enum class SinkSampleFormat : u32_le { + None = 0, + Pcm8 = 1, + Pcm16 = 2, + Pcm24 = 3, + Pcm32 = 4, + PcmFloat = 5, + Adpcm = 6, +}; + +class SinkInfo { +public: + struct CircularBufferIn { + u64_le address; + u32_le size; + u32_le input_count; + u32_le sample_count; + u32_le previous_position; + SinkSampleFormat sample_format; + std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; + bool in_use; + INSERT_PADDING_BYTES_NOINIT(5); + }; + static_assert(sizeof(CircularBufferIn) == 0x28, + "SinkInfo::CircularBufferIn is in invalid size"); + + struct DeviceIn { + std::array<u8, 255> device_name; + INSERT_PADDING_BYTES_NOINIT(1); + s32_le input_count; + std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; + INSERT_PADDING_BYTES_NOINIT(1); + bool down_matrix_enabled; + DownmixCoefficients down_matrix_coef; + }; + static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); + + struct InParams { + SinkTypes type{}; + bool in_use{}; + INSERT_PADDING_BYTES(2); + u32_le node_id{}; + INSERT_PADDING_WORDS(6); + union { + // std::array<u8, 0x120> raw{}; + DeviceIn device; + CircularBufferIn circular_buffer; + }; + }; + static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); +}; + +class SinkContext { +public: + explicit SinkContext(std::size_t sink_count_); + ~SinkContext(); + + [[nodiscard]] std::size_t GetCount() const; + + void UpdateMainSink(const SinkInfo::InParams& in); + [[nodiscard]] bool InUse() const; + [[nodiscard]] std::vector<u8> OutputBuffers() const; + + [[nodiscard]] bool HasDownMixingCoefficients() const; + [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; + +private: + bool in_use{false}; + s32 use_count{}; + std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{}; + std::size_t sink_count{}; + bool has_downmix_coefs{false}; + DownmixCoefficients downmix_coefficients{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp new file mode 100644 index 000000000..f4bcd0391 --- /dev/null +++ b/src/audio_core/splitter_context.cpp @@ -0,0 +1,617 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/splitter_context.h" +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" + +namespace AudioCore { + +ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {} +ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; + +void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { + // Log error as these are not actually failure states + if (header.magic != SplitterMagic::DataHeader) { + LOG_ERROR(Audio, "Splitter destination header is invalid!"); + return; + } + + // Incorrect splitter id + if (header.splitter_id != id) { + LOG_ERROR(Audio, "Splitter destination ids do not match!"); + return; + } + + mix_id = header.mix_id; + // Copy our mix volumes + std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin()); + if (!in_use && header.in_use) { + // Update mix volumes + std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); + needs_update = false; + } + in_use = header.in_use; +} + +ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { + return next; +} + +const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { + return next; +} + +void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { + next = dest; +} + +bool ServerSplitterDestinationData::ValidMixId() const { + return GetMixId() != AudioCommon::NO_MIX; +} + +s32 ServerSplitterDestinationData::GetMixId() const { + return mix_id; +} + +bool ServerSplitterDestinationData::IsConfigured() const { + return in_use && ValidMixId(); +} + +float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return current_mix_volumes.at(i); +} + +const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& +ServerSplitterDestinationData::CurrentMixVolumes() const { + return current_mix_volumes; +} + +const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& +ServerSplitterDestinationData::LastMixVolumes() const { + return last_mix_volumes; +} + +void ServerSplitterDestinationData::MarkDirty() { + needs_update = true; +} + +void ServerSplitterDestinationData::UpdateInternalState() { + if (in_use && needs_update) { + std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); + } + needs_update = false; +} + +ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {} +ServerSplitterInfo::~ServerSplitterInfo() = default; + +void ServerSplitterInfo::InitializeInfos() { + send_length = 0; + head = nullptr; + new_connection = true; +} + +void ServerSplitterInfo::ClearNewConnectionFlag() { + new_connection = false; +} + +std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) { + if (header.send_id != id) { + return 0; + } + + sample_rate = header.sample_rate; + new_connection = true; + // We need to update the size here due to the splitter bug being present and providing an + // incorrect size. We're suppose to also update the header here but we just ignore and continue + return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3); +} + +ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { + return head; +} + +const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { + return head; +} + +ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { + auto* current_head = head; + for (std::size_t i = 0; i < depth; i++) { + if (current_head == nullptr) { + return nullptr; + } + current_head = current_head->GetNextDestination(); + } + return current_head; +} + +const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { + auto* current_head = head; + for (std::size_t i = 0; i < depth; i++) { + if (current_head == nullptr) { + return nullptr; + } + current_head = current_head->GetNextDestination(); + } + return current_head; +} + +bool ServerSplitterInfo::HasNewConnection() const { + return new_connection; +} + +s32 ServerSplitterInfo::GetLength() const { + return send_length; +} + +void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { + head = new_head; +} + +void ServerSplitterInfo::SetHeadDepth(s32 length) { + send_length = length; +} + +SplitterContext::SplitterContext() = default; +SplitterContext::~SplitterContext() = default; + +void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count, + std::size_t _data_count) { + if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) { + Setup(0, 0, false); + return; + } + // Only initialize if we're using splitters + Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed()); +} + +bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset, + std::size_t& bytes_read) { + const auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + if (info_count == 0 || data_count == 0) { + bytes_read = 0; + return true; + } + + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InHeader))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InHeader header{}; + std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader)); + UpdateOffsets(sizeof(SplitterInfo::InHeader)); + + if (header.magic != SplitterMagic::SplitterHeader) { + LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}", + SplitterMagic::SplitterHeader, header.magic); + return false; + } + + // Clear all connections + for (auto& info : infos) { + info.ClearNewConnectionFlag(); + } + + UpdateInfo(input, input_offset, bytes_read, header.info_count); + UpdateData(input, input_offset, bytes_read, header.data_count); + const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16); + input_offset += aligned_bytes_read - bytes_read; + bytes_read = aligned_bytes_read; + return true; +} + +bool SplitterContext::UsingSplitter() const { + return info_count > 0 && data_count > 0; +} + +ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { + ASSERT(i < info_count); + return infos.at(i); +} + +const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { + ASSERT(i < info_count); + return infos.at(i); +} + +ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { + ASSERT(i < data_count); + return datas.at(i); +} + +const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { + ASSERT(i < data_count); + return datas.at(i); +} + +ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, + std::size_t data) { + ASSERT(info < info_count); + auto& cur_info = GetInfo(info); + return cur_info.GetData(data); +} + +const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, + std::size_t data) const { + ASSERT(info < info_count); + const auto& cur_info = GetInfo(info); + return cur_info.GetData(data); +} + +void SplitterContext::UpdateInternalState() { + if (data_count == 0) { + return; + } + + for (auto& data : datas) { + data.UpdateInternalState(); + } +} + +std::size_t SplitterContext::GetInfoCount() const { + return info_count; +} + +std::size_t SplitterContext::GetDataCount() const { + return data_count; +} + +void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_, + bool is_splitter_bug_fixed) { + + info_count = info_count_; + data_count = data_count_; + + for (std::size_t i = 0; i < info_count; i++) { + auto& splitter = infos.emplace_back(static_cast<s32>(i)); + splitter.InitializeInfos(); + } + for (std::size_t i = 0; i < data_count; i++) { + datas.emplace_back(static_cast<s32>(i)); + } + + bug_fixed = is_splitter_bug_fixed; +} + +bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_splitter_count) { + const auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + for (s32 i = 0; i < in_splitter_count; i++) { + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InInfoPrams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InInfoPrams header{}; + std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams)); + + // Logged as warning as these don't actually cause a bailout for some reason + if (header.magic != SplitterMagic::InfoHeader) { + LOG_ERROR(Audio, "Bad splitter data header"); + break; + } + + if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) { + LOG_ERROR(Audio, "Bad splitter data id"); + break; + } + + UpdateOffsets(sizeof(SplitterInfo::InInfoPrams)); + auto& info = GetInfo(header.send_id); + if (!RecomposeDestination(info, header, input, input_offset)) { + LOG_ERROR(Audio, "Failed to recompose destination for splitter!"); + return false; + } + const std::size_t read = info.Update(header); + bytes_read += read; + input_offset += read; + } + return true; +} + +bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_data_count) { + const auto UpdateOffsets = [&](std::size_t read) { + input_offset += read; + bytes_read += read; + }; + + for (s32 i = 0; i < in_data_count; i++) { + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + sizeof(SplitterInfo::InDestinationParams))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + SplitterInfo::InDestinationParams header{}; + std::memcpy(&header, input.data() + input_offset, + sizeof(SplitterInfo::InDestinationParams)); + UpdateOffsets(sizeof(SplitterInfo::InDestinationParams)); + + // Logged as warning as these don't actually cause a bailout for some reason + if (header.magic != SplitterMagic::DataHeader) { + LOG_ERROR(Audio, "Bad splitter data header"); + break; + } + + if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) { + LOG_ERROR(Audio, "Bad splitter data id"); + break; + } + GetData(header.splitter_id).Update(header); + } + return true; +} + +bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, + SplitterInfo::InInfoPrams& header, + const std::vector<u8>& input, + const std::size_t& input_offset) { + // Clear our current destinations + auto* current_head = info.GetHead(); + while (current_head != nullptr) { + auto* next_head = current_head->GetNextDestination(); + current_head->SetNextDestination(nullptr); + current_head = next_head; + } + info.SetHead(nullptr); + + s32 size = header.length; + // If the splitter bug is present, calculate fixed size + if (!bug_fixed) { + if (info_count > 0) { + const auto factor = data_count / info_count; + size = std::min(header.length, static_cast<s32>(factor)); + } else { + size = 0; + } + } + + if (size < 1) { + LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size); + return true; + } + + auto* start_head = &GetData(header.resource_id_base); + current_head = start_head; + std::vector<s32_le> resource_ids(size - 1); + if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, + resource_ids.size() * sizeof(s32_le))) { + LOG_ERROR(Audio, "Buffer is an invalid size!"); + return false; + } + std::memcpy(resource_ids.data(), input.data() + input_offset, + resource_ids.size() * sizeof(s32_le)); + + for (auto resource_id : resource_ids) { + auto* head = &GetData(resource_id); + current_head->SetNextDestination(head); + current_head = head; + } + + info.SetHead(start_head); + info.SetHeadDepth(size); + + return true; +} + +NodeStates::NodeStates() = default; +NodeStates::~NodeStates() = default; + +void NodeStates::Initialize(std::size_t node_count_) { + // Setup our work parameters + node_count = node_count_; + was_node_found.resize(node_count); + was_node_completed.resize(node_count); + index_list.resize(node_count); + index_stack.Reset(node_count * node_count); +} + +bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { + return DepthFirstSearch(edge_matrix); +} + +std::size_t NodeStates::GetIndexPos() const { + return index_pos; +} + +const std::vector<s32>& NodeStates::GetIndexList() const { + return index_list; +} + +void NodeStates::PushTsortResult(s32 index) { + ASSERT(index < static_cast<s32>(node_count)); + index_list[index_pos++] = index; +} + +bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { + ResetState(); + for (std::size_t i = 0; i < node_count; i++) { + const auto node_id = static_cast<s32>(i); + + // If we don't have a state, send to our index stack for work + if (GetState(i) == NodeStates::State::NoState) { + index_stack.push(node_id); + } + + // While we have work to do in our stack + while (index_stack.Count() > 0) { + // Get the current node + const auto current_stack_index = index_stack.top(); + // Check if we've seen the node yet + const auto index_state = GetState(current_stack_index); + if (index_state == NodeStates::State::NoState) { + // Mark the node as seen + UpdateState(NodeStates::State::InFound, current_stack_index); + } else if (index_state == NodeStates::State::InFound) { + // We've seen this node before, mark it as completed + UpdateState(NodeStates::State::InCompleted, current_stack_index); + // Update our index list + PushTsortResult(current_stack_index); + // Pop the stack + index_stack.pop(); + continue; + } else if (index_state == NodeStates::State::InCompleted) { + // If our node is already sorted, clear it + index_stack.pop(); + continue; + } + + const auto edge_node_count = edge_matrix.GetNodeCount(); + for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) { + // Check if our node is connected to our edge matrix + if (!edge_matrix.Connected(current_stack_index, j)) { + continue; + } + + // Check if our node exists + const auto node_state = GetState(j); + if (node_state == NodeStates::State::NoState) { + // Add more work + index_stack.push(j); + } else if (node_state == NodeStates::State::InFound) { + UNREACHABLE_MSG("Node start marked as found"); + ResetState(); + return false; + } + } + } + } + return true; +} + +void NodeStates::ResetState() { + // Reset to the start of our index stack + index_pos = 0; + for (std::size_t i = 0; i < node_count; i++) { + // Mark all nodes as not found + was_node_found[i] = false; + // Mark all nodes as uncompleted + was_node_completed[i] = false; + // Mark all indexes as invalid + index_list[i] = -1; + } +} + +void NodeStates::UpdateState(NodeStates::State state, std::size_t i) { + switch (state) { + case NodeStates::State::NoState: + was_node_found[i] = false; + was_node_completed[i] = false; + break; + case NodeStates::State::InFound: + was_node_found[i] = true; + was_node_completed[i] = false; + break; + case NodeStates::State::InCompleted: + was_node_found[i] = false; + was_node_completed[i] = true; + break; + } +} + +NodeStates::State NodeStates::GetState(std::size_t i) { + ASSERT(i < node_count); + if (was_node_found[i]) { + // If our node exists in our found list + return NodeStates::State::InFound; + } else if (was_node_completed[i]) { + // If node is in the completed list + return NodeStates::State::InCompleted; + } else { + // If in neither + return NodeStates::State::NoState; + } +} + +NodeStates::Stack::Stack() = default; +NodeStates::Stack::~Stack() = default; + +void NodeStates::Stack::Reset(std::size_t size) { + // Mark our stack as empty + stack.resize(size); + stack_size = size; + stack_pos = 0; + std::fill(stack.begin(), stack.end(), 0); +} + +void NodeStates::Stack::push(s32 val) { + ASSERT(stack_pos < stack_size); + stack[stack_pos++] = val; +} + +std::size_t NodeStates::Stack::Count() const { + return stack_pos; +} + +s32 NodeStates::Stack::top() const { + ASSERT(stack_pos > 0); + return stack[stack_pos - 1]; +} + +s32 NodeStates::Stack::pop() { + ASSERT(stack_pos > 0); + stack_pos--; + return stack[stack_pos]; +} + +EdgeMatrix::EdgeMatrix() = default; +EdgeMatrix::~EdgeMatrix() = default; + +void EdgeMatrix::Initialize(std::size_t _node_count) { + node_count = _node_count; + edge_matrix.resize(node_count * node_count); +} + +bool EdgeMatrix::Connected(s32 a, s32 b) { + return GetState(a, b); +} + +void EdgeMatrix::Connect(s32 a, s32 b) { + SetState(a, b, true); +} + +void EdgeMatrix::Disconnect(s32 a, s32 b) { + SetState(a, b, false); +} + +void EdgeMatrix::RemoveEdges(s32 edge) { + for (std::size_t i = 0; i < node_count; i++) { + SetState(edge, static_cast<s32>(i), false); + } +} + +std::size_t EdgeMatrix::GetNodeCount() const { + return node_count; +} + +void EdgeMatrix::SetState(s32 a, s32 b, bool state) { + ASSERT(InRange(a, b)); + edge_matrix.at(a * node_count + b) = state; +} + +bool EdgeMatrix::GetState(s32 a, s32 b) { + ASSERT(InRange(a, b)); + return edge_matrix.at(a * node_count + b); +} + +bool EdgeMatrix::InRange(s32 a, s32 b) const { + const std::size_t pos = a * node_count + b; + return pos < (node_count * node_count); +} + +} // namespace AudioCore diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h new file mode 100644 index 000000000..b490627f5 --- /dev/null +++ b/src/audio_core/splitter_context.h @@ -0,0 +1,219 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <stack> +#include <vector> +#include "audio_core/common.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace AudioCore { +class BehaviorInfo; + +class EdgeMatrix { +public: + EdgeMatrix(); + ~EdgeMatrix(); + + void Initialize(std::size_t _node_count); + bool Connected(s32 a, s32 b); + void Connect(s32 a, s32 b); + void Disconnect(s32 a, s32 b); + void RemoveEdges(s32 edge); + std::size_t GetNodeCount() const; + +private: + void SetState(s32 a, s32 b, bool state); + bool GetState(s32 a, s32 b); + + bool InRange(s32 a, s32 b) const; + std::vector<bool> edge_matrix{}; + std::size_t node_count{}; +}; + +class NodeStates { +public: + enum class State { + NoState = 0, + InFound = 1, + InCompleted = 2, + }; + + // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols + class Stack { + public: + Stack(); + ~Stack(); + + void Reset(std::size_t size); + void push(s32 val); + std::size_t Count() const; + s32 top() const; + s32 pop(); + + private: + std::vector<s32> stack{}; + std::size_t stack_size{}; + std::size_t stack_pos{}; + }; + NodeStates(); + ~NodeStates(); + + void Initialize(std::size_t node_count_); + bool Tsort(EdgeMatrix& edge_matrix); + std::size_t GetIndexPos() const; + const std::vector<s32>& GetIndexList() const; + +private: + void PushTsortResult(s32 index); + bool DepthFirstSearch(EdgeMatrix& edge_matrix); + void ResetState(); + void UpdateState(State state, std::size_t i); + State GetState(std::size_t i); + + std::size_t node_count{}; + std::vector<bool> was_node_found{}; + std::vector<bool> was_node_completed{}; + std::size_t index_pos{}; + std::vector<s32> index_list{}; + Stack index_stack{}; +}; + +enum class SplitterMagic : u32_le { + SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'), + DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'), + InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'), +}; + +class SplitterInfo { +public: + struct InHeader { + SplitterMagic magic{}; + s32_le info_count{}; + s32_le data_count{}; + INSERT_PADDING_WORDS(5); + }; + static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size"); + + struct InInfoPrams { + SplitterMagic magic{}; + s32_le send_id{}; + s32_le sample_rate{}; + s32_le length{}; + s32_le resource_id_base{}; + }; + static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size"); + + struct InDestinationParams { + SplitterMagic magic{}; + s32_le splitter_id{}; + std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{}; + s32_le mix_id{}; + bool in_use{}; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(InDestinationParams) == 0x70, + "SplitterInfo::InDestinationParams is an invalid size"); +}; + +class ServerSplitterDestinationData { +public: + explicit ServerSplitterDestinationData(s32 id_); + ~ServerSplitterDestinationData(); + + void Update(SplitterInfo::InDestinationParams& header); + + ServerSplitterDestinationData* GetNextDestination(); + const ServerSplitterDestinationData* GetNextDestination() const; + void SetNextDestination(ServerSplitterDestinationData* dest); + bool ValidMixId() const; + s32 GetMixId() const; + bool IsConfigured() const; + float GetMixVolume(std::size_t i) const; + const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const; + const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const; + void MarkDirty(); + void UpdateInternalState(); + +private: + bool needs_update{}; + bool in_use{}; + s32 id{}; + s32 mix_id{}; + std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{}; + std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{}; + ServerSplitterDestinationData* next = nullptr; +}; + +class ServerSplitterInfo { +public: + explicit ServerSplitterInfo(s32 id_); + ~ServerSplitterInfo(); + + void InitializeInfos(); + void ClearNewConnectionFlag(); + std::size_t Update(SplitterInfo::InInfoPrams& header); + + ServerSplitterDestinationData* GetHead(); + const ServerSplitterDestinationData* GetHead() const; + ServerSplitterDestinationData* GetData(std::size_t depth); + const ServerSplitterDestinationData* GetData(std::size_t depth) const; + + bool HasNewConnection() const; + s32 GetLength() const; + + void SetHead(ServerSplitterDestinationData* new_head); + void SetHeadDepth(s32 length); + +private: + s32 sample_rate{}; + s32 id{}; + s32 send_length{}; + ServerSplitterDestinationData* head = nullptr; + bool new_connection{}; +}; + +class SplitterContext { +public: + SplitterContext(); + ~SplitterContext(); + + void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count, + std::size_t data_count); + + bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read); + bool UsingSplitter() const; + + ServerSplitterInfo& GetInfo(std::size_t i); + const ServerSplitterInfo& GetInfo(std::size_t i) const; + ServerSplitterDestinationData& GetData(std::size_t i); + const ServerSplitterDestinationData& GetData(std::size_t i) const; + ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data); + const ServerSplitterDestinationData* GetDestinationData(std::size_t info, + std::size_t data) const; + void UpdateInternalState(); + + std::size_t GetInfoCount() const; + std::size_t GetDataCount() const; + +private: + void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed); + bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_splitter_count); + bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset, + std::size_t& bytes_read, s32 in_data_count); + bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header, + const std::vector<u8>& input, const std::size_t& input_offset); + + std::vector<ServerSplitterInfo> infos{}; + std::vector<ServerSplitterDestinationData> datas{}; + + std::size_t info_count{}; + std::size_t data_count{}; + bool bug_fixed{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index 7be5d5087..b0f6f0c34 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -12,7 +12,6 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core_timing.h" -#include "core/core_timing_util.h" #include "core/settings.h" namespace AudioCore { @@ -32,10 +31,10 @@ u32 Stream::GetNumChannels() const { return {}; } -Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format, - ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_) - : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)}, - sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} { +Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, + ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_) + : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)}, + sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} { release_event = Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) { ReleaseActiveBuffer(ns_late); @@ -52,6 +51,14 @@ void Stream::Stop() { UNIMPLEMENTED(); } +bool Stream::Flush() { + const bool had_buffers = !queued_buffers.empty(); + while (!queued_buffers.empty()) { + queued_buffers.pop(); + } + return had_buffers; +} + void Stream::SetVolume(float volume) { game_volume = volume; } @@ -104,11 +111,14 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) { sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); - const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue() - ? std::chrono::nanoseconds::zero() - : ns_late; - const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta; - core_timing.ScheduleEvent(future_time, release_event, {}); + const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer); + + // If ns_late is higher than the update rate ignore the delay + if (ns_late > buffer_release_ns) { + ns_late = {}; + } + + core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {}); } void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { @@ -127,7 +137,7 @@ bool Stream::QueueBuffer(BufferPtr&& buffer) { return false; } -bool Stream::ContainsBuffer(Buffer::Tag tag) const { +bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const { UNIMPLEMENTED(); return {}; } @@ -135,7 +145,25 @@ bool Stream::ContainsBuffer(Buffer::Tag tag) const { std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { std::vector<Buffer::Tag> tags; for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { - tags.push_back(released_buffers.front()->GetTag()); + if (released_buffers.front()) { + tags.push_back(released_buffers.front()->GetTag()); + } else { + ASSERT_MSG(false, "Invalid tag in released_buffers!"); + } + released_buffers.pop(); + } + return tags; +} + +std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() { + std::vector<Buffer::Tag> tags; + tags.reserve(released_buffers.size()); + while (!released_buffers.empty()) { + if (released_buffers.front()) { + tags.push_back(released_buffers.front()->GetTag()); + } else { + ASSERT_MSG(false, "Invalid tag in released_buffers!"); + } released_buffers.pop(); } return tags; diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 6437b8591..559844b9b 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -44,8 +44,8 @@ public: /// Callback function type, used to change guest state on a buffer being released using ReleaseCallback = std::function<void()>; - Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format, - ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_); + Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, + ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_); /// Plays the audio stream void Play(); @@ -56,38 +56,44 @@ public: /// Queues a buffer into the audio stream, returns true on success bool QueueBuffer(BufferPtr&& buffer); + /// Flush audio buffers + bool Flush(); + /// Returns true if the audio stream contains a buffer with the specified tag - bool ContainsBuffer(Buffer::Tag tag) const; + [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const; /// Returns a vector of recently released buffers specified by tag - std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); + [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); + + /// Returns a vector of all recently released buffers specified by tag + [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(); void SetVolume(float volume); - float GetVolume() const { + [[nodiscard]] float GetVolume() const { return game_volume; } /// Returns true if the stream is currently playing - bool IsPlaying() const { + [[nodiscard]] bool IsPlaying() const { return state == State::Playing; } /// Returns the number of queued buffers - std::size_t GetQueueSize() const { + [[nodiscard]] std::size_t GetQueueSize() const { return queued_buffers.size(); } /// Gets the sample rate - u32 GetSampleRate() const { + [[nodiscard]] u32 GetSampleRate() const { return sample_rate; } /// Gets the number of channels - u32 GetNumChannels() const; + [[nodiscard]] u32 GetNumChannels() const; /// Get the state - State GetState() const; + [[nodiscard]] State GetState() const; private: /// Plays the next queued buffer in the audio stream, starting playback if necessary @@ -97,7 +103,7 @@ private: void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); /// Gets the number of core cycles when the specified buffer will be released - std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; + [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; u32 sample_rate; ///< Sample rate of the stream Format format; ///< Format of the stream diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp new file mode 100644 index 000000000..867b8fc6b --- /dev/null +++ b/src/audio_core/voice_context.cpp @@ -0,0 +1,530 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/behavior_info.h" +#include "audio_core/voice_context.h" +#include "core/memory.h" + +namespace AudioCore { + +ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {} +ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; + +bool ServerVoiceChannelResource::InUse() const { + return in_use; +} + +float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return mix_volume.at(i); +} + +float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { + ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); + return last_mix_volume.at(i); +} + +void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) { + in_use = in_params.in_use; + // Update our mix volumes only if it's in use + if (in_params.in_use) { + mix_volume = in_params.mix_volume; + } +} + +void ServerVoiceChannelResource::UpdateLastMixVolumes() { + last_mix_volume = mix_volume; +} + +const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& +ServerVoiceChannelResource::GetCurrentMixVolume() const { + return mix_volume; +} + +const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& +ServerVoiceChannelResource::GetLastMixVolume() const { + return last_mix_volume; +} + +ServerVoiceInfo::ServerVoiceInfo() { + Initialize(); +} +ServerVoiceInfo::~ServerVoiceInfo() = default; + +void ServerVoiceInfo::Initialize() { + in_params.in_use = false; + in_params.node_id = 0; + in_params.id = 0; + in_params.current_playstate = ServerPlayState::Stop; + in_params.priority = 255; + in_params.sample_rate = 0; + in_params.sample_format = SampleFormat::Invalid; + in_params.channel_count = 0; + in_params.pitch = 0.0f; + in_params.volume = 0.0f; + in_params.last_volume = 0.0f; + in_params.biquad_filter.fill({}); + in_params.wave_buffer_count = 0; + in_params.wave_bufffer_head = 0; + in_params.mix_id = AudioCommon::NO_MIX; + in_params.splitter_info_id = AudioCommon::NO_SPLITTER; + in_params.additional_params_address = 0; + in_params.additional_params_size = 0; + in_params.is_new = false; + out_params.played_sample_count = 0; + out_params.wave_buffer_consumed = 0; + in_params.voice_drop_flag = false; + in_params.buffer_mapped = false; + in_params.wave_buffer_flush_request_count = 0; + in_params.was_biquad_filter_enabled.fill(false); + + for (auto& wave_buffer : in_params.wave_buffer) { + wave_buffer.start_sample_offset = 0; + wave_buffer.end_sample_offset = 0; + wave_buffer.is_looping = false; + wave_buffer.end_of_stream = false; + wave_buffer.buffer_address = 0; + wave_buffer.buffer_size = 0; + wave_buffer.context_address = 0; + wave_buffer.context_size = 0; + wave_buffer.sent_to_dsp = true; + } + + stored_samples.clear(); +} + +void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, + BehaviorInfo& behavior_info) { + in_params.in_use = voice_in.is_in_use; + in_params.id = voice_in.id; + in_params.node_id = voice_in.node_id; + in_params.last_playstate = in_params.current_playstate; + switch (voice_in.play_state) { + case PlayState::Paused: + in_params.current_playstate = ServerPlayState::Paused; + break; + case PlayState::Stopped: + if (in_params.current_playstate != ServerPlayState::Stop) { + in_params.current_playstate = ServerPlayState::RequestStop; + } + break; + case PlayState::Started: + in_params.current_playstate = ServerPlayState::Play; + break; + default: + UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state); + break; + } + + in_params.priority = voice_in.priority; + in_params.sorting_order = voice_in.sorting_order; + in_params.sample_rate = voice_in.sample_rate; + in_params.sample_format = voice_in.sample_format; + in_params.channel_count = voice_in.channel_count; + in_params.pitch = voice_in.pitch; + in_params.volume = voice_in.volume; + in_params.biquad_filter = voice_in.biquad_filter; + in_params.wave_buffer_count = voice_in.wave_buffer_count; + in_params.wave_bufffer_head = voice_in.wave_buffer_head; + if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { + const auto in_request_count = in_params.wave_buffer_flush_request_count; + const auto voice_request_count = voice_in.wave_buffer_flush_request_count; + in_params.wave_buffer_flush_request_count = + static_cast<u8>(in_request_count + voice_request_count); + } + in_params.mix_id = voice_in.mix_id; + if (behavior_info.IsSplitterSupported()) { + in_params.splitter_info_id = voice_in.splitter_info_id; + } else { + in_params.splitter_info_id = AudioCommon::NO_SPLITTER; + } + + std::memcpy(in_params.voice_channel_resource_id.data(), + voice_in.voice_channel_resource_ids.data(), + sizeof(s32) * in_params.voice_channel_resource_id.size()); + + if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { + in_params.behavior_flags.is_played_samples_reset_at_loop_point = + voice_in.behavior_flags.is_played_samples_reset_at_loop_point; + } else { + in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0); + } + if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) { + in_params.behavior_flags.is_pitch_and_src_skipped = + voice_in.behavior_flags.is_pitch_and_src_skipped; + } else { + in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0); + } + + if (voice_in.is_voice_drop_flag_clear_requested) { + in_params.voice_drop_flag = false; + } + + if (in_params.additional_params_address != voice_in.additional_params_address || + in_params.additional_params_size != voice_in.additional_params_size) { + in_params.additional_params_address = voice_in.additional_params_address; + in_params.additional_params_size = voice_in.additional_params_size; + // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that + // our context is new + } +} + +void ServerVoiceInfo::UpdateWaveBuffers( + const VoiceInfo::InParams& voice_in, + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, + BehaviorInfo& behavior_info) { + if (voice_in.is_new) { + // Initialize our wave buffers + for (auto& wave_buffer : in_params.wave_buffer) { + wave_buffer.start_sample_offset = 0; + wave_buffer.end_sample_offset = 0; + wave_buffer.is_looping = false; + wave_buffer.end_of_stream = false; + wave_buffer.buffer_address = 0; + wave_buffer.buffer_size = 0; + wave_buffer.context_address = 0; + wave_buffer.context_size = 0; + wave_buffer.sent_to_dsp = true; + } + + // Mark all our wave buffers as invalid + for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); + channel++) { + for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) { + is_valid = false; + } + } + } + + // Update our wave buffers + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + // Assume that we have at least 1 channel voice state + const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i]; + + UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format, + have_valid_wave_buffer, behavior_info); + } +} + +void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, + const WaveBuffer& in_wave_buffer, SampleFormat sample_format, + bool is_buffer_valid, + [[maybe_unused]] BehaviorInfo& behavior_info) { + if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { + out_wavebuffer.buffer_address = 0; + out_wavebuffer.buffer_size = 0; + } + + if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { + // Validate sample offset sizings + if (sample_format == SampleFormat::Pcm16) { + const auto buffer_size = in_wave_buffer.buffer_size; + if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 || + (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) || + (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) { + // TODO(ogniK): Write error info + return; + } + } + // TODO(ogniK): ADPCM Size error + + out_wavebuffer.sent_to_dsp = false; + out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset; + out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset; + out_wavebuffer.is_looping = in_wave_buffer.is_looping; + out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream; + + out_wavebuffer.buffer_address = in_wave_buffer.buffer_address; + out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; + out_wavebuffer.context_address = in_wave_buffer.context_address; + out_wavebuffer.context_size = in_wave_buffer.context_size; + in_params.buffer_mapped = + in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; + // TODO(ogniK): Pool mapper attachment + // TODO(ogniK): IsAdpcmLoopContextBugFixed + } +} + +void ServerVoiceInfo::WriteOutStatus( + VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { + if (voice_in.is_new) { + in_params.is_new = true; + voice_out.wave_buffer_consumed = 0; + voice_out.played_sample_count = 0; + voice_out.voice_dropped = false; + } else if (!in_params.is_new) { + voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed; + voice_out.played_sample_count = voice_states[0]->played_sample_count; + voice_out.voice_dropped = in_params.voice_drop_flag; + } else { + voice_out.wave_buffer_consumed = 0; + voice_out.played_sample_count = 0; + voice_out.voice_dropped = false; + } +} + +const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { + return in_params; +} + +ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { + return in_params; +} + +const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { + return out_params; +} + +ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { + return out_params; +} + +bool ServerVoiceInfo::ShouldSkip() const { + // TODO(ogniK): Handle unmapped wave buffers or parameters + return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag; +} + +bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{}; + if (in_params.is_new) { + ResetResources(voice_context); + in_params.last_volume = in_params.volume; + in_params.is_new = false; + } + + const s32 channel_count = in_params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + const auto channel_resource = in_params.voice_channel_resource_id[i]; + dsp_voice_states[i] = + &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); + } + return UpdateParametersForCommandGeneration(dsp_voice_states); +} + +void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { + const s32 channel_count = in_params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + const auto channel_resource = in_params.voice_channel_resource_id[i]; + auto& dsp_state = + voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource)); + dsp_state = {}; + voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource)) + .UpdateLastMixVolumes(); + } +} + +bool ServerVoiceInfo::UpdateParametersForCommandGeneration( + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) { + const s32 channel_count = in_params.channel_count; + if (in_params.wave_buffer_flush_request_count > 0) { + FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states, + channel_count); + in_params.wave_buffer_flush_request_count = 0; + } + + switch (in_params.current_playstate) { + case ServerPlayState::Play: { + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + if (!in_params.wave_buffer[i].sent_to_dsp) { + for (s32 channel = 0; channel < channel_count; channel++) { + dsp_voice_states[channel]->is_wave_buffer_valid[i] = true; + } + in_params.wave_buffer[i].sent_to_dsp = true; + } + } + in_params.should_depop = false; + return HasValidWaveBuffer(dsp_voice_states[0]); + } + case ServerPlayState::Paused: + case ServerPlayState::Stop: { + in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; + return in_params.should_depop; + } + case ServerPlayState::RequestStop: { + for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { + in_params.wave_buffer[i].sent_to_dsp = true; + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + + if (dsp_state->is_wave_buffer_valid[i]) { + dsp_state->wave_buffer_index = + (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + dsp_state->wave_buffer_consumed++; + } + + dsp_state->is_wave_buffer_valid[i] = false; + } + } + + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + dsp_state->offset = 0; + dsp_state->played_sample_count = 0; + dsp_state->fraction = 0; + dsp_state->sample_history.fill(0); + dsp_state->context = {}; + } + + in_params.current_playstate = ServerPlayState::Stop; + in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; + return in_params.should_depop; + } + default: + UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate); + } + + return false; +} + +void ServerVoiceInfo::FlushWaveBuffers( + u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, + s32 channel_count) { + auto wave_head = in_params.wave_bufffer_head; + + for (u8 i = 0; i < flush_count; i++) { + in_params.wave_buffer[wave_head].sent_to_dsp = true; + for (s32 channel = 0; channel < channel_count; channel++) { + auto* dsp_state = dsp_voice_states[channel]; + dsp_state->wave_buffer_consumed++; + dsp_state->is_wave_buffer_valid[wave_head] = false; + dsp_state->wave_buffer_index = + (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + } + wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS; + } +} + +bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { + const auto& valid_wb = state->is_wave_buffer_valid; + return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); +} + +VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { + for (std::size_t i = 0; i < voice_count; i++) { + voice_channel_resources.emplace_back(static_cast<s32>(i)); + sorted_voice_info.push_back(&voice_info.emplace_back()); + voice_states.emplace_back(); + dsp_voice_states.emplace_back(); + } +} + +VoiceContext::~VoiceContext() { + sorted_voice_info.clear(); +} + +std::size_t VoiceContext::GetVoiceCount() const { + return voice_count; +} + +ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { + ASSERT(i < voice_count); + return voice_channel_resources.at(i); +} + +const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { + ASSERT(i < voice_count); + return voice_channel_resources.at(i); +} + +VoiceState& VoiceContext::GetState(std::size_t i) { + ASSERT(i < voice_count); + return voice_states.at(i); +} + +const VoiceState& VoiceContext::GetState(std::size_t i) const { + ASSERT(i < voice_count); + return voice_states.at(i); +} + +VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { + ASSERT(i < voice_count); + return dsp_voice_states.at(i); +} + +const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { + ASSERT(i < voice_count); + return dsp_voice_states.at(i); +} + +ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { + ASSERT(i < voice_count); + return voice_info.at(i); +} + +const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { + ASSERT(i < voice_count); + return voice_info.at(i); +} + +ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { + ASSERT(i < voice_count); + return *sorted_voice_info.at(i); +} + +const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { + ASSERT(i < voice_count); + return *sorted_voice_info.at(i); +} + +s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, + s32 channel_count, s32 buffer_offset, s32 sample_count, + Core::Memory::Memory& memory) { + if (wave_buffer->buffer_address == 0) { + return 0; + } + if (wave_buffer->buffer_size == 0) { + return 0; + } + if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) { + return 0; + } + + const auto samples_remaining = + (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset; + const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count; + const auto buffer_pos = wave_buffer->buffer_address + start_offset; + + s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos)); + + const auto samples_processed = std::min(sample_count, samples_remaining); + + // Fast path + if (channel_count == 1) { + for (std::ptrdiff_t i = 0; i < samples_processed; i++) { + output_buffer[i] = buffer_data[i]; + } + } else { + for (std::ptrdiff_t i = 0; i < samples_processed; i++) { + output_buffer[i] = buffer_data[i * channel_count + channel]; + } + } + + return samples_processed; +} + +void VoiceContext::SortInfo() { + for (std::size_t i = 0; i < voice_count; i++) { + sorted_voice_info[i] = &voice_info[i]; + } + + std::sort(sorted_voice_info.begin(), sorted_voice_info.end(), + [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) { + const auto& lhs_in = lhs->GetInParams(); + const auto& rhs_in = rhs->GetInParams(); + // Sort by priority + if (lhs_in.priority != rhs_in.priority) { + return lhs_in.priority > rhs_in.priority; + } else { + // If the priorities match, sort by sorting order + return lhs_in.sorting_order > rhs_in.sorting_order; + } + }); +} + +void VoiceContext::UpdateStateByDspShared() { + voice_states = dsp_voice_states; +} + +} // namespace AudioCore diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h new file mode 100644 index 000000000..70359cadb --- /dev/null +++ b/src/audio_core/voice_context.h @@ -0,0 +1,296 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "audio_core/algorithm/interpolate.h" +#include "audio_core/codec.h" +#include "audio_core/common.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace AudioCore { + +class BehaviorInfo; +class VoiceContext; + +enum class SampleFormat : u8 { + Invalid = 0, + Pcm8 = 1, + Pcm16 = 2, + Pcm24 = 3, + Pcm32 = 4, + PcmFloat = 5, + Adpcm = 6, +}; + +enum class PlayState : u8 { + Started = 0, + Stopped = 1, + Paused = 2, +}; + +enum class ServerPlayState { + Play = 0, + Stop = 1, + RequestStop = 2, + Paused = 3, +}; + +struct BiquadFilterParameter { + bool enabled{}; + INSERT_PADDING_BYTES(1); + std::array<s16, 3> numerator{}; + std::array<s16, 2> denominator{}; +}; +static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); + +struct WaveBuffer { + u64_le buffer_address{}; + u64_le buffer_size{}; + s32_le start_sample_offset{}; + s32_le end_sample_offset{}; + u8 is_looping{}; + u8 end_of_stream{}; + u8 sent_to_server{}; + INSERT_PADDING_BYTES(5); + u64 context_address{}; + u64 context_size{}; + INSERT_PADDING_BYTES(8); +}; +static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); + +struct ServerWaveBuffer { + VAddr buffer_address{}; + std::size_t buffer_size{}; + s32 start_sample_offset{}; + s32 end_sample_offset{}; + bool is_looping{}; + bool end_of_stream{}; + VAddr context_address{}; + std::size_t context_size{}; + bool sent_to_dsp{true}; +}; + +struct BehaviorFlags { + BitField<0, 1, u16> is_played_samples_reset_at_loop_point; + BitField<1, 1, u16> is_pitch_and_src_skipped; +}; +static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); + +struct ADPCMContext { + u16 header; + s16 yn1; + s16 yn2; +}; +static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); + +struct VoiceState { + s64 played_sample_count; + s32 offset; + s32 wave_buffer_index; + std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid; + s32 wave_buffer_consumed; + std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history; + s32 fraction; + VAddr context_address; + Codec::ADPCM_Coeff coeff; + ADPCMContext context; + std::array<s64, 2> biquad_filter_state; + std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples; + u32 external_context_size; + bool is_external_context_used; + bool voice_dropped; +}; + +class VoiceChannelResource { +public: + struct InParams { + s32_le id{}; + std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; + bool in_use{}; + INSERT_PADDING_BYTES(11); + }; + static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size"); +}; + +class ServerVoiceChannelResource { +public: + explicit ServerVoiceChannelResource(s32 id_); + ~ServerVoiceChannelResource(); + + bool InUse() const; + float GetCurrentMixVolumeAt(std::size_t i) const; + float GetLastMixVolumeAt(std::size_t i) const; + void Update(VoiceChannelResource::InParams& in_params); + void UpdateLastMixVolumes(); + + const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const; + const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const; + +private: + s32 id{}; + std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{}; + std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{}; + bool in_use{}; +}; + +class VoiceInfo { +public: + struct InParams { + s32_le id{}; + u32_le node_id{}; + u8 is_new{}; + u8 is_in_use{}; + PlayState play_state{}; + SampleFormat sample_format{}; + s32_le sample_rate{}; + s32_le priority{}; + s32_le sorting_order{}; + s32_le channel_count{}; + float_le pitch{}; + float_le volume{}; + std::array<BiquadFilterParameter, 2> biquad_filter{}; + s32_le wave_buffer_count{}; + s16_le wave_buffer_head{}; + INSERT_PADDING_BYTES(6); + u64_le additional_params_address{}; + u64_le additional_params_size{}; + s32_le mix_id{}; + s32_le splitter_info_id{}; + std::array<WaveBuffer, 4> wave_buffer{}; + std::array<u32_le, 6> voice_channel_resource_ids{}; + // TODO(ogniK): Remaining flags + u8 is_voice_drop_flag_clear_requested{}; + u8 wave_buffer_flush_request_count{}; + INSERT_PADDING_BYTES(2); + BehaviorFlags behavior_flags{}; + INSERT_PADDING_BYTES(16); + }; + static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size"); + + struct OutParams { + u64_le played_sample_count{}; + u32_le wave_buffer_consumed{}; + u8 voice_dropped{}; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); +}; + +class ServerVoiceInfo { +public: + struct InParams { + bool in_use{}; + bool is_new{}; + bool should_depop{}; + SampleFormat sample_format{}; + s32 sample_rate{}; + s32 channel_count{}; + s32 id{}; + s32 node_id{}; + s32 mix_id{}; + ServerPlayState current_playstate{}; + ServerPlayState last_playstate{}; + s32 priority{}; + s32 sorting_order{}; + float pitch{}; + float volume{}; + float last_volume{}; + std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; + s32 wave_buffer_count{}; + s16 wave_bufffer_head{}; + INSERT_PADDING_BYTES(2); + BehaviorFlags behavior_flags{}; + VAddr additional_params_address{}; + std::size_t additional_params_size{}; + std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{}; + std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{}; + s32 splitter_info_id{}; + u8 wave_buffer_flush_request_count{}; + bool voice_drop_flag{}; + bool buffer_mapped{}; + std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{}; + }; + + struct OutParams { + s64 played_sample_count{}; + s32 wave_buffer_consumed{}; + }; + + ServerVoiceInfo(); + ~ServerVoiceInfo(); + void Initialize(); + void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info); + void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in, + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states, + BehaviorInfo& behavior_info); + void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, + SampleFormat sample_format, bool is_buffer_valid, + BehaviorInfo& behavior_info); + void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states); + + const InParams& GetInParams() const; + InParams& GetInParams(); + + const OutParams& GetOutParams() const; + OutParams& GetOutParams(); + + bool ShouldSkip() const; + bool UpdateForCommandGeneration(VoiceContext& voice_context); + void ResetResources(VoiceContext& voice_context); + bool UpdateParametersForCommandGeneration( + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states); + void FlushWaveBuffers(u8 flush_count, + std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, + s32 channel_count); + +private: + std::vector<s16> stored_samples; + InParams in_params{}; + OutParams out_params{}; + + bool HasValidWaveBuffer(const VoiceState* state) const; +}; + +class VoiceContext { +public: + explicit VoiceContext(std::size_t voice_count_); + ~VoiceContext(); + + std::size_t GetVoiceCount() const; + ServerVoiceChannelResource& GetChannelResource(std::size_t i); + const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const; + VoiceState& GetState(std::size_t i); + const VoiceState& GetState(std::size_t i) const; + VoiceState& GetDspSharedState(std::size_t i); + const VoiceState& GetDspSharedState(std::size_t i) const; + ServerVoiceInfo& GetInfo(std::size_t i); + const ServerVoiceInfo& GetInfo(std::size_t i) const; + ServerVoiceInfo& GetSortedInfo(std::size_t i); + const ServerVoiceInfo& GetSortedInfo(std::size_t i) const; + + s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, + s32 channel_count, s32 buffer_offset, s32 sample_count, + Core::Memory::Memory& memory); + void SortInfo(); + void UpdateStateByDspShared(); + +private: + std::size_t voice_count{}; + std::vector<ServerVoiceChannelResource> voice_channel_resources{}; + std::vector<VoiceState> voice_states{}; + std::vector<VoiceState> dsp_voice_states{}; + std::vector<ServerVoiceInfo> voice_info{}; + std::vector<ServerVoiceInfo*> sorted_voice_info{}; +}; + +} // namespace AudioCore diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5d54516eb..263c457cd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -98,19 +98,20 @@ add_library(common STATIC algorithm.h alignment.h assert.h - atomic_ops.cpp atomic_ops.h detached_tasks.cpp detached_tasks.h + bit_cast.h bit_field.h + bit_set.h bit_util.h cityhash.cpp cityhash.h - color.h common_funcs.h common_paths.h common_types.h concepts.h + div_ceil.h dynamic_library.cpp dynamic_library.h fiber.cpp @@ -120,6 +121,7 @@ add_library(common STATIC hash.h hex_util.cpp hex_util.h + intrusive_red_black_tree.h logging/backend.cpp logging/backend.h logging/filter.cpp @@ -132,17 +134,17 @@ add_library(common STATIC math_util.h memory_detect.cpp memory_detect.h - memory_hook.cpp - memory_hook.h microprofile.cpp microprofile.h microprofileui.h misc.cpp - multi_level_queue.h + nvidia_flags.cpp + nvidia_flags.h page_table.cpp page_table.h param_package.cpp param_package.h + parent_of_member.h quaternion.h ring_buffer.h scm_rev.cpp @@ -150,6 +152,8 @@ add_library(common STATIC scope_exit.h spin_lock.cpp spin_lock.h + stream.cpp + stream.h string_util.cpp string_util.h swap.h @@ -158,11 +162,12 @@ add_library(common STATIC thread.cpp thread.h thread_queue_list.h + thread_worker.cpp + thread_worker.h threadsafe_queue.h time_zone.cpp time_zone.h - timer.cpp - timer.h + tree.h uint128.cpp uint128.h uuid.cpp @@ -188,8 +193,30 @@ if(ARCHITECTURE_x86_64) ) endif() +if (MSVC) + target_compile_definitions(common PRIVATE + # The standard library doesn't provide any replacement for codecvt yet + # so we can disable this deprecation warning for the time being. + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + ) + target_compile_options(common PRIVATE + /W4 + /WX + ) +else() + target_compile_options(common PRIVATE + -Werror + + $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> + ) +endif() + create_target_directory_groups(common) -find_package(Boost 1.71 COMPONENTS context headers REQUIRED) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile) -target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) +target_link_libraries(common PRIVATE lz4::lz4 xbyak) +if (MSVC) + target_link_libraries(common PRIVATE zstd::zstd) +else() + target_link_libraries(common PRIVATE zstd) +endif() diff --git a/src/common/alignment.h b/src/common/alignment.h index 5040043de..fb81f10d8 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -9,50 +9,45 @@ namespace Common { template <typename T> -[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) { - static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); +requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUp(T value, size_t size) { auto mod{static_cast<T>(value % size)}; value -= mod; return static_cast<T>(mod == T{0} ? value : value + size); } template <typename T> -[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) { - static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); - return static_cast<T>(value - value % size); +requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUpLog2(T value, size_t align_log2) { + return static_cast<T>((value + ((1ULL << align_log2) - 1)) >> align_log2 << align_log2); } template <typename T> -[[nodiscard]] constexpr T AlignBits(T value, std::size_t align) { - static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); - return static_cast<T>((value + ((1ULL << align) - 1)) >> align << align); +requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignDown(T value, size_t size) { + return static_cast<T>(value - value % size); } template <typename T> -[[nodiscard]] constexpr bool Is4KBAligned(T value) { - static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); +requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool Is4KBAligned(T value) { return (value & 0xFFF) == 0; } template <typename T> -[[nodiscard]] constexpr bool IsWordAligned(T value) { - static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); +requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool IsWordAligned(T value) { return (value & 0b11) == 0; } template <typename T> -[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) { - using U = typename std::make_unsigned<T>::type; +requires std::is_integral_v<T>[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) { + using U = typename std::make_unsigned_t<T>; const U mask = static_cast<U>(alignment - 1); return (value & mask) == 0; } -template <typename T, std::size_t Align = 16> +template <typename T, size_t Align = 16> class AlignmentAllocator { public: using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; + using size_type = size_t; + using difference_type = ptrdiff_t; using propagate_on_container_copy_assignment = std::true_type; using propagate_on_container_move_assignment = std::true_type; diff --git a/src/common/atomic_ops.cpp b/src/common/atomic_ops.cpp deleted file mode 100644 index 1612d0e67..000000000 --- a/src/common/atomic_ops.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstring> - -#include "common/atomic_ops.h" - -#if _MSC_VER -#include <intrin.h> -#endif - -namespace Common { - -#if _MSC_VER - -bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { - const u8 result = - _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected); - return result == expected; -} - -bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { - const u16 result = - _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected); - return result == expected; -} - -bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { - const u32 result = - _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected); - return result == expected; -} - -bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { - const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer), - value, expected); - return result == expected; -} - -bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) { - return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1], - value[0], - reinterpret_cast<__int64*>(expected.data())) != 0; -} - -#else - -bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) { - unsigned __int128 value_a; - unsigned __int128 expected_a; - std::memcpy(&value_a, value.data(), sizeof(u128)); - std::memcpy(&expected_a, expected.data(), sizeof(u128)); - return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a); -} - -#endif - -} // namespace Common diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h index b46888589..2b1f515e8 100644 --- a/src/common/atomic_ops.h +++ b/src/common/atomic_ops.h @@ -4,14 +4,75 @@ #pragma once +#include <cstring> +#include <memory> + #include "common/common_types.h" +#if _MSC_VER +#include <intrin.h> +#endif + namespace Common { -[[nodiscard]] bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected); -[[nodiscard]] bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected); -[[nodiscard]] bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected); -[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected); -[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected); +#if _MSC_VER + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { + const u8 result = + _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected); + return result == expected; +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { + const u16 result = + _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected); + return result == expected; +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { + const u32 result = + _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected); + return result == expected; +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { + const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer), + value, expected); + return result == expected; +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) { + return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1], + value[0], + reinterpret_cast<__int64*>(expected.data())) != 0; +} + +#else + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { + return __sync_bool_compare_and_swap(pointer, expected, value); +} + +[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) { + unsigned __int128 value_a; + unsigned __int128 expected_a; + std::memcpy(&value_a, value.data(), sizeof(u128)); + std::memcpy(&expected_a, expected.data(), sizeof(u128)); + return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a); +} + +#endif } // namespace Common diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h new file mode 100644 index 000000000..a32a063d1 --- /dev/null +++ b/src/common/bit_cast.h @@ -0,0 +1,22 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstring> +#include <type_traits> + +namespace Common { + +template <typename To, typename From> +[[nodiscard]] std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> && + std::is_trivially_copyable_v<To>, + To> +BitCast(const From& src) noexcept { + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + +} // namespace Common diff --git a/src/common/bit_set.h b/src/common/bit_set.h new file mode 100644 index 000000000..9235ad412 --- /dev/null +++ b/src/common/bit_set.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <array> +#include <bit> + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "common/common_types.h" + +namespace Common { + +namespace impl { + +template <typename Storage, size_t N> +class BitSet { + +public: + constexpr BitSet() = default; + + constexpr void SetBit(size_t i) { + this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord); + } + + constexpr void ClearBit(size_t i) { + this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord); + } + + constexpr size_t CountLeadingZero() const { + for (size_t i = 0; i < NumWords; i++) { + if (this->words[i]) { + return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]); + } + } + return FlagsPerWord * NumWords; + } + + constexpr size_t GetNextSet(size_t n) const { + for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) { + Storage word = this->words[i]; + if (!IsAligned(n + 1, FlagsPerWord)) { + word &= GetBitMask(n % FlagsPerWord) - 1; + } + if (word) { + return FlagsPerWord * i + CountLeadingZeroImpl(word); + } + } + return FlagsPerWord * NumWords; + } + +private: + static_assert(std::is_unsigned_v<Storage>); + static_assert(sizeof(Storage) <= sizeof(u64)); + + static constexpr size_t FlagsPerWord = BitSize<Storage>(); + static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord; + + static constexpr auto CountLeadingZeroImpl(Storage word) { + return std::countl_zero(static_cast<unsigned long long>(word)) - + (BitSize<unsigned long long>() - FlagsPerWord); + } + + static constexpr Storage GetBitMask(size_t bit) { + return Storage(1) << (FlagsPerWord - 1 - bit); + } + + std::array<Storage, NumWords> words{}; +}; + +} // namespace impl + +template <size_t N> +using BitSet8 = impl::BitSet<u8, N>; + +template <size_t N> +using BitSet16 = impl::BitSet<u16, N>; + +template <size_t N> +using BitSet32 = impl::BitSet<u32, N>; + +template <size_t N> +using BitSet64 = impl::BitSet<u64, N>; + +} // namespace Common diff --git a/src/common/bit_util.h b/src/common/bit_util.h index 29f59a9a3..64520ca4e 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -4,13 +4,10 @@ #pragma once +#include <bit> #include <climits> #include <cstddef> -#ifdef _MSC_VER -#include <intrin.h> -#endif - #include "common/common_types.h" namespace Common { @@ -21,124 +18,30 @@ template <typename T> return sizeof(T) * CHAR_BIT; } -#ifdef _MSC_VER -[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) { - unsigned long leading_zero = 0; - - if (_BitScanReverse(&leading_zero, value) != 0) { - return 31 - leading_zero; - } - - return 32; -} - -[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) { - unsigned long leading_zero = 0; - - if (_BitScanReverse64(&leading_zero, value) != 0) { - return 63 - leading_zero; - } - - return 64; -} -#else -[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) { - if (value == 0) { - return 32; - } - - return static_cast<u32>(__builtin_clz(value)); -} - -[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) { - if (value == 0) { - return 64; - } - - return static_cast<u32>(__builtin_clzll(value)); -} -#endif - -#ifdef _MSC_VER -[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) { - unsigned long trailing_zero = 0; - - if (_BitScanForward(&trailing_zero, value) != 0) { - return trailing_zero; - } - - return 32; -} - -[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) { - unsigned long trailing_zero = 0; - - if (_BitScanForward64(&trailing_zero, value) != 0) { - return trailing_zero; - } - - return 64; -} -#else -[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) { - if (value == 0) { - return 32; - } - - return static_cast<u32>(__builtin_ctz(value)); -} - -[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) { - if (value == 0) { - return 64; - } - - return static_cast<u32>(__builtin_ctzll(value)); +[[nodiscard]] constexpr u32 MostSignificantBit32(const u32 value) { + return 31U - static_cast<u32>(std::countl_zero(value)); } -#endif - -#ifdef _MSC_VER -[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) { - unsigned long result; - _BitScanReverse(&result, value); - return static_cast<u32>(result); +[[nodiscard]] constexpr u32 MostSignificantBit64(const u64 value) { + return 63U - static_cast<u32>(std::countl_zero(value)); } -[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) { - unsigned long result; - _BitScanReverse64(&result, value); - return static_cast<u32>(result); -} - -#else - -[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) { - return 31U - static_cast<u32>(__builtin_clz(value)); -} - -[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) { - return 63U - static_cast<u32>(__builtin_clzll(value)); -} - -#endif - -[[nodiscard]] inline u32 Log2Floor32(const u32 value) { +[[nodiscard]] constexpr u32 Log2Floor32(const u32 value) { return MostSignificantBit32(value); } -[[nodiscard]] inline u32 Log2Ceil32(const u32 value) { - const u32 log2_f = Log2Floor32(value); - return log2_f + ((value ^ (1U << log2_f)) != 0U); +[[nodiscard]] constexpr u32 Log2Floor64(const u64 value) { + return MostSignificantBit64(value); } -[[nodiscard]] inline u32 Log2Floor64(const u64 value) { - return MostSignificantBit64(value); +[[nodiscard]] constexpr u32 Log2Ceil32(const u32 value) { + const u32 log2_f = Log2Floor32(value); + return log2_f + static_cast<u32>((value ^ (1U << log2_f)) != 0U); } -[[nodiscard]] inline u32 Log2Ceil64(const u64 value) { - const u64 log2_f = static_cast<u64>(Log2Floor64(value)); - return static_cast<u32>(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL)); +[[nodiscard]] constexpr u32 Log2Ceil64(const u64 value) { + const u64 log2_f = Log2Floor64(value); + return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL)); } } // namespace Common diff --git a/src/common/color.h b/src/common/color.h deleted file mode 100644 index bbcac858e..000000000 --- a/src/common/color.h +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstring> - -#include "common/common_types.h" -#include "common/swap.h" -#include "common/vector_math.h" - -namespace Common::Color { - -/// Convert a 1-bit color component to 8 bit -[[nodiscard]] constexpr u8 Convert1To8(u8 value) { - return value * 255; -} - -/// Convert a 4-bit color component to 8 bit -[[nodiscard]] constexpr u8 Convert4To8(u8 value) { - return (value << 4) | value; -} - -/// Convert a 5-bit color component to 8 bit -[[nodiscard]] constexpr u8 Convert5To8(u8 value) { - return (value << 3) | (value >> 2); -} - -/// Convert a 6-bit color component to 8 bit -[[nodiscard]] constexpr u8 Convert6To8(u8 value) { - return (value << 2) | (value >> 4); -} - -/// Convert a 8-bit color component to 1 bit -[[nodiscard]] constexpr u8 Convert8To1(u8 value) { - return value >> 7; -} - -/// Convert a 8-bit color component to 4 bit -[[nodiscard]] constexpr u8 Convert8To4(u8 value) { - return value >> 4; -} - -/// Convert a 8-bit color component to 5 bit -[[nodiscard]] constexpr u8 Convert8To5(u8 value) { - return value >> 3; -} - -/// Convert a 8-bit color component to 6 bit -[[nodiscard]] constexpr u8 Convert8To6(u8 value) { - return value >> 2; -} - -/** - * Decode a color stored in RGBA8 format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) { - return {bytes[3], bytes[2], bytes[1], bytes[0]}; -} - -/** - * Decode a color stored in RGB8 format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) { - return {bytes[2], bytes[1], bytes[0], 255}; -} - -/** - * Decode a color stored in RG8 (aka HILO8) format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRG8(const u8* bytes) { - return {bytes[1], bytes[0], 0, 255}; -} - -/** - * Decode a color stored in RGB565 format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) { - u16_le pixel; - std::memcpy(&pixel, bytes, sizeof(pixel)); - return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F), - Convert5To8(pixel & 0x1F), 255}; -} - -/** - * Decode a color stored in RGB5A1 format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) { - u16_le pixel; - std::memcpy(&pixel, bytes, sizeof(pixel)); - return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F), - Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)}; -} - -/** - * Decode a color stored in RGBA4 format - * @param bytes Pointer to encoded source color - * @return Result color decoded as Common::Vec4<u8> - */ -[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) { - u16_le pixel; - std::memcpy(&pixel, bytes, sizeof(pixel)); - return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF), - Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)}; -} - -/** - * Decode a depth value stored in D16 format - * @param bytes Pointer to encoded source value - * @return Depth value as an u32 - */ -[[nodiscard]] inline u32 DecodeD16(const u8* bytes) { - u16_le data; - std::memcpy(&data, bytes, sizeof(data)); - return data; -} - -/** - * Decode a depth value stored in D24 format - * @param bytes Pointer to encoded source value - * @return Depth value as an u32 - */ -[[nodiscard]] inline u32 DecodeD24(const u8* bytes) { - return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; -} - -/** - * Decode a depth value and a stencil value stored in D24S8 format - * @param bytes Pointer to encoded source values - * @return Resulting values stored as a Common::Vec2 - */ -[[nodiscard]] inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) { - return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]}; -} - -/** - * Encode a color as RGBA8 format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRGBA8(const Common::Vec4<u8>& color, u8* bytes) { - bytes[3] = color.r(); - bytes[2] = color.g(); - bytes[1] = color.b(); - bytes[0] = color.a(); -} - -/** - * Encode a color as RGB8 format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRGB8(const Common::Vec4<u8>& color, u8* bytes) { - bytes[2] = color.r(); - bytes[1] = color.g(); - bytes[0] = color.b(); -} - -/** - * Encode a color as RG8 (aka HILO8) format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) { - bytes[1] = color.r(); - bytes[0] = color.g(); -} -/** - * Encode a color as RGB565 format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRGB565(const Common::Vec4<u8>& color, u8* bytes) { - const u16_le data = - (Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b()); - - std::memcpy(bytes, &data, sizeof(data)); -} - -/** - * Encode a color as RGB5A1 format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRGB5A1(const Common::Vec4<u8>& color, u8* bytes) { - const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) | - (Convert8To5(color.b()) << 1) | Convert8To1(color.a()); - - std::memcpy(bytes, &data, sizeof(data)); -} - -/** - * Encode a color as RGBA4 format - * @param color Source color to encode - * @param bytes Destination pointer to store encoded color - */ -inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) { - const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) | - (Convert8To4(color.b()) << 4) | Convert8To4(color.a()); - - std::memcpy(bytes, &data, sizeof(data)); -} - -/** - * Encode a 16 bit depth value as D16 format - * @param value 16 bit source depth value to encode - * @param bytes Pointer where to store the encoded value - */ -inline void EncodeD16(u32 value, u8* bytes) { - const u16_le data = static_cast<u16>(value); - std::memcpy(bytes, &data, sizeof(data)); -} - -/** - * Encode a 24 bit depth value as D24 format - * @param value 24 bit source depth value to encode - * @param bytes Pointer where to store the encoded value - */ -inline void EncodeD24(u32 value, u8* bytes) { - bytes[0] = value & 0xFF; - bytes[1] = (value >> 8) & 0xFF; - bytes[2] = (value >> 16) & 0xFF; -} - -/** - * Encode a 24 bit depth and 8 bit stencil values as D24S8 format - * @param depth 24 bit source depth value to encode - * @param stencil 8 bit source stencil value to encode - * @param bytes Pointer where to store the encoded value - */ -inline void EncodeD24S8(u32 depth, u8 stencil, u8* bytes) { - bytes[0] = depth & 0xFF; - bytes[1] = (depth >> 8) & 0xFF; - bytes[2] = (depth >> 16) & 0xFF; - bytes[3] = stencil; -} - -/** - * Encode a 24 bit depth value as D24X8 format (32 bits per pixel with 8 bits unused) - * @param depth 24 bit source depth value to encode - * @param bytes Pointer where to store the encoded value - * @note unused bits will not be modified - */ -inline void EncodeD24X8(u32 depth, u8* bytes) { - bytes[0] = depth & 0xFF; - bytes[1] = (depth >> 8) & 0xFF; - bytes[2] = (depth >> 16) & 0xFF; -} - -/** - * Encode an 8 bit stencil value as X24S8 format (32 bits per pixel with 24 bits unused) - * @param stencil 8 bit source stencil value to encode - * @param bytes Pointer where to store the encoded value - * @note unused bits will not be modified - */ -inline void EncodeX24S8(u8 stencil, u8* bytes) { - bytes[3] = stencil; -} - -} // namespace Common::Color diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 98421bced..71b64e32a 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -24,10 +24,10 @@ #define INSERT_PADDING_WORDS(num_words) \ std::array<u32, num_words> CONCAT2(pad, __LINE__) {} -/// These are similar to the INSERT_PADDING_* macros, but are needed for padding unions. This is -/// because unions can only be initialized by one member. -#define INSERT_UNION_PADDING_BYTES(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__) -#define INSERT_UNION_PADDING_WORDS(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__) +/// These are similar to the INSERT_PADDING_* macros but do not zero-initialize the contents. +/// This keeps the structure trivial to construct. +#define INSERT_PADDING_BYTES_NOINIT(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__) +#define INSERT_PADDING_WORDS_NOINIT(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__) #ifndef _MSC_VER @@ -64,14 +64,20 @@ __declspec(dllimport) void __stdcall DebugBreak(void); using T = std::underlying_type_t<type>; \ return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \ } \ - constexpr type& operator|=(type& a, type b) noexcept { \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ using T = std::underlying_type_t<type>; \ - a = static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \ + return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \ + } \ + constexpr type& operator|=(type& a, type b) noexcept { \ + a = a | b; \ return a; \ } \ constexpr type& operator&=(type& a, type b) noexcept { \ - using T = std::underlying_type_t<type>; \ - a = static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \ + a = a & b; \ + return a; \ + } \ + constexpr type& operator^=(type& a, type b) noexcept { \ + a = a ^ b; \ return a; \ } \ [[nodiscard]] constexpr type operator~(type key) noexcept { \ @@ -87,6 +93,31 @@ __declspec(dllimport) void __stdcall DebugBreak(void); return static_cast<T>(key) == 0; \ } +/// Evaluates a boolean expression, and returns a result unless that expression is true. +#define R_UNLESS(expr, res) \ + { \ + if (!(expr)) { \ + if (res.IsError()) { \ + LOG_ERROR(Kernel, "Failed with result: {}", res.raw); \ + } \ + return res; \ + } \ + } + +#define R_SUCCEEDED(res) (res.IsSuccess()) + +/// Evaluates an expression that returns a result, and returns the result if it would fail. +#define R_TRY(res_expr) \ + { \ + const auto _tmp_r_try_rc = (res_expr); \ + if (_tmp_r_try_rc.IsError()) { \ + return _tmp_r_try_rc; \ + } \ + } + +/// Evaluates a boolean expression, and succeeds if that expression is true. +#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), RESULT_SUCCESS) + namespace Common { [[nodiscard]] constexpr u32 MakeMagic(char a, char b, char c, char d) { diff --git a/src/common/concepts.h b/src/common/concepts.h index 5bef3ad67..aa08065a7 100644 --- a/src/common/concepts.h +++ b/src/common/concepts.h @@ -31,4 +31,8 @@ concept DerivedFrom = requires { std::is_convertible_v<const volatile Derived*, const volatile Base*>; }; +// TODO: Replace with std::convertible_to when libc++ implements it. +template <typename From, typename To> +concept ConvertibleTo = std::is_convertible_v<From, To>; + } // namespace Common diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h new file mode 100644 index 000000000..95e1489a9 --- /dev/null +++ b/src/common/div_ceil.h @@ -0,0 +1,26 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <type_traits> + +namespace Common { + +/// Ceiled integer division. +template <typename N, typename D> +requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeil(N number, + D divisor) { + return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor); +} + +/// Ceiled integer division with logarithmic divisor in base 2 +template <typename N, typename D> +requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeilLog2( + N value, D alignment_log2) { + return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); +} + +} // namespace Common diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 1c1d09ccb..3c1eefcb7 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -4,129 +4,51 @@ #include "common/assert.h" #include "common/fiber.h" -#if defined(_WIN32) || defined(WIN32) -#include <windows.h> -#else +#include "common/spin_lock.h" +#include "common/virtual_buffer.h" + #include <boost/context/detail/fcontext.hpp> -#endif namespace Common { -constexpr std::size_t default_stack_size = 256 * 1024; // 256kb - -#if defined(_WIN32) || defined(WIN32) +constexpr std::size_t default_stack_size = 256 * 1024; struct Fiber::FiberImpl { - LPVOID handle = nullptr; - LPVOID rewind_handle = nullptr; + FiberImpl() : stack{default_stack_size}, rewind_stack{default_stack_size} {} + + VirtualBuffer<u8> stack; + VirtualBuffer<u8> rewind_stack; + + SpinLock guard{}; + std::function<void(void*)> entry_point; + std::function<void(void*)> rewind_point; + void* rewind_parameter{}; + void* start_parameter{}; + std::shared_ptr<Fiber> previous_fiber; + bool is_thread_fiber{}; + bool released{}; + + u8* stack_limit{}; + u8* rewind_stack_limit{}; + boost::context::detail::fcontext_t context{}; + boost::context::detail::fcontext_t rewind_context{}; }; -void Fiber::Start() { - ASSERT(previous_fiber != nullptr); - previous_fiber->guard.unlock(); - previous_fiber.reset(); - entry_point(start_parameter); - UNREACHABLE(); -} - -void Fiber::OnRewind() { - ASSERT(impl->handle != nullptr); - DeleteFiber(impl->handle); - impl->handle = impl->rewind_handle; - impl->rewind_handle = nullptr; - rewind_point(rewind_parameter); - UNREACHABLE(); -} - -void Fiber::FiberStartFunc(void* fiber_parameter) { - auto fiber = static_cast<Fiber*>(fiber_parameter); - fiber->Start(); -} - -void Fiber::RewindStartFunc(void* fiber_parameter) { - auto fiber = static_cast<Fiber*>(fiber_parameter); - fiber->OnRewind(); -} - -Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) - : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { - impl = std::make_unique<FiberImpl>(); - impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this); -} - -Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {} - -Fiber::~Fiber() { - if (released) { - return; - } - // Make sure the Fiber is not being used - const bool locked = guard.try_lock(); - ASSERT_MSG(locked, "Destroying a fiber that's still running"); - if (locked) { - guard.unlock(); - } - DeleteFiber(impl->handle); -} - -void Fiber::Exit() { - ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); - if (!is_thread_fiber) { - return; - } - ConvertFiberToThread(); - guard.unlock(); - released = true; -} - -void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) { - rewind_point = std::move(rewind_func); - rewind_parameter = start_parameter; -} - -void Fiber::Rewind() { - ASSERT(rewind_point); - ASSERT(impl->rewind_handle == nullptr); - impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this); - SwitchToFiber(impl->rewind_handle); -} - -void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) { - ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); - ASSERT_MSG(to != nullptr, "Next fiber is null!"); - to->guard.lock(); - to->previous_fiber = from; - SwitchToFiber(to->impl->handle); - ASSERT(from->previous_fiber != nullptr); - from->previous_fiber->guard.unlock(); - from->previous_fiber.reset(); +void Fiber::SetStartParameter(void* new_parameter) { + impl->start_parameter = new_parameter; } -std::shared_ptr<Fiber> Fiber::ThreadToFiber() { - std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; - fiber->guard.lock(); - fiber->impl->handle = ConvertThreadToFiber(nullptr); - fiber->is_thread_fiber = true; - return fiber; +void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param) { + impl->rewind_point = std::move(rewind_func); + impl->rewind_parameter = rewind_param; } -#else - -struct Fiber::FiberImpl { - alignas(64) std::array<u8, default_stack_size> stack; - alignas(64) std::array<u8, default_stack_size> rewind_stack; - u8* stack_limit; - u8* rewind_stack_limit; - boost::context::detail::fcontext_t context; - boost::context::detail::fcontext_t rewind_context; -}; - void Fiber::Start(boost::context::detail::transfer_t& transfer) { - ASSERT(previous_fiber != nullptr); - previous_fiber->impl->context = transfer.fctx; - previous_fiber->guard.unlock(); - previous_fiber.reset(); - entry_point(start_parameter); + ASSERT(impl->previous_fiber != nullptr); + impl->previous_fiber->impl->context = transfer.fctx; + impl->previous_fiber->impl->guard.unlock(); + impl->previous_fiber.reset(); + impl->entry_point(impl->start_parameter); UNREACHABLE(); } @@ -137,23 +59,24 @@ void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transf u8* tmp = impl->stack_limit; impl->stack_limit = impl->rewind_stack_limit; impl->rewind_stack_limit = tmp; - rewind_point(rewind_parameter); + impl->rewind_point(impl->rewind_parameter); UNREACHABLE(); } void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { - auto fiber = static_cast<Fiber*>(transfer.data); + auto* fiber = static_cast<Fiber*>(transfer.data); fiber->Start(transfer); } void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) { - auto fiber = static_cast<Fiber*>(transfer.data); + auto* fiber = static_cast<Fiber*>(transfer.data); fiber->OnRewind(transfer); } Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter) - : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { - impl = std::make_unique<FiberImpl>(); + : impl{std::make_unique<FiberImpl>()} { + impl->entry_point = std::move(entry_point_func); + impl->start_parameter = start_parameter; impl->stack_limit = impl->stack.data(); impl->rewind_stack_limit = impl->rewind_stack.data(); u8* stack_base = impl->stack_limit + default_stack_size; @@ -161,37 +84,31 @@ Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_paramete boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc); } -void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) { - rewind_point = std::move(rewind_func); - rewind_parameter = start_parameter; -} - Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {} Fiber::~Fiber() { - if (released) { + if (impl->released) { return; } // Make sure the Fiber is not being used - const bool locked = guard.try_lock(); + const bool locked = impl->guard.try_lock(); ASSERT_MSG(locked, "Destroying a fiber that's still running"); if (locked) { - guard.unlock(); + impl->guard.unlock(); } } void Fiber::Exit() { - - ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); - if (!is_thread_fiber) { + ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber"); + if (!impl->is_thread_fiber) { return; } - guard.unlock(); - released = true; + impl->guard.unlock(); + impl->released = true; } void Fiber::Rewind() { - ASSERT(rewind_point); + ASSERT(impl->rewind_point); ASSERT(impl->rewind_context == nullptr); u8* stack_base = impl->rewind_stack_limit + default_stack_size; impl->rewind_context = @@ -199,24 +116,23 @@ void Fiber::Rewind() { boost::context::detail::jump_fcontext(impl->rewind_context, this); } -void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) { +void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) { ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); ASSERT_MSG(to != nullptr, "Next fiber is null!"); - to->guard.lock(); - to->previous_fiber = from; + to->impl->guard.lock(); + to->impl->previous_fiber = from; auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get()); - ASSERT(from->previous_fiber != nullptr); - from->previous_fiber->impl->context = transfer.fctx; - from->previous_fiber->guard.unlock(); - from->previous_fiber.reset(); + ASSERT(from->impl->previous_fiber != nullptr); + from->impl->previous_fiber->impl->context = transfer.fctx; + from->impl->previous_fiber->impl->guard.unlock(); + from->impl->previous_fiber.reset(); } std::shared_ptr<Fiber> Fiber::ThreadToFiber() { std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()}; - fiber->guard.lock(); - fiber->is_thread_fiber = true; + fiber->impl->guard.lock(); + fiber->impl->is_thread_fiber = true; return fiber; } -#endif } // namespace Common diff --git a/src/common/fiber.h b/src/common/fiber.h index 89dde5e36..f7f587f8c 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -7,14 +7,9 @@ #include <functional> #include <memory> -#include "common/common_types.h" -#include "common/spin_lock.h" - -#if !defined(_WIN32) && !defined(WIN32) namespace boost::context::detail { struct transfer_t; } -#endif namespace Common { @@ -46,10 +41,10 @@ public: /// Yields control from Fiber 'from' to Fiber 'to' /// Fiber 'from' must be the currently running fiber. - static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to); + static void YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to); [[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber(); - void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter); + void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param); void Rewind(); @@ -57,36 +52,18 @@ public: void Exit(); /// Changes the start parameter of the fiber. Has no effect if the fiber already started - void SetStartParameter(void* new_parameter) { - start_parameter = new_parameter; - } + void SetStartParameter(void* new_parameter); private: Fiber(); -#if defined(_WIN32) || defined(WIN32) - void OnRewind(); - void Start(); - static void FiberStartFunc(void* fiber_parameter); - static void RewindStartFunc(void* fiber_parameter); -#else void OnRewind(boost::context::detail::transfer_t& transfer); void Start(boost::context::detail::transfer_t& transfer); static void FiberStartFunc(boost::context::detail::transfer_t transfer); static void RewindStartFunc(boost::context::detail::transfer_t transfer); -#endif struct FiberImpl; - - SpinLock guard{}; - std::function<void(void*)> entry_point; - std::function<void(void*)> rewind_point; - void* rewind_parameter{}; - void* start_parameter{}; - std::shared_ptr<Fiber> previous_fiber; std::unique_ptr<FiberImpl> impl; - bool is_thread_fiber{}; - bool released{}; }; } // namespace Common diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 16c3713e0..18fbfa25b 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -472,13 +472,14 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, } bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) { - const auto callback = [recursion](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string new_path = directory + DIR_SEP_CHR + virtual_name; + const auto callback = [recursion](u64*, const std::string& directory, + const std::string& virtual_name) { + const std::string new_path = directory + DIR_SEP_CHR + virtual_name; if (IsDirectory(new_path)) { - if (recursion == 0) + if (recursion == 0) { return false; + } return DeleteDirRecursively(new_path, recursion - 1); } return Delete(new_path); @@ -492,7 +493,8 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) return true; } -void CopyDir(const std::string& source_path, const std::string& dest_path) { +void CopyDir([[maybe_unused]] const std::string& source_path, + [[maybe_unused]] const std::string& dest_path) { #ifndef _WIN32 if (source_path == dest_path) { return; @@ -553,7 +555,7 @@ std::optional<std::string> GetCurrentDir() { std::string strDir = dir; #endif free(dir); - return std::move(strDir); + return strDir; } bool SetCurrentDir(const std::string& directory) { @@ -772,21 +774,23 @@ std::size_t ReadFileToString(bool text_file, const std::string& filename, std::s void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, std::array<char, 4>& extension) { - const std::string forbidden_characters = ".\"/\\[]:;=, "; + static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, "; // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces. short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}}; extension = {{' ', ' ', ' ', '\0'}}; - std::string::size_type point = filename.rfind('.'); - if (point == filename.size() - 1) + auto point = filename.rfind('.'); + if (point == filename.size() - 1) { point = filename.rfind('.', point); + } // Get short name. int j = 0; for (char letter : filename.substr(0, point)) { - if (forbidden_characters.find(letter, 0) != std::string::npos) + if (forbidden_characters.find(letter, 0) != std::string::npos) { continue; + } if (j == 8) { // TODO(Link Mauve): also do that for filenames containing a space. // TODO(Link Mauve): handle multiple files having the same short name. @@ -794,14 +798,15 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam short_name[7] = '1'; break; } - short_name[j++] = toupper(letter); + short_name[j++] = static_cast<char>(std::toupper(letter)); } // Get extension. if (point != std::string::npos) { j = 0; - for (char letter : filename.substr(point + 1, 3)) - extension[j++] = toupper(letter); + for (char letter : filename.substr(point + 1, 3)) { + extension[j++] = static_cast<char>(std::toupper(letter)); + } } } diff --git a/src/common/file_util.h b/src/common/file_util.h index 8b587320f..840cde2a6 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -232,7 +232,7 @@ public: void Swap(IOFile& other) noexcept; - [[nodiscard]] bool Open(const std::string& filename, const char openmode[], int flags = 0); + bool Open(const std::string& filename, const char openmode[], int flags = 0); bool Close(); template <typename T> diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 120f1a5e6..a8d414fb8 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -16,14 +16,14 @@ namespace Common { [[nodiscard]] constexpr u8 ToHexNibble(char c) { if (c >= 65 && c <= 70) { - return c - 55; + return static_cast<u8>(c - 55); } if (c >= 97 && c <= 102) { - return c - 87; + return static_cast<u8>(c - 87); } - return c - 48; + return static_cast<u8>(c - 48); } [[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); @@ -33,11 +33,11 @@ template <std::size_t Size, bool le = false> std::array<u8, Size> out{}; if constexpr (le) { for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) { - out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); } } else { for (std::size_t i = 0; i < 2 * Size; i += 2) { - out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); } } return out; diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h new file mode 100644 index 000000000..c0bbcd457 --- /dev/null +++ b/src/common/intrusive_red_black_tree.h @@ -0,0 +1,602 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/parent_of_member.h" +#include "common/tree.h" + +namespace Common { + +namespace impl { + +class IntrusiveRedBlackTreeImpl; + +} + +struct IntrusiveRedBlackTreeNode { +public: + using EntryType = RBEntry<IntrusiveRedBlackTreeNode>; + + constexpr IntrusiveRedBlackTreeNode() = default; + + void SetEntry(const EntryType& new_entry) { + entry = new_entry; + } + + [[nodiscard]] EntryType& GetEntry() { + return entry; + } + + [[nodiscard]] const EntryType& GetEntry() const { + return entry; + } + +private: + EntryType entry{}; + + friend class impl::IntrusiveRedBlackTreeImpl; + + template <class, class, class> + friend class IntrusiveRedBlackTree; +}; + +template <class T, class Traits, class Comparator> +class IntrusiveRedBlackTree; + +namespace impl { + +class IntrusiveRedBlackTreeImpl { +private: + template <class, class, class> + friend class ::Common::IntrusiveRedBlackTree; + + using RootType = RBHead<IntrusiveRedBlackTreeNode>; + RootType root; + +public: + template <bool Const> + class Iterator; + + using value_type = IntrusiveRedBlackTreeNode; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + + template <bool Const> + class Iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveRedBlackTreeImpl::value_type; + using difference_type = typename IntrusiveRedBlackTreeImpl::difference_type; + using pointer = std::conditional_t<Const, IntrusiveRedBlackTreeImpl::const_pointer, + IntrusiveRedBlackTreeImpl::pointer>; + using reference = std::conditional_t<Const, IntrusiveRedBlackTreeImpl::const_reference, + IntrusiveRedBlackTreeImpl::reference>; + + private: + pointer node; + + public: + explicit Iterator(pointer n) : node(n) {} + + bool operator==(const Iterator& rhs) const { + return this->node == rhs.node; + } + + bool operator!=(const Iterator& rhs) const { + return !(*this == rhs); + } + + pointer operator->() const { + return this->node; + } + + reference operator*() const { + return *this->node; + } + + Iterator& operator++() { + this->node = GetNext(this->node); + return *this; + } + + Iterator& operator--() { + this->node = GetPrev(this->node); + return *this; + } + + Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + operator Iterator<true>() const { + return Iterator<true>(this->node); + } + }; + +private: + // Define accessors using RB_* functions. + bool EmptyImpl() const { + return root.IsEmpty(); + } + + IntrusiveRedBlackTreeNode* GetMinImpl() const { + return RB_MIN(const_cast<RootType*>(&root)); + } + + IntrusiveRedBlackTreeNode* GetMaxImpl() const { + return RB_MAX(const_cast<RootType*>(&root)); + } + + IntrusiveRedBlackTreeNode* RemoveImpl(IntrusiveRedBlackTreeNode* node) { + return RB_REMOVE(&root, node); + } + +public: + static IntrusiveRedBlackTreeNode* GetNext(IntrusiveRedBlackTreeNode* node) { + return RB_NEXT(node); + } + + static IntrusiveRedBlackTreeNode* GetPrev(IntrusiveRedBlackTreeNode* node) { + return RB_PREV(node); + } + + static const IntrusiveRedBlackTreeNode* GetNext(const IntrusiveRedBlackTreeNode* node) { + return static_cast<const IntrusiveRedBlackTreeNode*>( + GetNext(const_cast<IntrusiveRedBlackTreeNode*>(node))); + } + + static const IntrusiveRedBlackTreeNode* GetPrev(const IntrusiveRedBlackTreeNode* node) { + return static_cast<const IntrusiveRedBlackTreeNode*>( + GetPrev(const_cast<IntrusiveRedBlackTreeNode*>(node))); + } + +public: + constexpr IntrusiveRedBlackTreeImpl() {} + + // Iterator accessors. + iterator begin() { + return iterator(this->GetMinImpl()); + } + + const_iterator begin() const { + return const_iterator(this->GetMinImpl()); + } + + iterator end() { + return iterator(static_cast<IntrusiveRedBlackTreeNode*>(nullptr)); + } + + const_iterator end() const { + return const_iterator(static_cast<const IntrusiveRedBlackTreeNode*>(nullptr)); + } + + const_iterator cbegin() const { + return this->begin(); + } + + const_iterator cend() const { + return this->end(); + } + + iterator iterator_to(reference ref) { + return iterator(&ref); + } + + const_iterator iterator_to(const_reference ref) const { + return const_iterator(&ref); + } + + // Content management. + bool empty() const { + return this->EmptyImpl(); + } + + reference back() { + return *this->GetMaxImpl(); + } + + const_reference back() const { + return *this->GetMaxImpl(); + } + + reference front() { + return *this->GetMinImpl(); + } + + const_reference front() const { + return *this->GetMinImpl(); + } + + iterator erase(iterator it) { + auto cur = std::addressof(*it); + auto next = GetNext(cur); + this->RemoveImpl(cur); + return iterator(next); + } +}; + +} // namespace impl + +template <typename T> +concept HasLightCompareType = requires { + { std::is_same<typename T::LightCompareType, void>::value } + ->std::convertible_to<bool>; +}; + +namespace impl { + +template <typename T, typename Default> +consteval auto* GetLightCompareType() { + if constexpr (HasLightCompareType<T>) { + return static_cast<typename T::LightCompareType*>(nullptr); + } else { + return static_cast<Default*>(nullptr); + } +} + +} // namespace impl + +template <typename T, typename Default> +using LightCompareType = std::remove_pointer_t<decltype(impl::GetLightCompareType<T, Default>())>; + +template <class T, class Traits, class Comparator> +class IntrusiveRedBlackTree { + +public: + using ImplType = impl::IntrusiveRedBlackTreeImpl; + +private: + ImplType impl{}; + +public: + template <bool Const> + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + + using light_value_type = LightCompareType<Comparator, value_type>; + using const_light_pointer = const light_value_type*; + using const_light_reference = const light_value_type&; + + template <bool Const> + class Iterator { + public: + friend class IntrusiveRedBlackTree<T, Traits, Comparator>; + + using ImplIterator = + std::conditional_t<Const, ImplType::const_iterator, ImplType::iterator>; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveRedBlackTree::value_type; + using difference_type = typename IntrusiveRedBlackTree::difference_type; + using pointer = std::conditional_t<Const, IntrusiveRedBlackTree::const_pointer, + IntrusiveRedBlackTree::pointer>; + using reference = std::conditional_t<Const, IntrusiveRedBlackTree::const_reference, + IntrusiveRedBlackTree::reference>; + + private: + ImplIterator iterator; + + private: + explicit Iterator(ImplIterator it) : iterator(it) {} + + explicit Iterator(typename std::conditional<Const, ImplType::const_iterator, + ImplType::iterator>::type::pointer ptr) + : iterator(ptr) {} + + ImplIterator GetImplIterator() const { + return this->iterator; + } + + public: + bool operator==(const Iterator& rhs) const { + return this->iterator == rhs.iterator; + } + + bool operator!=(const Iterator& rhs) const { + return !(*this == rhs); + } + + pointer operator->() const { + return Traits::GetParent(std::addressof(*this->iterator)); + } + + reference operator*() const { + return *Traits::GetParent(std::addressof(*this->iterator)); + } + + Iterator& operator++() { + ++this->iterator; + return *this; + } + + Iterator& operator--() { + --this->iterator; + return *this; + } + + Iterator operator++(int) { + const Iterator it{*this}; + ++this->iterator; + return it; + } + + Iterator operator--(int) { + const Iterator it{*this}; + --this->iterator; + return it; + } + + operator Iterator<true>() const { + return Iterator<true>(this->iterator); + } + }; + +private: + static int CompareImpl(const IntrusiveRedBlackTreeNode* lhs, + const IntrusiveRedBlackTreeNode* rhs) { + return Comparator::Compare(*Traits::GetParent(lhs), *Traits::GetParent(rhs)); + } + + static int LightCompareImpl(const void* elm, const IntrusiveRedBlackTreeNode* rhs) { + return Comparator::Compare(*static_cast<const_light_pointer>(elm), *Traits::GetParent(rhs)); + } + + // Define accessors using RB_* functions. + IntrusiveRedBlackTreeNode* InsertImpl(IntrusiveRedBlackTreeNode* node) { + return RB_INSERT(&impl.root, node, CompareImpl); + } + + IntrusiveRedBlackTreeNode* FindImpl(const IntrusiveRedBlackTreeNode* node) const { + return RB_FIND(const_cast<ImplType::RootType*>(&impl.root), + const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl); + } + + IntrusiveRedBlackTreeNode* NFindImpl(const IntrusiveRedBlackTreeNode* node) const { + return RB_NFIND(const_cast<ImplType::RootType*>(&impl.root), + const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl); + } + + IntrusiveRedBlackTreeNode* FindLightImpl(const_light_pointer lelm) const { + return RB_FIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root), + static_cast<const void*>(lelm), LightCompareImpl); + } + + IntrusiveRedBlackTreeNode* NFindLightImpl(const_light_pointer lelm) const { + return RB_NFIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root), + static_cast<const void*>(lelm), LightCompareImpl); + } + +public: + constexpr IntrusiveRedBlackTree() = default; + + // Iterator accessors. + iterator begin() { + return iterator(this->impl.begin()); + } + + const_iterator begin() const { + return const_iterator(this->impl.begin()); + } + + iterator end() { + return iterator(this->impl.end()); + } + + const_iterator end() const { + return const_iterator(this->impl.end()); + } + + const_iterator cbegin() const { + return this->begin(); + } + + const_iterator cend() const { + return this->end(); + } + + iterator iterator_to(reference ref) { + return iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref)))); + } + + const_iterator iterator_to(const_reference ref) const { + return const_iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref)))); + } + + // Content management. + bool empty() const { + return this->impl.empty(); + } + + reference back() { + return *Traits::GetParent(std::addressof(this->impl.back())); + } + + const_reference back() const { + return *Traits::GetParent(std::addressof(this->impl.back())); + } + + reference front() { + return *Traits::GetParent(std::addressof(this->impl.front())); + } + + const_reference front() const { + return *Traits::GetParent(std::addressof(this->impl.front())); + } + + iterator erase(iterator it) { + return iterator(this->impl.erase(it.GetImplIterator())); + } + + iterator insert(reference ref) { + ImplType::pointer node = Traits::GetNode(std::addressof(ref)); + this->InsertImpl(node); + return iterator(node); + } + + iterator find(const_reference ref) const { + return iterator(this->FindImpl(Traits::GetNode(std::addressof(ref)))); + } + + iterator nfind(const_reference ref) const { + return iterator(this->NFindImpl(Traits::GetNode(std::addressof(ref)))); + } + + iterator find_light(const_light_reference ref) const { + return iterator(this->FindLightImpl(std::addressof(ref))); + } + + iterator nfind_light(const_light_reference ref) const { + return iterator(this->NFindLightImpl(std::addressof(ref))); + } +}; + +template <auto T, class Derived = impl::GetParentType<T>> +class IntrusiveRedBlackTreeMemberTraits; + +template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived> +class IntrusiveRedBlackTreeMemberTraits<Member, Derived> { +public: + template <class Comparator> + using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraits, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + +private: + template <class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) { + return std::addressof(parent->*Member); + } + + static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) { + return std::addressof(parent->*Member); + } + + static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) { + return GetParentPointer<Member, Derived>(node); + } + + static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) { + return GetParentPointer<Member, Derived>(node); + } + +private: + static constexpr TypedStorage<Derived> DerivedStorage = {}; + static_assert(GetParent(GetNode(GetPointer(DerivedStorage))) == GetPointer(DerivedStorage)); +}; + +template <auto T, class Derived = impl::GetParentType<T>> +class IntrusiveRedBlackTreeMemberTraitsDeferredAssert; + +template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived> +class IntrusiveRedBlackTreeMemberTraitsDeferredAssert<Member, Derived> { +public: + template <class Comparator> + using TreeType = + IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraitsDeferredAssert, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + + static constexpr bool IsValid() { + TypedStorage<Derived> DerivedStorage = {}; + return GetParent(GetNode(GetPointer(DerivedStorage))) == GetPointer(DerivedStorage); + } + +private: + template <class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) { + return std::addressof(parent->*Member); + } + + static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) { + return std::addressof(parent->*Member); + } + + static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) { + return GetParentPointer<Member, Derived>(node); + } + + static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) { + return GetParentPointer<Member, Derived>(node); + } +}; + +template <class Derived> +class IntrusiveRedBlackTreeBaseNode : public IntrusiveRedBlackTreeNode { +public: + constexpr Derived* GetPrev() { + return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this)); + } + constexpr const Derived* GetPrev() const { + return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this)); + } + + constexpr Derived* GetNext() { + return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this)); + } + constexpr const Derived* GetNext() const { + return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this)); + } +}; + +template <class Derived> +class IntrusiveRedBlackTreeBaseTraits { +public: + template <class Comparator> + using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeBaseTraits, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + +private: + template <class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) { + return static_cast<IntrusiveRedBlackTreeNode*>(parent); + } + + static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) { + return static_cast<const IntrusiveRedBlackTreeNode*>(parent); + } + + static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) { + return static_cast<Derived*>(node); + } + + static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) { + return static_cast<const Derived*>(node); + } +}; + +} // namespace Common diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 62cfde397..2d4d2e9e7 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -23,6 +23,7 @@ #include "common/logging/text_formatter.h" #include "common/string_util.h" #include "common/threadsafe_queue.h" +#include "core/settings.h" namespace Log { @@ -144,18 +145,35 @@ void ColorConsoleBackend::Write(const Entry& entry) { PrintColoredMessage(entry); } -// _SH_DENYWR allows read only access to the file for other programs. -// It is #defined to 0 on other platforms -FileBackend::FileBackend(const std::string& filename) - : file(filename, "w", _SH_DENYWR), bytes_written(0) {} +FileBackend::FileBackend(const std::string& filename) : bytes_written(0) { + if (Common::FS::Exists(filename + ".old.txt")) { + Common::FS::Delete(filename + ".old.txt"); + } + if (Common::FS::Exists(filename)) { + Common::FS::Rename(filename, filename + ".old.txt"); + } + + // _SH_DENYWR allows read only access to the file for other programs. + // It is #defined to 0 on other platforms + file = Common::FS::IOFile(filename, "w", _SH_DENYWR); +} void FileBackend::Write(const Entry& entry) { // prevent logs from going over the maximum size (in case its spamming and the user doesn't // know) - constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; - if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { + constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; + constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; + + if (!file.IsOpen()) { return; } + + if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) { + return; + } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) { + return; + } + bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); if (entry.log_level >= Level::Error) { file.Flush(); @@ -222,6 +240,7 @@ void DebuggerBackend::Write(const Entry& entry) { SUB(Service, NPNS) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ + SUB(Service, OLSC) \ SUB(Service, PCIE) \ SUB(Service, PCTL) \ SUB(Service, PCV) \ @@ -274,7 +293,6 @@ const char* GetLogClassName(Class log_class) { case Class::Count: break; } - UNREACHABLE(); return "Invalid"; } @@ -293,7 +311,6 @@ const char* GetLevelName(Level log_level) { break; } #undef LVL - UNREACHABLE(); return "Invalid"; } diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 13a4f1e30..835894918 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -95,6 +95,7 @@ enum class Class : ClassType { Service_NPNS, ///< The NPNS service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service + Service_OLSC, ///< The OLSC service Service_PCIE, ///< The PCIe service Service_PCTL, ///< The PCTL (Parental control) service Service_PCV, ///< The PCV service diff --git a/src/common/math_util.h b/src/common/math_util.h index cc35c90ee..4c38d8040 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -9,7 +9,7 @@ namespace Common { -constexpr float PI = 3.14159265f; +constexpr float PI = 3.1415926535f; template <class T> struct Rectangle { @@ -20,14 +20,14 @@ struct Rectangle { constexpr Rectangle() = default; - constexpr Rectangle(T left, T top, T right, T bottom) - : left(left), top(top), right(right), bottom(bottom) {} + constexpr Rectangle(T left_, T top_, T right_, T bottom_) + : left(left_), top(top_), right(right_), bottom(bottom_) {} [[nodiscard]] T GetWidth() const { if constexpr (std::is_floating_point_v<T>) { return std::abs(right - left); } else { - return std::abs(static_cast<std::make_signed_t<T>>(right - left)); + return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(right - left))); } } @@ -35,7 +35,7 @@ struct Rectangle { if constexpr (std::is_floating_point_v<T>) { return std::abs(bottom - top); } else { - return std::abs(static_cast<std::make_signed_t<T>>(bottom - top)); + return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(bottom - top))); } } diff --git a/src/common/memory_hook.cpp b/src/common/memory_hook.cpp deleted file mode 100644 index 3986986d6..000000000 --- a/src/common/memory_hook.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/memory_hook.h" - -namespace Common { - -MemoryHook::~MemoryHook() = default; - -} // namespace Common diff --git a/src/common/memory_hook.h b/src/common/memory_hook.h deleted file mode 100644 index adaa4c2c5..000000000 --- a/src/common/memory_hook.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <optional> - -#include "common/common_types.h" - -namespace Common { - -/** - * Memory hooks have two purposes: - * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement - * texture forwarding and memory breakpoints for debugging. - * 2. To allow for the implementation of MMIO devices. - * - * A hook may be mapped to multiple regions of memory. - * - * If a std::nullopt or false is returned from a function, the read/write request is passed through - * to the underlying memory region. - */ -class MemoryHook { -public: - virtual ~MemoryHook(); - - virtual std::optional<bool> IsValidAddress(VAddr addr) = 0; - - virtual std::optional<u8> Read8(VAddr addr) = 0; - virtual std::optional<u16> Read16(VAddr addr) = 0; - virtual std::optional<u32> Read32(VAddr addr) = 0; - virtual std::optional<u64> Read64(VAddr addr) = 0; - - virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0; - - virtual bool Write8(VAddr addr, u8 data) = 0; - virtual bool Write16(VAddr addr, u16 data) = 0; - virtual bool Write32(VAddr addr, u32 data) = 0; - virtual bool Write64(VAddr addr, u64 data) = 0; - - virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0; -}; - -using MemoryHookPointer = std::shared_ptr<MemoryHook>; -} // namespace Common diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 68cb86cd1..1d5393597 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -16,16 +16,23 @@ // Call directly after the command or use the error num. // This function might change the error code. std::string GetLastErrorMsg() { - static const std::size_t buff_size = 255; + static constexpr std::size_t buff_size = 255; char err_str[buff_size]; #ifdef _WIN32 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); + return std::string(err_str, buff_size); +#elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) + // Thread safe (GNU-specific) + const char* str = strerror_r(errno, err_str, buff_size); + return std::string(str); #else // Thread safe (XSI-compliant) - strerror_r(errno, err_str, buff_size); + const int success = strerror_r(errno, err_str, buff_size); + if (success != 0) { + return {}; + } + return std::string(err_str); #endif - - return std::string(err_str, buff_size); } diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h deleted file mode 100644 index 4b305bf40..000000000 --- a/src/common/multi_level_queue.h +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2019 TuxSH -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <iterator> -#include <list> -#include <utility> - -#include "common/bit_util.h" -#include "common/common_types.h" - -namespace Common { - -/** - * A MultiLevelQueue is a type of priority queue which has the following characteristics: - * - iteratable through each of its elements. - * - back can be obtained. - * - O(1) add, lookup (both front and back) - * - discrete priorities and a max of 64 priorities (limited domain) - * This type of priority queue is normaly used for managing threads within an scheduler - */ -template <typename T, std::size_t Depth> -class MultiLevelQueue { -public: - using value_type = T; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - using difference_type = typename std::pointer_traits<pointer>::difference_type; - using size_type = std::size_t; - - template <bool is_constant> - class iterator_impl { - public: - using iterator_category = std::bidirectional_iterator_tag; - using value_type = T; - using pointer = std::conditional_t<is_constant, T*, const T*>; - using reference = std::conditional_t<is_constant, const T&, T&>; - using difference_type = typename std::pointer_traits<pointer>::difference_type; - - friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) { - if (lhs.IsEnd() && rhs.IsEnd()) - return true; - return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it); - } - - friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) { - return !operator==(lhs, rhs); - } - - reference operator*() const { - return *it; - } - - pointer operator->() const { - return it.operator->(); - } - - iterator_impl& operator++() { - if (IsEnd()) { - return *this; - } - - ++it; - - if (it == GetEndItForPrio()) { - u64 prios = mlq.used_priorities; - prios &= ~((1ULL << (current_priority + 1)) - 1); - if (prios == 0) { - current_priority = static_cast<u32>(mlq.depth()); - } else { - current_priority = CountTrailingZeroes64(prios); - it = GetBeginItForPrio(); - } - } - return *this; - } - - iterator_impl& operator--() { - if (IsEnd()) { - if (mlq.used_priorities != 0) { - current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities); - it = GetEndItForPrio(); - --it; - } - } else if (it == GetBeginItForPrio()) { - u64 prios = mlq.used_priorities; - prios &= (1ULL << current_priority) - 1; - if (prios != 0) { - current_priority = CountTrailingZeroes64(prios); - it = GetEndItForPrio(); - --it; - } - } else { - --it; - } - return *this; - } - - iterator_impl operator++(int) { - const iterator_impl v{*this}; - ++(*this); - return v; - } - - iterator_impl operator--(int) { - const iterator_impl v{*this}; - --(*this); - return v; - } - - // allow implicit const->non-const - iterator_impl(const iterator_impl<false>& other) - : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {} - - iterator_impl(const iterator_impl<true>& other) - : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {} - - iterator_impl& operator=(const iterator_impl<false>& other) { - mlq = other.mlq; - it = other.it; - current_priority = other.current_priority; - return *this; - } - - friend class iterator_impl<true>; - iterator_impl() = default; - - private: - friend class MultiLevelQueue; - using container_ref = - std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>; - using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator, - typename std::list<T>::iterator>; - - explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority) - : mlq(mlq), it(it), current_priority(current_priority) {} - explicit iterator_impl(container_ref mlq, u32 current_priority) - : mlq(mlq), it(), current_priority(current_priority) {} - - bool IsEnd() const { - return current_priority == mlq.depth(); - } - - list_iterator GetBeginItForPrio() const { - return mlq.levels[current_priority].begin(); - } - - list_iterator GetEndItForPrio() const { - return mlq.levels[current_priority].end(); - } - - container_ref mlq; - list_iterator it; - u32 current_priority; - }; - - using iterator = iterator_impl<false>; - using const_iterator = iterator_impl<true>; - - void add(const T& element, u32 priority, bool send_back = true) { - if (send_back) - levels[priority].push_back(element); - else - levels[priority].push_front(element); - used_priorities |= 1ULL << priority; - } - - void remove(const T& element, u32 priority) { - auto it = ListIterateTo(levels[priority], element); - if (it == levels[priority].end()) - return; - levels[priority].erase(it); - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) { - remove(element, old_priority); - add(element, new_priority, !adjust_front); - } - void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) { - adjust(*it, old_priority, new_priority, adjust_front); - } - - void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) { - ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority], - ListIterateTo(levels[priority], element)); - - other.used_priorities |= 1ULL << priority; - - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) { - transfer_to_front(*it, priority, other); - } - - void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) { - ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority], - ListIterateTo(levels[priority], element)); - - other.used_priorities |= 1ULL << priority; - - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) { - transfer_to_back(*it, priority, other); - } - - void yield(u32 priority, std::size_t n = 1) { - ListShiftForward(levels[priority], n); - } - - [[nodiscard]] std::size_t depth() const { - return Depth; - } - - [[nodiscard]] std::size_t size(u32 priority) const { - return levels[priority].size(); - } - - [[nodiscard]] std::size_t size() const { - u64 priorities = used_priorities; - std::size_t size = 0; - while (priorities != 0) { - const u64 current_priority = CountTrailingZeroes64(priorities); - size += levels[current_priority].size(); - priorities &= ~(1ULL << current_priority); - } - return size; - } - - [[nodiscard]] bool empty() const { - return used_priorities == 0; - } - - [[nodiscard]] bool empty(u32 priority) const { - return (used_priorities & (1ULL << priority)) == 0; - } - - [[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const { - const u64 priorities = - max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1)); - return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities)); - } - - [[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const { - const u64 priorities = min_priority >= Depth - 1 - ? used_priorities - : (used_priorities & ((1ULL << (min_priority + 1)) - 1)); - return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities); - } - - [[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const { - const u32 priority = highest_priority_set(max_prio); - return priority == Depth ? cend() - : const_iterator{*this, levels[priority].cbegin(), priority}; - } - [[nodiscard]] const_iterator begin(u32 max_prio = 0) const { - return cbegin(max_prio); - } - [[nodiscard]] iterator begin(u32 max_prio = 0) { - const u32 priority = highest_priority_set(max_prio); - return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority}; - } - - [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const { - return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1); - } - [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const { - return cend(min_prio); - } - [[nodiscard]] iterator end(u32 min_prio = Depth - 1) { - return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1); - } - - [[nodiscard]] T& front(u32 max_priority = 0) { - const u32 priority = highest_priority_set(max_priority); - return levels[priority == Depth ? 0 : priority].front(); - } - [[nodiscard]] const T& front(u32 max_priority = 0) const { - const u32 priority = highest_priority_set(max_priority); - return levels[priority == Depth ? 0 : priority].front(); - } - - [[nodiscard]] T& back(u32 min_priority = Depth - 1) { - const u32 priority = lowest_priority_set(min_priority); // intended - return levels[priority == Depth ? 63 : priority].back(); - } - [[nodiscard]] const T& back(u32 min_priority = Depth - 1) const { - const u32 priority = lowest_priority_set(min_priority); // intended - return levels[priority == Depth ? 63 : priority].back(); - } - - void clear() { - used_priorities = 0; - for (std::size_t i = 0; i < Depth; i++) { - levels[i].clear(); - } - } - -private: - using const_list_iterator = typename std::list<T>::const_iterator; - - static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) { - if (shift >= list.size()) { - return; - } - - const auto begin_range = list.begin(); - const auto end_range = std::next(begin_range, shift); - list.splice(list.end(), list, begin_range, end_range); - } - - static void ListSplice(std::list<T>& in_list, const_list_iterator position, - std::list<T>& out_list, const_list_iterator element) { - in_list.splice(position, out_list, element); - } - - [[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list, - const T& element) { - auto it = list.cbegin(); - while (it != list.cend() && *it != element) { - ++it; - } - return it; - } - - std::array<std::list<T>, Depth> levels; - u64 used_priorities = 0; -}; - -} // namespace Common diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp new file mode 100644 index 000000000..d537517db --- /dev/null +++ b/src/common/nvidia_flags.cpp @@ -0,0 +1,27 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <filesystem> +#include <stdlib.h> + +#include <fmt/format.h> + +#include "common/file_util.h" +#include "common/nvidia_flags.h" + +namespace Common { + +void ConfigureNvidiaEnvironmentFlags() { +#ifdef _WIN32 + const std::string shader_path = Common::FS::SanitizePath( + fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir))); + const std::string windows_path = + Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash); + void(Common::FS::CreateFullPath(shader_path + '/')); + void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str())); + void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); +#endif +} + +} // namespace Common diff --git a/src/common/nvidia_flags.h b/src/common/nvidia_flags.h new file mode 100644 index 000000000..75a0233ac --- /dev/null +++ b/src/common/nvidia_flags.h @@ -0,0 +1,10 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace Common { + +/// Configure platform specific flags for Nvidia's driver +void ConfigureNvidiaEnvironmentFlags(); + +} // namespace Common diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp index e5d3090d5..8fd8620fd 100644 --- a/src/common/page_table.cpp +++ b/src/common/page_table.cpp @@ -8,18 +8,12 @@ namespace Common { PageTable::PageTable() = default; -PageTable::~PageTable() = default; +PageTable::~PageTable() noexcept = default; -void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, - bool has_attribute) { - const std::size_t num_page_table_entries{1ULL - << (address_space_width_in_bits - page_size_in_bits)}; +void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_bits) { + const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)}; pointers.resize(num_page_table_entries); backing_addr.resize(num_page_table_entries); - - if (has_attribute) { - attributes.resize(num_page_table_entries); - } } } // namespace Common diff --git a/src/common/page_table.h b/src/common/page_table.h index cf5eed780..61c5552e0 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -4,12 +4,10 @@ #pragma once -#include <vector> - -#include <boost/icl/interval_map.hpp> +#include <atomic> +#include <tuple> #include "common/common_types.h" -#include "common/memory_hook.h" #include "common/virtual_buffer.h" namespace Common { @@ -22,27 +20,6 @@ enum class PageType : u8 { /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and /// invalidation RasterizerCachedMemory, - /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. - Special, - /// Page is allocated for use. - Allocated, -}; - -struct SpecialRegion { - enum class Type { - DebugHook, - IODevice, - } type; - - MemoryHookPointer handler; - - [[nodiscard]] bool operator<(const SpecialRegion& other) const { - return std::tie(type, handler) < std::tie(other.type, other.handler); - } - - [[nodiscard]] bool operator==(const SpecialRegion& other) const { - return std::tie(type, handler) == std::tie(other.type, other.handler); - } }; /** @@ -50,27 +27,84 @@ struct SpecialRegion { * mimics the way a real CPU page table works. */ struct PageTable { + /// Number of bits reserved for attribute tagging. + /// This can be at most the guaranteed alignment of the pointers in the page table. + static constexpr int ATTRIBUTE_BITS = 2; + + /** + * Pair of host pointer and page type attribute. + * This uses the lower bits of a given pointer to store the attribute tag. + * Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method + * call. In other words, they are guaranteed to be synchronized at all times. + */ + class PageInfo { + public: + /// Returns the page pointer + [[nodiscard]] u8* Pointer() const noexcept { + return ExtractPointer(raw.load(std::memory_order_relaxed)); + } + + /// Returns the page type attribute + [[nodiscard]] PageType Type() const noexcept { + return ExtractType(raw.load(std::memory_order_relaxed)); + } + + /// Returns the page pointer and attribute pair, extracted from the same atomic read + [[nodiscard]] std::pair<u8*, PageType> PointerType() const noexcept { + const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed); + return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)}; + } + + /// Returns the raw representation of the page information. + /// Use ExtractPointer and ExtractType to unpack the value. + [[nodiscard]] uintptr_t Raw() const noexcept { + return raw.load(std::memory_order_relaxed); + } + + /// Write a page pointer and type pair atomically + void Store(u8* pointer, PageType type) noexcept { + raw.store(reinterpret_cast<uintptr_t>(pointer) | static_cast<uintptr_t>(type)); + } + + /// Unpack a pointer from a page info raw representation + [[nodiscard]] static u8* ExtractPointer(uintptr_t raw) noexcept { + return reinterpret_cast<u8*>(raw & (~uintptr_t{0} << ATTRIBUTE_BITS)); + } + + /// Unpack a page type from a page info raw representation + [[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept { + return static_cast<PageType>(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1)); + } + + private: + std::atomic<uintptr_t> raw; + }; + PageTable(); - ~PageTable(); + ~PageTable() noexcept; + + PageTable(const PageTable&) = delete; + PageTable& operator=(const PageTable&) = delete; + + PageTable(PageTable&&) noexcept = default; + PageTable& operator=(PageTable&&) noexcept = default; /** - * Resizes the page table to be able to accomodate enough pages within + * Resizes the page table to be able to accommodate enough pages within * a given address space. * * @param address_space_width_in_bits The address size width in bits. + * @param page_size_in_bits The page size in bits. */ - void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, - bool has_attribute); + void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits); /** * Vector of memory pointers backing each page. An entry can only be non-null if the - * corresponding entry in the `attributes` vector is of type `Memory`. + * corresponding attribute element is of type `Memory`. */ - VirtualBuffer<u8*> pointers; + VirtualBuffer<PageInfo> pointers; VirtualBuffer<u64> backing_addr; - - VirtualBuffer<PageType> attributes; }; } // namespace Common diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h new file mode 100644 index 000000000..d9a14529d --- /dev/null +++ b/src/common/parent_of_member.h @@ -0,0 +1,191 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> + +#include "common/assert.h" +#include "common/common_types.h" + +namespace Common { +namespace detail { +template <typename T, size_t Size, size_t Align> +struct TypedStorageImpl { + std::aligned_storage_t<Size, Align> storage_; +}; +} // namespace detail + +template <typename T> +using TypedStorage = detail::TypedStorageImpl<T, sizeof(T), alignof(T)>; + +template <typename T> +static constexpr T* GetPointer(TypedStorage<T>& ts) { + return static_cast<T*>(static_cast<void*>(std::addressof(ts.storage_))); +} + +template <typename T> +static constexpr const T* GetPointer(const TypedStorage<T>& ts) { + return static_cast<const T*>(static_cast<const void*>(std::addressof(ts.storage_))); +} + +namespace impl { + +template <size_t MaxDepth> +struct OffsetOfUnionHolder { + template <typename ParentType, typename MemberType, size_t Offset> + union UnionImpl { + using PaddingMember = char; + static constexpr size_t GetOffset() { + return Offset; + } + +#pragma pack(push, 1) + struct { + PaddingMember padding[Offset]; + MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1]; + } data; +#pragma pack(pop) + UnionImpl<ParentType, MemberType, Offset + 1> next_union; + }; + + template <typename ParentType, typename MemberType> + union UnionImpl<ParentType, MemberType, 0> { + static constexpr size_t GetOffset() { + return 0; + } + + struct { + MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1]; + } data; + UnionImpl<ParentType, MemberType, 1> next_union; + }; + + template <typename ParentType, typename MemberType> + union UnionImpl<ParentType, MemberType, MaxDepth> {}; +}; + +template <typename ParentType, typename MemberType> +struct OffsetOfCalculator { + using UnionHolder = + typename OffsetOfUnionHolder<sizeof(MemberType)>::template UnionImpl<ParentType, MemberType, + 0>; + union Union { + char c{}; + UnionHolder first_union; + TypedStorage<ParentType> parent; + + constexpr Union() : c() {} + }; + static constexpr Union U = {}; + + static constexpr const MemberType* GetNextAddress(const MemberType* start, + const MemberType* target) { + while (start < target) { + start++; + } + return start; + } + + static constexpr std::ptrdiff_t GetDifference(const MemberType* start, + const MemberType* target) { + return (target - start) * sizeof(MemberType); + } + + template <typename CurUnion> + static constexpr std::ptrdiff_t OffsetOfImpl(MemberType ParentType::*member, + CurUnion& cur_union) { + constexpr size_t Offset = CurUnion::GetOffset(); + const auto target = std::addressof(GetPointer(U.parent)->*member); + const auto start = std::addressof(cur_union.data.members[0]); + const auto next = GetNextAddress(start, target); + + if (next != target) { + if constexpr (Offset < sizeof(MemberType) - 1) { + return OffsetOfImpl(member, cur_union.next_union); + } else { + UNREACHABLE(); + } + } + + return (next - start) * sizeof(MemberType) + Offset; + } + + static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) { + return OffsetOfImpl(member, U.first_union); + } +}; + +template <typename T> +struct GetMemberPointerTraits; + +template <typename P, typename M> +struct GetMemberPointerTraits<M P::*> { + using Parent = P; + using Member = M; +}; + +template <auto MemberPtr> +using GetParentType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Parent; + +template <auto MemberPtr> +using GetMemberType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Member; + +template <auto MemberPtr, typename RealParentType = GetParentType<MemberPtr>> +static inline std::ptrdiff_t OffsetOf = [] { + using DeducedParentType = GetParentType<MemberPtr>; + using MemberType = GetMemberType<MemberPtr>; + static_assert(std::is_base_of<DeducedParentType, RealParentType>::value || + std::is_same<RealParentType, DeducedParentType>::value); + + return OffsetOfCalculator<RealParentType, MemberType>::OffsetOf(MemberPtr); +}(); + +} // namespace impl + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>* member) { + std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>; + return *static_cast<RealParentType*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(member)) - Offset)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const* member) { + std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>; + return *static_cast<const RealParentType*>(static_cast<const void*>( + static_cast<const uint8_t*>(static_cast<const void*>(member)) - Offset)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>* member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const* member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>& member) { + return GetParentReference<MemberPtr, RealParentType>(std::addressof(member)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const& member) { + return GetParentReference<MemberPtr, RealParentType>(std::addressof(member)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>& member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); +} + +template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> +constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const& member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); +} + +} // namespace Common diff --git a/src/common/quaternion.h b/src/common/quaternion.h index da44f35cd..4d0871eb4 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -36,6 +36,36 @@ public: T length = std::sqrt(xyz.Length2() + w * w); return {xyz / length, w / length}; } + + [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const { + const T x2 = xyz[0] * xyz[0]; + const T y2 = xyz[1] * xyz[1]; + const T z2 = xyz[2] * xyz[2]; + + const T xy = xyz[0] * xyz[1]; + const T wz = w * xyz[2]; + const T xz = xyz[0] * xyz[2]; + const T wy = w * xyz[1]; + const T yz = xyz[1] * xyz[2]; + const T wx = w * xyz[0]; + + return {1.0f - 2.0f * (y2 + z2), + 2.0f * (xy + wz), + 2.0f * (xz - wy), + 0.0f, + 2.0f * (xy - wz), + 1.0f - 2.0f * (x2 + z2), + 2.0f * (yz + wx), + 0.0f, + 2.0f * (xz + wy), + 2.0f * (yz - wx), + 1.0f - 2.0f * (x2 + y2), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f}; + } }; template <typename T> diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h index 138fa0131..4a8d09806 100644 --- a/src/common/ring_buffer.h +++ b/src/common/ring_buffer.h @@ -19,15 +19,14 @@ namespace Common { /// SPSC ring buffer /// @tparam T Element type /// @tparam capacity Number of slots in ring buffer -/// @tparam granularity Slot size in terms of number of elements -template <typename T, std::size_t capacity, std::size_t granularity = 1> +template <typename T, std::size_t capacity> class RingBuffer { - /// A "slot" is made of `granularity` elements of `T`. - static constexpr std::size_t slot_size = granularity * sizeof(T); + /// A "slot" is made of a single `T`. + static constexpr std::size_t slot_size = sizeof(T); // T must be safely memcpy-able and have a trivial default constructor. static_assert(std::is_trivial_v<T>); // Ensure capacity is sensible. - static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2 / granularity); + static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2); static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two"); // Ensure lock-free. static_assert(std::atomic_size_t::is_always_lock_free); @@ -47,7 +46,7 @@ public: const std::size_t second_copy = push_count - first_copy; const char* in = static_cast<const char*>(new_slots); - std::memcpy(m_data.data() + pos * granularity, in, first_copy * slot_size); + std::memcpy(m_data.data() + pos, in, first_copy * slot_size); in += first_copy * slot_size; std::memcpy(m_data.data(), in, second_copy * slot_size); @@ -74,7 +73,7 @@ public: const std::size_t second_copy = pop_count - first_copy; char* out = static_cast<char*>(output); - std::memcpy(out, m_data.data() + pos * granularity, first_copy * slot_size); + std::memcpy(out, m_data.data() + pos, first_copy * slot_size); out += first_copy * slot_size; std::memcpy(out, m_data.data(), second_copy * slot_size); @@ -84,9 +83,9 @@ public: } std::vector<T> Pop(std::size_t max_slots = ~std::size_t(0)) { - std::vector<T> out(std::min(max_slots, capacity) * granularity); - const std::size_t count = Pop(out.data(), out.size() / granularity); - out.resize(count * granularity); + std::vector<T> out(std::min(max_slots, capacity)); + const std::size_t count = Pop(out.data(), out.size()); + out.resize(count); return out; } @@ -113,7 +112,7 @@ private: alignas(128) std::atomic_size_t m_write_index{0}; #endif - std::array<T, granularity * capacity> m_data; + std::array<T, capacity> m_data; }; } // namespace Common diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h index 68ef5f197..35dac3a8f 100644 --- a/src/common/scope_exit.h +++ b/src/common/scope_exit.h @@ -10,7 +10,7 @@ namespace detail { template <typename Func> struct ScopeExitHelper { - explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {} + explicit ScopeExitHelper(Func&& func_) : func(std::move(func_)) {} ~ScopeExitHelper() { if (active) { func(); @@ -49,3 +49,9 @@ ScopeExitHelper<Func> ScopeExit(Func&& func) { * \endcode */ #define SCOPE_EXIT(body) auto CONCAT2(scope_exit_helper_, __LINE__) = detail::ScopeExit([&]() body) + +/** + * This macro is similar to SCOPE_EXIT, except the object is caller managed. This is intended to be + * used when the caller might want to cancel the ScopeExit. + */ +#define SCOPE_GUARD(body) detail::ScopeExit([&]() body) diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h index 4f946a258..06ac2f5bb 100644 --- a/src/common/spin_lock.h +++ b/src/common/spin_lock.h @@ -15,6 +15,14 @@ namespace Common { */ class SpinLock { public: + SpinLock() = default; + + SpinLock(const SpinLock&) = delete; + SpinLock& operator=(const SpinLock&) = delete; + + SpinLock(SpinLock&&) = delete; + SpinLock& operator=(SpinLock&&) = delete; + void lock(); void unlock(); [[nodiscard]] bool try_lock(); diff --git a/src/common/stream.cpp b/src/common/stream.cpp new file mode 100644 index 000000000..bf0496c26 --- /dev/null +++ b/src/common/stream.cpp @@ -0,0 +1,47 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <stdexcept> +#include "common/common_types.h" +#include "common/stream.h" + +namespace Common { + +Stream::Stream() = default; +Stream::~Stream() = default; + +void Stream::Seek(s32 offset, SeekOrigin origin) { + if (origin == SeekOrigin::SetOrigin) { + if (offset < 0) { + position = 0; + } else if (position >= buffer.size()) { + position = buffer.size(); + } else { + position = offset; + } + } else if (origin == SeekOrigin::FromCurrentPos) { + Seek(static_cast<s32>(position) + offset, SeekOrigin::SetOrigin); + } else if (origin == SeekOrigin::FromEnd) { + Seek(static_cast<s32>(buffer.size()) - offset, SeekOrigin::SetOrigin); + } +} + +u8 Stream::ReadByte() { + if (position < buffer.size()) { + return buffer[position++]; + } else { + throw std::out_of_range("Attempting to read a byte not within the buffer range"); + } +} + +void Stream::WriteByte(u8 byte) { + if (position == buffer.size()) { + buffer.push_back(byte); + position++; + } else { + buffer.insert(buffer.begin() + position, byte); + } +} + +} // namespace Common diff --git a/src/common/stream.h b/src/common/stream.h new file mode 100644 index 000000000..0e40692de --- /dev/null +++ b/src/common/stream.h @@ -0,0 +1,56 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" + +namespace Common { + +enum class SeekOrigin { + SetOrigin, + FromCurrentPos, + FromEnd, +}; + +class Stream { +public: + /// Stream creates a bitstream and provides common functionality on the stream. + explicit Stream(); + ~Stream(); + + Stream(const Stream&) = delete; + Stream& operator=(const Stream&) = delete; + + Stream(Stream&&) = default; + Stream& operator=(Stream&&) = default; + + /// Reposition bitstream "cursor" to the specified offset from origin + void Seek(s32 offset, SeekOrigin origin); + + /// Reads next byte in the stream buffer and increments position + u8 ReadByte(); + + /// Writes byte at current position + void WriteByte(u8 byte); + + [[nodiscard]] std::size_t GetPosition() const { + return position; + } + + [[nodiscard]] std::vector<u8>& GetBuffer() { + return buffer; + } + + [[nodiscard]] const std::vector<u8>& GetBuffer() const { + return buffer; + } + +private: + std::vector<u8> buffer; + std::size_t position{0}; +}; + +} // namespace Common diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 84883a1d3..7b614ad89 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -8,6 +8,7 @@ #include <cstdlib> #include <locale> #include <sstream> + #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -21,14 +22,14 @@ namespace Common { /// Make a string lowercase std::string ToLower(std::string str) { std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return std::tolower(c); }); + [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); return str; } /// Make a string uppercase std::string ToUpper(std::string str) { std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return std::toupper(c); }); + [](unsigned char c) { return static_cast<char>(std::toupper(c)); }); return str; } @@ -140,27 +141,13 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st } std::string UTF16ToUTF8(const std::u16string& input) { -#ifdef _MSC_VER - // Workaround for missing char16_t/char32_t instantiations in MSVC2017 - std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; - std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend()); - return convert.to_bytes(tmp_buffer); -#else std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; return convert.to_bytes(input); -#endif } std::u16string UTF8ToUTF16(const std::string& input) { -#ifdef _MSC_VER - // Workaround for missing char16_t/char32_t instantiations in MSVC2017 - std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; - auto tmp_buffer = convert.from_bytes(input); - return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend()); -#else std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; return convert.from_bytes(input); -#endif } #ifdef _WIN32 diff --git a/src/common/swap.h b/src/common/swap.h index 7665942a2..a80e191dc 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -394,7 +394,7 @@ public: template <typename S, typename T2, typename F2> friend S operator%(const S& p, const swapped_t v); - // Arithmetics + assignements + // Arithmetics + assignments template <typename S, typename T2, typename F2> friend S operator+=(const S& p, const swapped_t v); @@ -451,7 +451,7 @@ S operator%(const S& i, const swap_struct_t<T, F> v) { return i % v.swap(); } -// Arithmetics + assignements +// Arithmetics + assignments template <typename S, typename T, typename F> S& operator+=(S& i, const swap_struct_t<T, F> v) { i += v.swap(); diff --git a/src/common/telemetry.h b/src/common/telemetry.h index a50c5d1de..49186e848 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -52,8 +52,8 @@ public: template <typename T> class Field : public FieldInterface { public: - Field(FieldType type, std::string name, T value) - : name(std::move(name)), type(type), value(std::move(value)) {} + Field(FieldType type_, std::string name_, T value_) + : name(std::move(name_)), type(type_), value(std::move(value_)) {} Field(const Field&) = default; Field& operator=(const Field&) = default; diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 8e5935e6a..d2c1ac60d 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/common_funcs.h" +#include "common/logging/log.h" #include "common/thread.h" #ifdef __APPLE__ #include <mach/mach.h> @@ -19,6 +21,8 @@ #include <unistd.h> #endif +#include <string> + #ifdef __FreeBSD__ #define cpu_set_t cpuset_t #endif @@ -110,6 +114,14 @@ void SetCurrentThreadName(const char* name) { pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void*)name); +#elif defined(__linux__) + // Linux limits thread names to 15 characters and will outright reject any + // attempt to set a longer name with ERANGE. + std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15))); + if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) { + errno = e; + LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg()); + } #else pthread_setname_np(pthread_self(), name); #endif diff --git a/src/common/thread.h b/src/common/thread.h index 52b359413..a8c17c71a 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include <atomic> #include <chrono> #include <condition_variable> #include <cstddef> @@ -25,13 +26,13 @@ public: void Wait() { std::unique_lock lk{mutex}; - condvar.wait(lk, [&] { return is_set; }); + condvar.wait(lk, [&] { return is_set.load(); }); is_set = false; } bool WaitFor(const std::chrono::nanoseconds& time) { std::unique_lock lk{mutex}; - if (!condvar.wait_for(lk, time, [this] { return is_set; })) + if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) return false; is_set = false; return true; @@ -40,7 +41,7 @@ public: template <class Clock, class Duration> bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { std::unique_lock lk{mutex}; - if (!condvar.wait_until(lk, time, [this] { return is_set; })) + if (!condvar.wait_until(lk, time, [this] { return is_set.load(); })) return false; is_set = false; return true; @@ -54,9 +55,9 @@ public: } private: - bool is_set = false; std::condition_variable condvar; std::mutex mutex; + std::atomic_bool is_set{false}; }; class Barrier { diff --git a/src/common/thread_worker.cpp b/src/common/thread_worker.cpp new file mode 100644 index 000000000..8f9bf447a --- /dev/null +++ b/src/common/thread_worker.cpp @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/thread.h" +#include "common/thread_worker.h" + +namespace Common { + +ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) { + for (std::size_t i = 0; i < num_workers; ++i) + threads.emplace_back([this, thread_name{std::string{name}}] { + Common::SetCurrentThreadName(thread_name.c_str()); + + // Wait for first request + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + } + + while (true) { + std::function<void()> task; + + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + if (stop || requests.empty()) { + return; + } + task = std::move(requests.front()); + requests.pop(); + } + + task(); + } + }); +} + +ThreadWorker::~ThreadWorker() { + { + std::unique_lock lock{queue_mutex}; + stop = true; + } + condition.notify_all(); + for (std::thread& thread : threads) { + thread.join(); + } +} + +void ThreadWorker::QueueWork(std::function<void()>&& work) { + { + std::unique_lock lock{queue_mutex}; + requests.emplace(work); + } + condition.notify_one(); +} + +} // namespace Common diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h new file mode 100644 index 000000000..f1859971f --- /dev/null +++ b/src/common/thread_worker.h @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <functional> +#include <mutex> +#include <string> +#include <vector> +#include <queue> + +namespace Common { + +class ThreadWorker final { +public: + explicit ThreadWorker(std::size_t num_workers, const std::string& name); + ~ThreadWorker(); + void QueueWork(std::function<void()>&& work); + +private: + std::vector<std::thread> threads; + std::queue<std::function<void()>> requests; + std::mutex queue_mutex; + std::condition_variable condition; + std::atomic_bool stop{}; +}; + +} // namespace Common diff --git a/src/common/timer.cpp b/src/common/timer.cpp deleted file mode 100644 index 2dc15e434..000000000 --- a/src/common/timer.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <ctime> -#include <fmt/format.h> -#include "common/common_types.h" -#include "common/string_util.h" -#include "common/timer.h" - -namespace Common { - -std::chrono::milliseconds Timer::GetTimeMs() { - return std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::system_clock::now().time_since_epoch()); -} - -// -------------------------------------------- -// Initiate, Start, Stop, and Update the time -// -------------------------------------------- - -// Set initial values for the class -Timer::Timer() : m_LastTime(0), m_StartTime(0), m_Running(false) { - Update(); -} - -// Write the starting time -void Timer::Start() { - m_StartTime = GetTimeMs(); - m_Running = true; -} - -// Stop the timer -void Timer::Stop() { - // Write the final time - m_LastTime = GetTimeMs(); - m_Running = false; -} - -// Update the last time variable -void Timer::Update() { - m_LastTime = GetTimeMs(); - // TODO(ector) - QPF -} - -// ------------------------------------- -// Get time difference and elapsed time -// ------------------------------------- - -// Get the number of milliseconds since the last Update() -std::chrono::milliseconds Timer::GetTimeDifference() { - return GetTimeMs() - m_LastTime; -} - -// Add the time difference since the last Update() to the starting time. -// This is used to compensate for a paused game. -void Timer::AddTimeDifference() { - m_StartTime += GetTimeDifference(); -} - -// Get the time elapsed since the Start() -std::chrono::milliseconds Timer::GetTimeElapsed() { - // If we have not started yet, return 1 (because then I don't - // have to change the FPS calculation in CoreRerecording.cpp . - if (m_StartTime.count() == 0) - return std::chrono::milliseconds(1); - - // Return the final timer time if the timer is stopped - if (!m_Running) - return (m_LastTime - m_StartTime); - - return (GetTimeMs() - m_StartTime); -} - -// Get the formatted time elapsed since the Start() -std::string Timer::GetTimeElapsedFormatted() const { - // If we have not started yet, return zero - if (m_StartTime.count() == 0) - return "00:00:00:000"; - - // The number of milliseconds since the start. - // Use a different value if the timer is stopped. - std::chrono::milliseconds Milliseconds; - if (m_Running) - Milliseconds = GetTimeMs() - m_StartTime; - else - Milliseconds = m_LastTime - m_StartTime; - // Seconds - std::chrono::seconds Seconds = std::chrono::duration_cast<std::chrono::seconds>(Milliseconds); - // Minutes - std::chrono::minutes Minutes = std::chrono::duration_cast<std::chrono::minutes>(Milliseconds); - // Hours - std::chrono::hours Hours = std::chrono::duration_cast<std::chrono::hours>(Milliseconds); - - std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours.count(), Minutes.count() % 60, - Seconds.count() % 60, Milliseconds.count() % 1000); - return TmpStr; -} - -// Get the number of seconds since January 1 1970 -std::chrono::seconds Timer::GetTimeSinceJan1970() { - return std::chrono::duration_cast<std::chrono::seconds>(GetTimeMs()); -} - -std::chrono::seconds Timer::GetLocalTimeSinceJan1970() { - time_t sysTime, tzDiff, tzDST; - struct tm* gmTime; - - time(&sysTime); - - // Account for DST where needed - gmTime = localtime(&sysTime); - if (gmTime->tm_isdst == 1) - tzDST = 3600; - else - tzDST = 0; - - // Lazy way to get local time in sec - gmTime = gmtime(&sysTime); - tzDiff = sysTime - mktime(gmTime); - - return std::chrono::seconds(sysTime + tzDiff + tzDST); -} - -// Return the current time formatted as Minutes:Seconds:Milliseconds -// in the form 00:00:000. -std::string Timer::GetTimeFormatted() { - time_t sysTime; - struct tm* gmTime; - char tmp[13]; - - time(&sysTime); - gmTime = localtime(&sysTime); - - strftime(tmp, 6, "%M:%S", gmTime); - - u64 milliseconds = static_cast<u64>(GetTimeMs().count()) % 1000; - return fmt::format("{}:{:03}", tmp, milliseconds); -} - -// Returns a timestamp with decimals for precise time comparisons -// ---------------- -double Timer::GetDoubleTime() { - // Get continuous timestamp - u64 TmpSeconds = static_cast<u64>(Common::Timer::GetTimeSinceJan1970().count()); - double ms = static_cast<u64>(GetTimeMs().count()) % 1000; - - // Remove a few years. We only really want enough seconds to make - // sure that we are detecting actual actions, perhaps 60 seconds is - // enough really, but I leave a year of seconds anyway, in case the - // user's clock is incorrect or something like that. - TmpSeconds = TmpSeconds - (38 * 365 * 24 * 60 * 60); - - // Make a smaller integer that fits in the double - u32 Seconds = static_cast<u32>(TmpSeconds); - double TmpTime = Seconds + ms; - - return TmpTime; -} - -} // Namespace Common diff --git a/src/common/timer.h b/src/common/timer.h deleted file mode 100644 index 8894a143d..000000000 --- a/src/common/timer.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <chrono> -#include <string> -#include "common/common_types.h" - -namespace Common { -class Timer { -public: - Timer(); - - void Start(); - void Stop(); - void Update(); - - // The time difference is always returned in milliseconds, regardless of alternative internal - // representation - [[nodiscard]] std::chrono::milliseconds GetTimeDifference(); - void AddTimeDifference(); - - [[nodiscard]] static std::chrono::seconds GetTimeSinceJan1970(); - [[nodiscard]] static std::chrono::seconds GetLocalTimeSinceJan1970(); - [[nodiscard]] static double GetDoubleTime(); - - [[nodiscard]] static std::string GetTimeFormatted(); - [[nodiscard]] std::string GetTimeElapsedFormatted() const; - [[nodiscard]] std::chrono::milliseconds GetTimeElapsed(); - - [[nodiscard]] static std::chrono::milliseconds GetTimeMs(); - -private: - std::chrono::milliseconds m_LastTime; - std::chrono::milliseconds m_StartTime; - bool m_Running; -}; - -} // Namespace Common diff --git a/src/common/tree.h b/src/common/tree.h new file mode 100644 index 000000000..3da49e422 --- /dev/null +++ b/src/common/tree.h @@ -0,0 +1,674 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright 2002 Niels Provos <provos@citi.umich.edu> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +/* + * This file defines data structures for red-black trees. + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +namespace Common { +template <typename T> +class RBHead { +public: + [[nodiscard]] T* Root() { + return rbh_root; + } + + [[nodiscard]] const T* Root() const { + return rbh_root; + } + + void SetRoot(T* root) { + rbh_root = root; + } + + [[nodiscard]] bool IsEmpty() const { + return Root() == nullptr; + } + +private: + T* rbh_root = nullptr; +}; + +enum class EntryColor { + Black, + Red, +}; + +template <typename T> +class RBEntry { +public: + [[nodiscard]] T* Left() { + return rbe_left; + } + + [[nodiscard]] const T* Left() const { + return rbe_left; + } + + void SetLeft(T* left) { + rbe_left = left; + } + + [[nodiscard]] T* Right() { + return rbe_right; + } + + [[nodiscard]] const T* Right() const { + return rbe_right; + } + + void SetRight(T* right) { + rbe_right = right; + } + + [[nodiscard]] T* Parent() { + return rbe_parent; + } + + [[nodiscard]] const T* Parent() const { + return rbe_parent; + } + + void SetParent(T* parent) { + rbe_parent = parent; + } + + [[nodiscard]] bool IsBlack() const { + return rbe_color == EntryColor::Black; + } + + [[nodiscard]] bool IsRed() const { + return rbe_color == EntryColor::Red; + } + + [[nodiscard]] EntryColor Color() const { + return rbe_color; + } + + void SetColor(EntryColor color) { + rbe_color = color; + } + +private: + T* rbe_left = nullptr; + T* rbe_right = nullptr; + T* rbe_parent = nullptr; + EntryColor rbe_color{}; +}; + +template <typename Node> +[[nodiscard]] RBEntry<Node>& RB_ENTRY(Node* node) { + return node->GetEntry(); +} + +template <typename Node> +[[nodiscard]] const RBEntry<Node>& RB_ENTRY(const Node* node) { + return node->GetEntry(); +} + +template <typename Node> +[[nodiscard]] Node* RB_PARENT(Node* node) { + return RB_ENTRY(node).Parent(); +} + +template <typename Node> +[[nodiscard]] const Node* RB_PARENT(const Node* node) { + return RB_ENTRY(node).Parent(); +} + +template <typename Node> +void RB_SET_PARENT(Node* node, Node* parent) { + return RB_ENTRY(node).SetParent(parent); +} + +template <typename Node> +[[nodiscard]] Node* RB_LEFT(Node* node) { + return RB_ENTRY(node).Left(); +} + +template <typename Node> +[[nodiscard]] const Node* RB_LEFT(const Node* node) { + return RB_ENTRY(node).Left(); +} + +template <typename Node> +void RB_SET_LEFT(Node* node, Node* left) { + return RB_ENTRY(node).SetLeft(left); +} + +template <typename Node> +[[nodiscard]] Node* RB_RIGHT(Node* node) { + return RB_ENTRY(node).Right(); +} + +template <typename Node> +[[nodiscard]] const Node* RB_RIGHT(const Node* node) { + return RB_ENTRY(node).Right(); +} + +template <typename Node> +void RB_SET_RIGHT(Node* node, Node* right) { + return RB_ENTRY(node).SetRight(right); +} + +template <typename Node> +[[nodiscard]] bool RB_IS_BLACK(const Node* node) { + return RB_ENTRY(node).IsBlack(); +} + +template <typename Node> +[[nodiscard]] bool RB_IS_RED(const Node* node) { + return RB_ENTRY(node).IsRed(); +} + +template <typename Node> +[[nodiscard]] EntryColor RB_COLOR(const Node* node) { + return RB_ENTRY(node).Color(); +} + +template <typename Node> +void RB_SET_COLOR(Node* node, EntryColor color) { + return RB_ENTRY(node).SetColor(color); +} + +template <typename Node> +void RB_SET(Node* node, Node* parent) { + auto& entry = RB_ENTRY(node); + entry.SetParent(parent); + entry.SetLeft(nullptr); + entry.SetRight(nullptr); + entry.SetColor(EntryColor::Red); +} + +template <typename Node> +void RB_SET_BLACKRED(Node* black, Node* red) { + RB_SET_COLOR(black, EntryColor::Black); + RB_SET_COLOR(red, EntryColor::Red); +} + +template <typename Node> +void RB_ROTATE_LEFT(RBHead<Node>* head, Node* elm, Node*& tmp) { + tmp = RB_RIGHT(elm); + RB_SET_RIGHT(elm, RB_LEFT(tmp)); + if (RB_RIGHT(elm) != nullptr) { + RB_SET_PARENT(RB_LEFT(tmp), elm); + } + + RB_SET_PARENT(tmp, RB_PARENT(elm)); + if (RB_PARENT(tmp) != nullptr) { + if (elm == RB_LEFT(RB_PARENT(elm))) { + RB_SET_LEFT(RB_PARENT(elm), tmp); + } else { + RB_SET_RIGHT(RB_PARENT(elm), tmp); + } + } else { + head->SetRoot(tmp); + } + + RB_SET_LEFT(tmp, elm); + RB_SET_PARENT(elm, tmp); +} + +template <typename Node> +void RB_ROTATE_RIGHT(RBHead<Node>* head, Node* elm, Node*& tmp) { + tmp = RB_LEFT(elm); + RB_SET_LEFT(elm, RB_RIGHT(tmp)); + if (RB_LEFT(elm) != nullptr) { + RB_SET_PARENT(RB_RIGHT(tmp), elm); + } + + RB_SET_PARENT(tmp, RB_PARENT(elm)); + if (RB_PARENT(tmp) != nullptr) { + if (elm == RB_LEFT(RB_PARENT(elm))) { + RB_SET_LEFT(RB_PARENT(elm), tmp); + } else { + RB_SET_RIGHT(RB_PARENT(elm), tmp); + } + } else { + head->SetRoot(tmp); + } + + RB_SET_RIGHT(tmp, elm); + RB_SET_PARENT(elm, tmp); +} + +template <typename Node> +void RB_INSERT_COLOR(RBHead<Node>* head, Node* elm) { + Node* parent = nullptr; + Node* tmp = nullptr; + + while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) { + Node* gparent = RB_PARENT(parent); + if (parent == RB_LEFT(gparent)) { + tmp = RB_RIGHT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, EntryColor::Black); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_RIGHT(parent) == elm) { + RB_ROTATE_LEFT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_RIGHT(head, gparent, tmp); + } else { + tmp = RB_LEFT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, EntryColor::Black); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_LEFT(parent) == elm) { + RB_ROTATE_RIGHT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_LEFT(head, gparent, tmp); + } + } + + RB_SET_COLOR(head->Root(), EntryColor::Black); +} + +template <typename Node> +void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) { + Node* tmp; + while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root()) { + if (RB_LEFT(parent) == elm) { + tmp = RB_RIGHT(parent); + if (RB_IS_RED(tmp)) { + RB_SET_BLACKRED(tmp, parent); + RB_ROTATE_LEFT(head, parent, tmp); + tmp = RB_RIGHT(parent); + } + + if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && + (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { + RB_SET_COLOR(tmp, EntryColor::Red); + elm = parent; + parent = RB_PARENT(elm); + } else { + if (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp))) { + Node* oleft; + if ((oleft = RB_LEFT(tmp)) != nullptr) { + RB_SET_COLOR(oleft, EntryColor::Black); + } + + RB_SET_COLOR(tmp, EntryColor::Red); + RB_ROTATE_RIGHT(head, tmp, oleft); + tmp = RB_RIGHT(parent); + } + + RB_SET_COLOR(tmp, RB_COLOR(parent)); + RB_SET_COLOR(parent, EntryColor::Black); + if (RB_RIGHT(tmp)) { + RB_SET_COLOR(RB_RIGHT(tmp), EntryColor::Black); + } + + RB_ROTATE_LEFT(head, parent, tmp); + elm = head->Root(); + break; + } + } else { + tmp = RB_LEFT(parent); + if (RB_IS_RED(tmp)) { + RB_SET_BLACKRED(tmp, parent); + RB_ROTATE_RIGHT(head, parent, tmp); + tmp = RB_LEFT(parent); + } + + if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && + (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { + RB_SET_COLOR(tmp, EntryColor::Red); + elm = parent; + parent = RB_PARENT(elm); + } else { + if (RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) { + Node* oright; + if ((oright = RB_RIGHT(tmp)) != nullptr) { + RB_SET_COLOR(oright, EntryColor::Black); + } + + RB_SET_COLOR(tmp, EntryColor::Red); + RB_ROTATE_LEFT(head, tmp, oright); + tmp = RB_LEFT(parent); + } + + RB_SET_COLOR(tmp, RB_COLOR(parent)); + RB_SET_COLOR(parent, EntryColor::Black); + + if (RB_LEFT(tmp)) { + RB_SET_COLOR(RB_LEFT(tmp), EntryColor::Black); + } + + RB_ROTATE_RIGHT(head, parent, tmp); + elm = head->Root(); + break; + } + } + } + + if (elm) { + RB_SET_COLOR(elm, EntryColor::Black); + } +} + +template <typename Node> +Node* RB_REMOVE(RBHead<Node>* head, Node* elm) { + Node* child = nullptr; + Node* parent = nullptr; + Node* old = elm; + EntryColor color{}; + + const auto finalize = [&] { + if (color == EntryColor::Black) { + RB_REMOVE_COLOR(head, parent, child); + } + + return old; + }; + + if (RB_LEFT(elm) == nullptr) { + child = RB_RIGHT(elm); + } else if (RB_RIGHT(elm) == nullptr) { + child = RB_LEFT(elm); + } else { + Node* left; + elm = RB_RIGHT(elm); + while ((left = RB_LEFT(elm)) != nullptr) { + elm = left; + } + + child = RB_RIGHT(elm); + parent = RB_PARENT(elm); + color = RB_COLOR(elm); + + if (child) { + RB_SET_PARENT(child, parent); + } + if (parent) { + if (RB_LEFT(parent) == elm) { + RB_SET_LEFT(parent, child); + } else { + RB_SET_RIGHT(parent, child); + } + } else { + head->SetRoot(child); + } + + if (RB_PARENT(elm) == old) { + parent = elm; + } + + elm->SetEntry(old->GetEntry()); + + if (RB_PARENT(old)) { + if (RB_LEFT(RB_PARENT(old)) == old) { + RB_SET_LEFT(RB_PARENT(old), elm); + } else { + RB_SET_RIGHT(RB_PARENT(old), elm); + } + } else { + head->SetRoot(elm); + } + RB_SET_PARENT(RB_LEFT(old), elm); + if (RB_RIGHT(old)) { + RB_SET_PARENT(RB_RIGHT(old), elm); + } + if (parent) { + left = parent; + } + + return finalize(); + } + + parent = RB_PARENT(elm); + color = RB_COLOR(elm); + + if (child) { + RB_SET_PARENT(child, parent); + } + if (parent) { + if (RB_LEFT(parent) == elm) { + RB_SET_LEFT(parent, child); + } else { + RB_SET_RIGHT(parent, child); + } + } else { + head->SetRoot(child); + } + + return finalize(); +} + +// Inserts a node into the RB tree +template <typename Node, typename CompareFunction> +Node* RB_INSERT(RBHead<Node>* head, Node* elm, CompareFunction cmp) { + Node* parent = nullptr; + Node* tmp = head->Root(); + int comp = 0; + + while (tmp) { + parent = tmp; + comp = cmp(elm, parent); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + RB_SET(elm, parent); + + if (parent != nullptr) { + if (comp < 0) { + RB_SET_LEFT(parent, elm); + } else { + RB_SET_RIGHT(parent, elm); + } + } else { + head->SetRoot(elm); + } + + RB_INSERT_COLOR(head, elm); + return nullptr; +} + +// Finds the node with the same key as elm +template <typename Node, typename CompareFunction> +Node* RB_FIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) { + Node* tmp = head->Root(); + + while (tmp) { + const int comp = cmp(elm, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return nullptr; +} + +// Finds the first node greater than or equal to the search key +template <typename Node, typename CompareFunction> +Node* RB_NFIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) { + Node* tmp = head->Root(); + Node* res = nullptr; + + while (tmp) { + const int comp = cmp(elm, tmp); + if (comp < 0) { + res = tmp; + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return res; +} + +// Finds the node with the same key as lelm +template <typename Node, typename CompareFunction> +Node* RB_FIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) { + Node* tmp = head->Root(); + + while (tmp) { + const int comp = lcmp(lelm, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return nullptr; +} + +// Finds the first node greater than or equal to the search key +template <typename Node, typename CompareFunction> +Node* RB_NFIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) { + Node* tmp = head->Root(); + Node* res = nullptr; + + while (tmp) { + const int comp = lcmp(lelm, tmp); + if (comp < 0) { + res = tmp; + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return res; +} + +template <typename Node> +Node* RB_NEXT(Node* elm) { + if (RB_RIGHT(elm)) { + elm = RB_RIGHT(elm); + while (RB_LEFT(elm)) { + elm = RB_LEFT(elm); + } + } else { + if (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } else { + while (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } + elm = RB_PARENT(elm); + } + } + return elm; +} + +template <typename Node> +Node* RB_PREV(Node* elm) { + if (RB_LEFT(elm)) { + elm = RB_LEFT(elm); + while (RB_RIGHT(elm)) { + elm = RB_RIGHT(elm); + } + } else { + if (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } else { + while (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } + elm = RB_PARENT(elm); + } + } + return elm; +} + +template <typename Node> +Node* RB_MINMAX(RBHead<Node>* head, bool is_min) { + Node* tmp = head->Root(); + Node* parent = nullptr; + + while (tmp) { + parent = tmp; + if (is_min) { + tmp = RB_LEFT(tmp); + } else { + tmp = RB_RIGHT(tmp); + } + } + + return parent; +} + +template <typename Node> +Node* RB_MIN(RBHead<Node>* head) { + return RB_MINMAX(head, true); +} + +template <typename Node> +Node* RB_MAX(RBHead<Node>* head) { + return RB_MINMAX(head, false); +} +} // namespace Common diff --git a/src/common/uuid.h b/src/common/uuid.h index 4ab9a25f0..2e7a18405 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -14,8 +14,8 @@ constexpr u128 INVALID_UUID{{0, 0}}; struct UUID { // UUIDs which are 0 are considered invalid! - u128 uuid = INVALID_UUID; - constexpr UUID() = default; + u128 uuid; + UUID() = default; constexpr explicit UUID(const u128& id) : uuid{id} {} constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 2a0fcf541..22dba3c2d 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -87,7 +87,13 @@ public: template <typename V> [[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { - return {x * f, y * f}; + using TV = decltype(T{} * V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)), + }; } template <typename V> @@ -98,7 +104,13 @@ public: template <typename V> [[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const { - return {x / f, y / f}; + using TV = decltype(T{} / V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)), + }; } template <typename V> @@ -168,7 +180,10 @@ public: template <typename T, typename V> [[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) { - return Vec2<T>(f * vec.x, f * vec.y); + using C = std::common_type_t<T, V>; + + return Vec2<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)), + static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y))); } using Vec2f = Vec2<float>; @@ -237,7 +252,14 @@ public: template <typename V> [[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const { - return {x * f, y * f, z * f}; + using TV = decltype(T{} * V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)), + }; } template <typename V> @@ -247,7 +269,14 @@ public: } template <typename V> [[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const { - return {x / f, y / f, z / f}; + using TV = decltype(T{} / V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)), + }; } template <typename V> @@ -367,7 +396,11 @@ public: template <typename T, typename V> [[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) { - return Vec3<T>(f * vec.x, f * vec.y, f * vec.z); + using C = std::common_type_t<T, V>; + + return Vec3<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)), + static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)), + static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.z))); } template <> @@ -446,7 +479,15 @@ public: template <typename V> [[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const { - return {x * f, y * f, z * f, w * f}; + using TV = decltype(T{} * V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)), + static_cast<TV>(static_cast<C>(w) * static_cast<C>(f)), + }; } template <typename V> @@ -457,7 +498,15 @@ public: template <typename V> [[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const { - return {x / f, y / f, z / f, w / f}; + using TV = decltype(T{} / V{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)), + static_cast<TV>(static_cast<C>(w) / static_cast<C>(f)), + }; } template <typename V> @@ -582,7 +631,15 @@ public: template <typename T, typename V> [[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) { - return {f * vec.x, f * vec.y, f * vec.z, f * vec.w}; + using TV = decltype(V{} * T{}); + using C = std::common_type_t<T, V>; + + return { + static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.x)), + static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.y)), + static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.z)), + static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.w)), + }; } using Vec4f = Vec4<float>; diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp index b009cb500..e3ca29258 100644 --- a/src/common/virtual_buffer.cpp +++ b/src/common/virtual_buffer.cpp @@ -13,7 +13,7 @@ namespace Common { -void* AllocateMemoryPages(std::size_t size) { +void* AllocateMemoryPages(std::size_t size) noexcept { #ifdef _WIN32 void* base{VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE)}; #else @@ -29,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) { return base; } -void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) { +void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { if (!base) { return; } diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h index 125cb42f0..fb1a6f81f 100644 --- a/src/common/virtual_buffer.h +++ b/src/common/virtual_buffer.h @@ -4,29 +4,55 @@ #pragma once -#include "common/common_funcs.h" +#include <type_traits> +#include <utility> namespace Common { -void* AllocateMemoryPages(std::size_t size); -void FreeMemoryPages(void* base, std::size_t size); +void* AllocateMemoryPages(std::size_t size) noexcept; +void FreeMemoryPages(void* base, std::size_t size) noexcept; template <typename T> -class VirtualBuffer final : NonCopyable { +class VirtualBuffer final { public: + // TODO: Uncomment this and change Common::PageTable::PageInfo to be trivially constructible + // using std::atomic_ref once libc++ has support for it + // static_assert( + // std::is_trivially_constructible_v<T>, + // "T must be trivially constructible, as non-trivial constructors will not be executed " + // "with the current allocator"); + constexpr VirtualBuffer() = default; explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} { base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); } - ~VirtualBuffer() { + ~VirtualBuffer() noexcept { FreeMemoryPages(base_ptr, alloc_size); } + VirtualBuffer(const VirtualBuffer&) = delete; + VirtualBuffer& operator=(const VirtualBuffer&) = delete; + + VirtualBuffer(VirtualBuffer&& other) noexcept + : alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), + nullptr} {} + + VirtualBuffer& operator=(VirtualBuffer&& other) noexcept { + alloc_size = std::exchange(other.alloc_size, 0); + base_ptr = std::exchange(other.base_ptr, nullptr); + return *this; + } + void resize(std::size_t count) { + const auto new_size = count * sizeof(T); + if (new_size == alloc_size) { + return; + } + FreeMemoryPages(base_ptr, alloc_size); - alloc_size = count * sizeof(T); + alloc_size = new_size; base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); } diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index 3afbdb898..a8c143f85 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -15,10 +15,10 @@ namespace Common { using base_timer = std::chrono::steady_clock; using base_time_point = std::chrono::time_point<base_timer>; -class StandardWallClock : public WallClock { +class StandardWallClock final : public WallClock { public: - StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency) - : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) { + explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_) + : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) { start_time = base_timer::now(); } @@ -53,7 +53,7 @@ public: return Common::Divide128On32(temporary, 1000000000).first; } - void Pause(bool is_paused) override { + void Pause([[maybe_unused]] bool is_paused) override { // Do nothing in this clock type. } diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 5db30083d..cef3e9499 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.h @@ -13,6 +13,8 @@ namespace Common { class WallClock { public: + virtual ~WallClock() = default; + /// Returns current wall time in nanoseconds [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; @@ -36,9 +38,9 @@ public: } protected: - WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native) - : emulated_cpu_frequency{emulated_cpu_frequency}, - emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {} + explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_) + : emulated_cpu_frequency{emulated_cpu_frequency_}, + emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {} u64 emulated_cpu_frequency; u64 emulated_clock_frequency; diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp index 424b39b1f..a65f6b832 100644 --- a/src/common/x64/native_clock.cpp +++ b/src/common/x64/native_clock.cpp @@ -2,19 +2,74 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <chrono> +#include <limits> #include <mutex> #include <thread> #ifdef _MSC_VER #include <intrin.h> + +#pragma intrinsic(__umulh) +#pragma intrinsic(_udiv128) #else #include <x86intrin.h> #endif +#include "common/atomic_ops.h" #include "common/uint128.h" #include "common/x64/native_clock.h" +namespace { + +[[nodiscard]] u64 GetFixedPoint64Factor(u64 numerator, u64 divisor) { +#ifdef __SIZEOF_INT128__ + const auto base = static_cast<unsigned __int128>(numerator) << 64ULL; + return static_cast<u64>(base / divisor); +#elif defined(_M_X64) || defined(_M_ARM64) + std::array<u64, 2> r = {0, numerator}; + u64 remainder; +#if _MSC_VER < 1923 + return udiv128(r[1], r[0], divisor, &remainder); +#else + return _udiv128(r[1], r[0], divisor, &remainder); +#endif +#else + // This one is bit more inaccurate. + return MultiplyAndDivide64(std::numeric_limits<u64>::max(), numerator, divisor); +#endif +} + +[[nodiscard]] u64 MultiplyHigh(u64 a, u64 b) { +#ifdef __SIZEOF_INT128__ + return (static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b)) >> 64; +#elif defined(_M_X64) || defined(_M_ARM64) + return __umulh(a, b); // MSVC +#else + // Generic fallback + const u64 a_lo = u32(a); + const u64 a_hi = a >> 32; + const u64 b_lo = u32(b); + const u64 b_hi = b >> 32; + + const u64 a_x_b_hi = a_hi * b_hi; + const u64 a_x_b_mid = a_hi * b_lo; + const u64 b_x_a_mid = b_hi * a_lo; + const u64 a_x_b_lo = a_lo * b_lo; + + const u64 carry_bit = (static_cast<u64>(static_cast<u32>(a_x_b_mid)) + + static_cast<u64>(static_cast<u32>(b_x_a_mid)) + (a_x_b_lo >> 32)) >> + 32; + + const u64 multhi = a_x_b_hi + (a_x_b_mid >> 32) + (b_x_a_mid >> 32) + carry_bit; + + return multhi; +#endif +} + +} // namespace + namespace Common { u64 EstimateRDTSCFrequency() { @@ -43,59 +98,76 @@ u64 EstimateRDTSCFrequency() { } namespace X64 { -NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, - u64 rtsc_frequency) - : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{ - rtsc_frequency} { +NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, + u64 rtsc_frequency_) + : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{ + rtsc_frequency_} { _mm_mfence(); - last_measure = __rdtsc(); - accumulated_ticks = 0U; + time_point.inner.last_measure = __rdtsc(); + time_point.inner.accumulated_ticks = 0U; + ns_rtsc_factor = GetFixedPoint64Factor(1000000000, rtsc_frequency); + us_rtsc_factor = GetFixedPoint64Factor(1000000, rtsc_frequency); + ms_rtsc_factor = GetFixedPoint64Factor(1000, rtsc_frequency); + clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency); + cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency); } u64 NativeClock::GetRTSC() { - std::scoped_lock scope{rtsc_serialize}; - _mm_mfence(); - const u64 current_measure = __rdtsc(); - u64 diff = current_measure - last_measure; - diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0) - if (current_measure > last_measure) { - last_measure = current_measure; - } - accumulated_ticks += diff; + TimePoint new_time_point{}; + TimePoint current_time_point{}; + do { + current_time_point.pack = time_point.pack; + _mm_mfence(); + const u64 current_measure = __rdtsc(); + u64 diff = current_measure - current_time_point.inner.last_measure; + diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0) + new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure + ? current_measure + : current_time_point.inner.last_measure; + new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff; + } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack, + current_time_point.pack)); /// The clock cannot be more precise than the guest timer, remove the lower bits - return accumulated_ticks & inaccuracy_mask; + return new_time_point.inner.accumulated_ticks & inaccuracy_mask; } void NativeClock::Pause(bool is_paused) { if (!is_paused) { - _mm_mfence(); - last_measure = __rdtsc(); + TimePoint current_time_point{}; + TimePoint new_time_point{}; + do { + current_time_point.pack = time_point.pack; + new_time_point.pack = current_time_point.pack; + _mm_mfence(); + new_time_point.inner.last_measure = __rdtsc(); + } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack, + current_time_point.pack)); } } std::chrono::nanoseconds NativeClock::GetTimeNS() { const u64 rtsc_value = GetRTSC(); - return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)}; + return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)}; } std::chrono::microseconds NativeClock::GetTimeUS() { const u64 rtsc_value = GetRTSC(); - return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)}; + return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)}; } std::chrono::milliseconds NativeClock::GetTimeMS() { const u64 rtsc_value = GetRTSC(); - return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)}; + return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)}; } u64 NativeClock::GetClockCycles() { const u64 rtsc_value = GetRTSC(); - return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency); + return MultiplyHigh(rtsc_value, clock_rtsc_factor); } u64 NativeClock::GetCPUCycles() { const u64 rtsc_value = GetRTSC(); - return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency); + return MultiplyHigh(rtsc_value, cpu_rtsc_factor); } } // namespace X64 diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h index 891a3bbfd..7cbd400d2 100644 --- a/src/common/x64/native_clock.h +++ b/src/common/x64/native_clock.h @@ -6,15 +6,15 @@ #include <optional> -#include "common/spin_lock.h" #include "common/wall_clock.h" namespace Common { namespace X64 { -class NativeClock : public WallClock { +class NativeClock final : public WallClock { public: - NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency); + explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, + u64 rtsc_frequency_); std::chrono::nanoseconds GetTimeNS() override; @@ -31,14 +31,28 @@ public: private: u64 GetRTSC(); + union alignas(16) TimePoint { + TimePoint() : pack{} {} + u128 pack{}; + struct Inner { + u64 last_measure{}; + u64 accumulated_ticks{}; + } inner; + }; + /// value used to reduce the native clocks accuracy as some apss rely on /// undefined behavior where the level of accuracy in the clock shouldn't /// be higher. - static constexpr u64 inaccuracy_mask = ~(0x400 - 1); + static constexpr u64 inaccuracy_mask = ~(UINT64_C(0x400) - 1); + + TimePoint time_point; + // factors + u64 clock_rtsc_factor{}; + u64 cpu_rtsc_factor{}; + u64 ns_rtsc_factor{}; + u64 us_rtsc_factor{}; + u64 ms_rtsc_factor{}; - SpinLock rtsc_serialize{}; - u64 last_measure{}; - u64 accumulated_ticks{}; u64 rtsc_frequency; }; } // namespace X64 diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index a5f5d4fc1..c2c9b6134 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -11,25 +11,25 @@ namespace Common::X64 { -inline std::size_t RegToIndex(const Xbyak::Reg& reg) { +constexpr size_t RegToIndex(const Xbyak::Reg& reg) { using Kind = Xbyak::Reg::Kind; ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, "RegSet only support GPRs and XMM registers."); ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); - return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); + return static_cast<size_t>(reg.getIdx()) + (reg.getKind() == Kind::REG ? 0 : 16); } -inline Xbyak::Reg64 IndexToReg64(std::size_t reg_index) { +constexpr Xbyak::Reg64 IndexToReg64(size_t reg_index) { ASSERT(reg_index < 16); return Xbyak::Reg64(static_cast<int>(reg_index)); } -inline Xbyak::Xmm IndexToXmm(std::size_t reg_index) { +constexpr Xbyak::Xmm IndexToXmm(size_t reg_index) { ASSERT(reg_index >= 16 && reg_index < 32); return Xbyak::Xmm(static_cast<int>(reg_index - 16)); } -inline Xbyak::Reg IndexToReg(std::size_t reg_index) { +constexpr Xbyak::Reg IndexToReg(size_t reg_index) { if (reg_index < 16) { return IndexToReg64(reg_index); } else { @@ -45,17 +45,17 @@ inline std::bitset<32> BuildRegSet(std::initializer_list<Xbyak::Reg> regs) { return bits; } -const std::bitset<32> ABI_ALL_GPRS(0x0000FFFF); -const std::bitset<32> ABI_ALL_XMMS(0xFFFF0000); +constexpr inline std::bitset<32> ABI_ALL_GPRS(0x0000FFFF); +constexpr inline std::bitset<32> ABI_ALL_XMMS(0xFFFF0000); #ifdef _WIN32 // Microsoft x64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; +constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; +constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; +constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; +constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({ // GPRs @@ -102,11 +102,11 @@ constexpr size_t ABI_SHADOW_SPACE = 0x20; #else // System V x86-64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; +constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; +constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; +constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; +constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({ // GPRs @@ -182,7 +182,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_GPRS[i]) { code.push(IndexToReg64(i)); } @@ -192,7 +192,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b code.sub(code.rsp, frame_info.subtraction); } - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_XMMS[i]) { code.movaps(code.xword[code.rsp + frame_info.xmm_offset], IndexToXmm(i)); frame_info.xmm_offset += 0x10; @@ -206,7 +206,7 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_XMMS[i]) { code.movaps(IndexToXmm(i), code.xword[code.rsp + frame_info.xmm_offset]); frame_info.xmm_offset += 0x10; @@ -218,8 +218,8 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits } // GPRs need to be popped in reverse order - for (std::size_t j = 0; j < regs.size(); ++j) { - const std::size_t i = regs.size() - j - 1; + for (size_t j = 0; j < regs.size(); ++j) { + const size_t i = regs.size() - j - 1; if (regs[i] && ABI_ALL_GPRS[i]) { code.pop(IndexToReg64(i)); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c85c9485f..28196d26a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,9 +1,3 @@ -if (YUZU_ENABLE_BOXCAT) - set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h) -else() - set(BCAT_BOXCAT_ADDITIONAL_SOURCES) -endif() - add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp @@ -19,8 +13,6 @@ add_library(core STATIC arm/dynarmic/arm_exclusive_monitor.h arm/exclusive_monitor.cpp arm/exclusive_monitor.h - arm/unicorn/arm_unicorn.cpp - arm/unicorn/arm_unicorn.h constants.cpp constants.h core.cpp @@ -49,6 +41,7 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h + file_sys/common_funcs.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -126,6 +119,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/controller.cpp + frontend/applets/controller.h frontend/applets/error.cpp frontend/applets/error.h frontend/applets/general_frontend.cpp @@ -140,26 +135,54 @@ add_library(core STATIC frontend/emu_window.h frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h + frontend/input_interpreter.cpp + frontend/input_interpreter.h frontend/input.h - gdbstub/gdbstub.cpp - gdbstub/gdbstub.h hardware_interrupt_manager.cpp hardware_interrupt_manager.h hle/ipc.h hle/ipc_helpers.h - hle/kernel/address_arbiter.cpp - hle/kernel/address_arbiter.h hle/kernel/client_port.cpp hle/kernel/client_port.h hle/kernel/client_session.cpp hle/kernel/client_session.h hle/kernel/code_set.cpp hle/kernel/code_set.h - hle/kernel/errors.h + hle/kernel/svc_results.h + hle/kernel/global_scheduler_context.cpp + hle/kernel/global_scheduler_context.h hle/kernel/handle_table.cpp hle/kernel/handle_table.h hle/kernel/hle_ipc.cpp hle/kernel/hle_ipc.h + hle/kernel/k_address_arbiter.cpp + hle/kernel/k_address_arbiter.h + hle/kernel/k_affinity_mask.h + hle/kernel/k_condition_variable.cpp + hle/kernel/k_condition_variable.h + hle/kernel/k_event.cpp + hle/kernel/k_event.h + hle/kernel/k_light_condition_variable.h + hle/kernel/k_light_lock.cpp + hle/kernel/k_light_lock.h + hle/kernel/k_priority_queue.h + hle/kernel/k_readable_event.cpp + hle/kernel/k_readable_event.h + hle/kernel/k_resource_limit.cpp + hle/kernel/k_resource_limit.h + hle/kernel/k_scheduler.cpp + hle/kernel/k_scheduler.h + hle/kernel/k_scheduler_lock.h + hle/kernel/k_scoped_lock.h + hle/kernel/k_scoped_resource_reservation.h + hle/kernel/k_scoped_scheduler_lock_and_sleep.h + hle/kernel/k_synchronization_object.cpp + hle/kernel/k_synchronization_object.h + hle/kernel/k_thread.cpp + hle/kernel/k_thread.h + hle/kernel/k_thread_queue.h + hle/kernel/k_writable_event.cpp + hle/kernel/k_writable_event.h hle/kernel/kernel.cpp hle/kernel/kernel.h hle/kernel/memory/address_space_info.cpp @@ -179,8 +202,6 @@ add_library(core STATIC hle/kernel/memory/slab_heap.h hle/kernel/memory/system_control.cpp hle/kernel/memory/system_control.h - hle/kernel/mutex.cpp - hle/kernel/mutex.h hle/kernel/object.cpp hle/kernel/object.h hle/kernel/physical_core.cpp @@ -190,36 +211,25 @@ add_library(core STATIC hle/kernel/process.h hle/kernel/process_capability.cpp hle/kernel/process_capability.h - hle/kernel/readable_event.cpp - hle/kernel/readable_event.h - hle/kernel/resource_limit.cpp - hle/kernel/resource_limit.h - hle/kernel/scheduler.cpp - hle/kernel/scheduler.h hle/kernel/server_port.cpp hle/kernel/server_port.h hle/kernel/server_session.cpp hle/kernel/server_session.h + hle/kernel/service_thread.cpp + hle/kernel/service_thread.h hle/kernel/session.cpp hle/kernel/session.h hle/kernel/shared_memory.cpp hle/kernel/shared_memory.h hle/kernel/svc.cpp hle/kernel/svc.h + hle/kernel/svc_common.h hle/kernel/svc_types.h hle/kernel/svc_wrap.h - hle/kernel/synchronization_object.cpp - hle/kernel/synchronization_object.h - hle/kernel/synchronization.cpp - hle/kernel/synchronization.h - hle/kernel/thread.cpp - hle/kernel/thread.h hle/kernel/time_manager.cpp hle/kernel/time_manager.h hle/kernel/transfer_memory.cpp hle/kernel/transfer_memory.h - hle/kernel/writable_event.cpp - hle/kernel/writable_event.h hle/lock.cpp hle/lock.h hle/result.h @@ -244,6 +254,8 @@ add_library(core STATIC hle/service/am/applet_oe.h hle/service/am/applets/applets.cpp hle/service/am/applets/applets.h + hle/service/am/applets/controller.cpp + hle/service/am/applets/controller.h hle/service/am/applets/error.cpp hle/service/am/applets/error.h hle/service/am/applets/general_backend.cpp @@ -254,6 +266,7 @@ add_library(core STATIC hle/service/am/applets/software_keyboard.h hle/service/am/applets/web_browser.cpp hle/service/am/applets/web_browser.h + hle/service/am/applets/web_types.h hle/service/am/idle.cpp hle/service/am/idle.h hle/service/am/omm.cpp @@ -299,7 +312,6 @@ add_library(core STATIC hle/service/audio/hwopus.h hle/service/bcat/backend/backend.cpp hle/service/bcat/backend/backend.h - ${BCAT_BOXCAT_ADDITIONAL_SOURCES} hle/service/bcat/bcat.cpp hle/service/bcat/bcat.h hle/service/bcat/module.cpp @@ -389,14 +401,13 @@ add_library(core STATIC hle/service/hid/controllers/xpad.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h + hle/service/ldn/errors.h hle/service/ldn/ldn.cpp hle/service/ldn/ldn.h hle/service/ldr/ldr.cpp hle/service/ldr/ldr.h hle/service/lm/lm.cpp hle/service/lm/lm.h - hle/service/lm/manager.cpp - hle/service/lm/manager.h hle/service/mig/mig.cpp hle/service/mig/mig.h hle/service/mii/manager.cpp @@ -442,6 +453,8 @@ add_library(core STATIC hle/service/nvdrv/devices/nvhost_gpu.h hle/service/nvdrv/devices/nvhost_nvdec.cpp hle/service/nvdrv/devices/nvhost_nvdec.h + hle/service/nvdrv/devices/nvhost_nvdec_common.cpp + hle/service/nvdrv/devices/nvhost_nvdec_common.h hle/service/nvdrv/devices/nvhost_nvjpg.cpp hle/service/nvdrv/devices/nvhost_nvjpg.h hle/service/nvdrv/devices/nvhost_vic.cpp @@ -455,10 +468,14 @@ add_library(core STATIC hle/service/nvdrv/nvdrv.h hle/service/nvdrv/nvmemp.cpp hle/service/nvdrv/nvmemp.h + hle/service/nvdrv/syncpoint_manager.cpp + hle/service/nvdrv/syncpoint_manager.h hle/service/nvflinger/buffer_queue.cpp hle/service/nvflinger/buffer_queue.h hle/service/nvflinger/nvflinger.cpp hle/service/nvflinger/nvflinger.h + hle/service/olsc/olsc.cpp + hle/service/olsc/olsc.h hle/service/pcie/pcie.cpp hle/service/pcie/pcie.h hle/service/pctl/module.cpp @@ -501,6 +518,8 @@ add_library(core STATIC hle/service/sockets/sfdnsres.h hle/service/sockets/sockets.cpp hle/service/sockets/sockets.h + hle/service/sockets/sockets_translate.cpp + hle/service/sockets/sockets_translate.h hle/service/spl/csrng.cpp hle/service/spl/csrng.h hle/service/spl/module.cpp @@ -601,6 +620,13 @@ add_library(core STATIC tools/freezer.h ) +if (YUZU_ENABLE_BOXCAT) + target_sources(core PRIVATE + hle/service/bcat/backend/boxcat.cpp + hle/service/bcat/backend/boxcat.h + ) +endif() + if (MSVC) target_compile_options(core PRIVATE # 'expression' : signed/unsigned mismatch @@ -615,13 +641,30 @@ if (MSVC) /we4267 # 'context' : truncation from 'type1' to 'type2' /we4305 + # 'function' : not all control paths return a value + /we4715 + ) +else() + target_compile_options(core PRIVATE + -Werror=conversion + -Werror=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=sign-compare + + $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> + + $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> + + -Wno-sign-conversion ) endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus zip) if (YUZU_ENABLE_BOXCAT) target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index d2295ed90..0951e1976 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -147,10 +147,18 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContex auto fp = ctx.cpu_registers[29]; auto lr = ctx.cpu_registers[30]; while (true) { - out.push_back({"", 0, lr, 0}); - if (!fp) { + out.push_back({ + .module = "", + .address = 0, + .original_address = lr, + .offset = 0, + .name = {}, + }); + + if (fp == 0) { break; } + lr = memory.Read64(fp + 8) - 4; fp = memory.Read64(fp); } diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 1f24051e4..9a0151736 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -26,9 +26,10 @@ using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CO /// Generic ARMv8 CPU interface class ARM_Interface : NonCopyable { public: - explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers, bool uses_wall_clock) - : system{system_}, interrupt_handlers{interrupt_handlers}, uses_wall_clock{ - uses_wall_clock} {} + explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers_, + bool uses_wall_clock_) + : system{system_}, interrupt_handlers{interrupt_handlers_}, uses_wall_clock{ + uses_wall_clock_} {} virtual ~ARM_Interface() = default; struct ThreadContext32 { @@ -64,15 +65,25 @@ public: /// Step CPU by one instruction virtual void Step() = 0; + /// Exits execution from a callback, the callback must rewind the stack + virtual void ExceptionalExit() = 0; + /// Clear all instruction cache virtual void ClearInstructionCache() = 0; - /// Notifies CPU emulation that the current page table has changed. - /// - /// @param new_page_table The new page table. - /// @param new_address_space_size_in_bits The new usable size of the address space in bits. - /// This can be either 32, 36, or 39 on official software. - /// + /** + * Clear instruction cache range + * @param addr Start address of the cache range to clear + * @param size Size of the cache range to clear, starting at addr + */ + virtual void InvalidateCacheRange(VAddr addr, std::size_t size) = 0; + + /** + * Notifies CPU emulation that the current page table has changed. + * @param new_page_table The new page table. + * @param new_address_space_size_in_bits The new usable size of the address space in bits. + * This can be either 32, 36, or 39 on official software. + */ virtual void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) = 0; diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h index 71e582f79..c20c280f1 100644 --- a/src/core/arm/cpu_interrupt_handler.h +++ b/src/core/arm/cpu_interrupt_handler.h @@ -21,8 +21,8 @@ public: CPUInterruptHandler(const CPUInterruptHandler&) = delete; CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete; - CPUInterruptHandler(CPUInterruptHandler&&) = default; - CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default; + CPUInterruptHandler(CPUInterruptHandler&&) = delete; + CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete; bool IsInterrupted() const { return is_interrupted; diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index b5f28a86e..ec4407b6e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -7,6 +7,7 @@ #include <dynarmic/A32/a32.h> #include <dynarmic/A32/config.h> #include <dynarmic/A32/context.h> +#include "common/assert.h" #include "common/logging/log.h" #include "common/page_table.h" #include "core/arm/cpu_interrupt_handler.h" @@ -70,15 +71,9 @@ public: } void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - switch (exception) { - case Dynarmic::A32::Exception::UndefinedInstruction: - case Dynarmic::A32::Exception::UnpredictableInstruction: - break; - case Dynarmic::A32::Exception::Breakpoint: - break; - } - LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", - static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); + LOG_CRITICAL(Core_ARM, + "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", + exception, pc, MemoryReadCode(pc), parent.IsInThumbMode()); UNIMPLEMENTED(); } @@ -132,6 +127,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>( page_table.pointers.data()); config.absolute_offset_page_table = true; + config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; config.only_detect_misalignment_via_page_table_on_page_boundary = true; @@ -179,6 +175,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& if (Settings::values.cpuopt_unsafe_reduce_fp_error) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } + if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; + } } return std::make_unique<Dynarmic::A32::Jit>(config); @@ -188,6 +187,10 @@ void ARM_Dynarmic_32::Run() { jit->Run(); } +void ARM_Dynarmic_32::ExceptionalExit() { + jit->ExceptionalExit(); +} + void ARM_Dynarmic_32::Step() { jit->Step(); } @@ -253,6 +256,9 @@ void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) { } void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) { + if (!jit) { + return; + } Dynarmic::A32::Context context; jit->SaveContext(context); ctx.cpu_registers = context.Regs(); @@ -262,6 +268,9 @@ void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) { } void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) { + if (!jit) { + return; + } Dynarmic::A32::Context context; context.Regs() = ctx.cpu_registers; context.ExtRegs() = ctx.extension_registers; @@ -281,7 +290,17 @@ void ARM_Dynarmic_32::ClearInstructionCache() { jit->ClearCache(); } +void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) { + if (!jit) { + return; + } + jit->InvalidateCacheRange(static_cast<u32>(addr), size); +} + void ARM_Dynarmic_32::ClearExclusiveState() { + if (!jit) { + return; + } jit->ClearExclusiveState(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index 2bab31b92..f6c4d4db9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -42,6 +42,7 @@ public: u32 GetPSTATE() const override; void SetPSTATE(u32 pstate) override; void Run() override; + void ExceptionalExit() override; void Step() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; @@ -49,6 +50,10 @@ public: u64 GetTPIDR_EL0() const override; void ChangeProcessorID(std::size_t new_core_id) override; + bool IsInThumbMode() const { + return (GetPSTATE() & 0x20) != 0; + } + void SaveContext(ThreadContext32& ctx) override; void SaveContext(ThreadContext64& ctx) override {} void LoadContext(const ThreadContext32& ctx) override; @@ -58,6 +63,7 @@ public: void ClearExclusiveState() override; void ClearInstructionCache() override; + void InvalidateCacheRange(VAddr addr, std::size_t size) override; void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) override; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index ce9968724..ae5566ab8 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -6,6 +6,7 @@ #include <memory> #include <dynarmic/A64/a64.h> #include <dynarmic/A64/config.h> +#include "common/assert.h" #include "common/logging/log.h" #include "common/page_table.h" #include "core/arm/cpu_interrupt_handler.h" @@ -13,11 +14,9 @@ #include "core/arm/dynarmic/arm_exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" -#include "core/core_timing_util.h" -#include "core/gdbstub/gdbstub.h" #include "core/hardware_properties.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/svc.h" #include "core/memory.h" #include "core/settings.h" @@ -82,16 +81,9 @@ public: } void InterpreterFallback(u64 pc, std::size_t num_instructions) override { - LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc, - num_instructions, MemoryReadCode(pc)); - - ARM_Interface::ThreadContext64 ctx; - parent.SaveContext(ctx); - parent.inner_unicorn.LoadContext(ctx); - parent.inner_unicorn.ExecuteInstructions(num_instructions); - parent.inner_unicorn.SaveContext(ctx); - parent.LoadContext(ctx); - num_interpreted_instructions += num_instructions; + LOG_ERROR(Core_ARM, + "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc, + num_instructions, MemoryReadCode(pc)); } void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { @@ -103,16 +95,6 @@ public: case Dynarmic::A64::Exception::Yield: return; case Dynarmic::A64::Exception::Breakpoint: - if (GDBStub::IsServerEnabled()) { - parent.jit->HaltExecution(); - parent.SetPC(pc); - Kernel::Thread* const thread = parent.system.CurrentScheduler().GetCurrentThread(); - parent.SaveContext(thread->GetContext64()); - GDBStub::Break(); - GDBStub::SendTrap(thread, 5); - return; - } - [[fallthrough]]; default: ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); @@ -127,18 +109,17 @@ public: if (parent.uses_wall_clock) { return; } + // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // rough approximation of the amount of executed ticks in the system, it may be thrown off // if not all cores are doing a similar amount of work. Instead of doing this, we should // device a way so that timing is consistent across all cores without increasing the ticks 4 // times. - u64 amortized_ticks = - (ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES; + u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max<u64>(amortized_ticks, 1); parent.system.CoreTiming().AddTicks(amortized_ticks); - num_interpreted_instructions = 0; } u64 GetTicksRemaining() override { @@ -156,7 +137,6 @@ public: } ARM_Dynarmic_64& parent; - std::size_t num_interpreted_instructions = 0; u64 tpidrro_el0 = 0; u64 tpidr_el0 = 0; static constexpr u64 minimum_run_cycles = 1000U; @@ -172,6 +152,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& // Memory config.page_table = reinterpret_cast<void**>(page_table.pointers.data()); config.page_table_address_space_bits = address_space_bits; + config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; config.silently_mirror_page_table = false; config.absolute_offset_page_table = true; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; @@ -231,6 +212,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& if (Settings::values.cpuopt_unsafe_reduce_fp_error) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } + if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; + } } return std::make_shared<Dynarmic::A64::Jit>(config); @@ -240,6 +224,10 @@ void ARM_Dynarmic_64::Run() { jit->Run(); } +void ARM_Dynarmic_64::ExceptionalExit() { + jit->ExceptionalExit(); +} + void ARM_Dynarmic_64::Step() { cb->InterpreterFallback(jit->GetPC(), 1); } @@ -248,12 +236,8 @@ ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handle bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor, std::size_t core_index) : ARM_Interface{system, interrupt_handlers, uses_wall_clock}, - cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system, interrupt_handlers, - uses_wall_clock, - ARM_Unicorn::Arch::AArch64, - core_index}, - core_index{core_index}, exclusive_monitor{ - dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} + cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index}, + exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; @@ -310,6 +294,9 @@ void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) { } void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) { + if (!jit) { + return; + } ctx.cpu_registers = jit->GetRegisters(); ctx.sp = jit->GetSP(); ctx.pc = jit->GetPC(); @@ -321,6 +308,9 @@ void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) { } void ARM_Dynarmic_64::LoadContext(const ThreadContext64& ctx) { + if (!jit) { + return; + } jit->SetRegisters(ctx.cpu_registers); jit->SetSP(ctx.sp); jit->SetPC(ctx.pc); @@ -342,7 +332,17 @@ void ARM_Dynarmic_64::ClearInstructionCache() { jit->ClearCache(); } +void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) { + if (!jit) { + return; + } + jit->InvalidateCacheRange(addr, size); +} + void ARM_Dynarmic_64::ClearExclusiveState() { + if (!jit) { + return; + } jit->ClearExclusiveState(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 403c55961..329b59a32 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -12,7 +12,6 @@ #include "common/hash.h" #include "core/arm/arm_interface.h" #include "core/arm/exclusive_monitor.h" -#include "core/arm/unicorn/arm_unicorn.h" namespace Core::Memory { class Memory; @@ -41,6 +40,7 @@ public: void SetPSTATE(u32 pstate) override; void Run() override; void Step() override; + void ExceptionalExit() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; @@ -56,6 +56,7 @@ public: void ClearExclusiveState() override; void ClearInstructionCache() override; + void InvalidateCacheRange(VAddr addr, std::size_t size) override; void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) override; @@ -71,7 +72,6 @@ private: std::unique_ptr<DynarmicCallbacks64> cb; JitCacheType jit_cache; std::shared_ptr<Dynarmic::A64::Jit> jit; - ARM_Unicorn inner_unicorn; std::size_t core_index; DynarmicExclusiveMonitor& exclusive_monitor; diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp index 54556e0f9..caefc09f4 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -34,7 +34,7 @@ std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigne CoprocReg CRm, unsigned opc2) { LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn, CRm, opc2); - return {}; + return std::nullopt; } CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, @@ -115,7 +115,7 @@ std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_trans LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "", long_transfer ? "l" : "", CRd); } - return {}; + return std::nullopt; } std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, @@ -127,7 +127,7 @@ std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_tran LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "", long_transfer ? "l" : "", CRd); } - return {}; + return std::nullopt; } } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h index 7356d252e..dc6f4af3a 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h @@ -35,8 +35,8 @@ public: std::optional<u8> option) override; ARM_Dynarmic_32& parent; - u32 uprw; - u32 uro; + u32 uprw = 0; + u32 uro = 0; }; } // namespace Core diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp deleted file mode 100644 index 1df3f3ed1..000000000 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <unicorn/arm64.h> -#include "common/assert.h" -#include "common/microprofile.h" -#include "core/arm/cpu_interrupt_handler.h" -#include "core/arm/unicorn/arm_unicorn.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/svc.h" -#include "core/memory.h" - -namespace Core { - -// Load Unicorn DLL once on Windows using RAII -#ifdef _MSC_VER -#include <unicorn_dynload.h> -struct LoadDll { -private: - LoadDll() { - ASSERT(uc_dyn_load(NULL, 0)); - } - ~LoadDll() { - ASSERT(uc_dyn_free()); - } - static LoadDll g_load_dll; -}; -LoadDll LoadDll::g_load_dll; -#endif - -#define CHECKED(expr) \ - do { \ - if (auto _cerr = (expr)) { \ - ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \ - uc_strerror(_cerr)); \ - } \ - } while (0) - -static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) { - GDBStub::BreakpointAddress bkpt = - GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute); - if (GDBStub::IsMemoryBreak() || - (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) { - auto core = static_cast<ARM_Unicorn*>(user_data); - core->RecordBreak(bkpt); - uc_emu_stop(uc); - } -} - -static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, - void* user_data) { - auto* const system = static_cast<System*>(user_data); - - ARM_Interface::ThreadContext64 ctx{}; - system->CurrentArmInterface().SaveContext(ctx); - ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr, - ctx.pc, ctx.cpu_registers[30]); - - return false; -} - -ARM_Unicorn::ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock, - Arch architecture, std::size_t core_index) - : ARM_Interface{system, interrupt_handlers, uses_wall_clock}, core_index{core_index} { - const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64; - CHECKED(uc_open(arch, UC_MODE_ARM, &uc)); - - auto fpv = 3 << 20; - CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv)); - - uc_hook hook{}; - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, UINT64_MAX)); - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0, - UINT64_MAX)); - if (GDBStub::IsServerEnabled()) { - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, UINT64_MAX)); - last_bkpt_hit = false; - } -} - -ARM_Unicorn::~ARM_Unicorn() { - CHECKED(uc_close(uc)); -} - -void ARM_Unicorn::SetPC(u64 pc) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); -} - -u64 ARM_Unicorn::GetPC() const { - u64 val{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val)); - return val; -} - -u64 ARM_Unicorn::GetReg(int regn) const { - u64 val{}; - auto treg = UC_ARM64_REG_SP; - if (regn <= 28) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn); - } else if (regn < 31) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29); - } - CHECKED(uc_reg_read(uc, treg, &val)); - return val; -} - -void ARM_Unicorn::SetReg(int regn, u64 val) { - auto treg = UC_ARM64_REG_SP; - if (regn <= 28) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn); - } else if (regn < 31) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29); - } - CHECKED(uc_reg_write(uc, treg, &val)); -} - -u128 ARM_Unicorn::GetVectorReg(int /*index*/) const { - UNIMPLEMENTED(); - static constexpr u128 res{}; - return res; -} - -void ARM_Unicorn::SetVectorReg(int /*index*/, u128 /*value*/) { - UNIMPLEMENTED(); -} - -u32 ARM_Unicorn::GetPSTATE() const { - u64 nzcv{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &nzcv)); - return static_cast<u32>(nzcv); -} - -void ARM_Unicorn::SetPSTATE(u32 pstate) { - u64 nzcv = pstate; - CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &nzcv)); -} - -VAddr ARM_Unicorn::GetTlsAddress() const { - u64 base{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); - return base; -} - -void ARM_Unicorn::SetTlsAddress(VAddr base) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); -} - -u64 ARM_Unicorn::GetTPIDR_EL0() const { - u64 value{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDR_EL0, &value)); - return value; -} - -void ARM_Unicorn::SetTPIDR_EL0(u64 value) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value)); -} - -void ARM_Unicorn::ChangeProcessorID(std::size_t new_core_id) { - core_index = new_core_id; -} - -void ARM_Unicorn::Run() { - if (GDBStub::IsServerEnabled()) { - ExecuteInstructions(std::max(4000000U, 0U)); - } else { - while (true) { - if (interrupt_handlers[core_index].IsInterrupted()) { - return; - } - ExecuteInstructions(10); - } - } -} - -void ARM_Unicorn::Step() { - ExecuteInstructions(1); -} - -MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64)); - -void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) { - MICROPROFILE_SCOPE(ARM_Jit_Unicorn); - - // Temporarily map the code page for Unicorn - u64 map_addr{GetPC() & ~Memory::PAGE_MASK}; - std::vector<u8> page_buffer(Memory::PAGE_SIZE); - system.Memory().ReadBlock(map_addr, page_buffer.data(), page_buffer.size()); - - CHECKED(uc_mem_map_ptr(uc, map_addr, page_buffer.size(), - UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data())); - CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); - CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size())); - if (GDBStub::IsServerEnabled()) { - if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) { - uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); - } - - Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread(); - SaveContext(thread->GetContext64()); - if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) { - last_bkpt_hit = false; - GDBStub::Break(); - GDBStub::SendTrap(thread, 5); - } - } -} - -void ARM_Unicorn::SaveContext(ThreadContext64& ctx) { - int uregs[32]; - void* tregs[32]; - - CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &ctx.sp)); - CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); - - for (auto i = 0; i < 29; ++i) { - uregs[i] = UC_ARM64_REG_X0 + i; - tregs[i] = &ctx.cpu_registers[i]; - } - uregs[29] = UC_ARM64_REG_X29; - tregs[29] = (void*)&ctx.cpu_registers[29]; - uregs[30] = UC_ARM64_REG_X30; - tregs[30] = (void*)&ctx.cpu_registers[30]; - - CHECKED(uc_reg_read_batch(uc, uregs, tregs, 31)); - - for (int i = 0; i < 32; ++i) { - uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = &ctx.vector_registers[i]; - } - - CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32)); -} - -void ARM_Unicorn::LoadContext(const ThreadContext64& ctx) { - int uregs[32]; - void* tregs[32]; - - CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &ctx.sp)); - CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); - - for (int i = 0; i < 29; ++i) { - uregs[i] = UC_ARM64_REG_X0 + i; - tregs[i] = (void*)&ctx.cpu_registers[i]; - } - uregs[29] = UC_ARM64_REG_X29; - tregs[29] = (void*)&ctx.cpu_registers[29]; - uregs[30] = UC_ARM64_REG_X30; - tregs[30] = (void*)&ctx.cpu_registers[30]; - - CHECKED(uc_reg_write_batch(uc, uregs, tregs, 31)); - - for (auto i = 0; i < 32; ++i) { - uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = (void*)&ctx.vector_registers[i]; - } - - CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32)); -} - -void ARM_Unicorn::PrepareReschedule() { - CHECKED(uc_emu_stop(uc)); -} - -void ARM_Unicorn::ClearExclusiveState() {} - -void ARM_Unicorn::ClearInstructionCache() {} - -void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) { - last_bkpt = bkpt; - last_bkpt_hit = true; -} - -void ARM_Unicorn::InterruptHook(uc_engine* uc, u32 int_no, void* user_data) { - u32 esr{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr)); - - const auto ec = esr >> 26; - const auto iss = esr & 0xFFFFFF; - - auto* const arm_instance = static_cast<ARM_Unicorn*>(user_data); - - switch (ec) { - case 0x15: // SVC - Kernel::Svc::Call(arm_instance->system, iss); - break; - } -} - -} // namespace Core diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h deleted file mode 100644 index 810aff311..000000000 --- a/src/core/arm/unicorn/arm_unicorn.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <unicorn/unicorn.h> -#include "common/common_types.h" -#include "core/arm/arm_interface.h" -#include "core/gdbstub/gdbstub.h" - -namespace Core { - -class System; - -class ARM_Unicorn final : public ARM_Interface { -public: - enum class Arch { - AArch32, // 32-bit ARM - AArch64, // 64-bit ARM - }; - - explicit ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock, - Arch architecture, std::size_t core_index); - ~ARM_Unicorn() override; - - void SetPC(u64 pc) override; - u64 GetPC() const override; - u64 GetReg(int index) const override; - void SetReg(int index, u64 value) override; - u128 GetVectorReg(int index) const override; - void SetVectorReg(int index, u128 value) override; - u32 GetPSTATE() const override; - void SetPSTATE(u32 pstate) override; - VAddr GetTlsAddress() const override; - void SetTlsAddress(VAddr address) override; - void SetTPIDR_EL0(u64 value) override; - u64 GetTPIDR_EL0() const override; - void ChangeProcessorID(std::size_t new_core_id) override; - void PrepareReschedule() override; - void ClearExclusiveState() override; - void ExecuteInstructions(std::size_t num_instructions); - void Run() override; - void Step() override; - void ClearInstructionCache() override; - void PageTableChanged(Common::PageTable&, std::size_t) override {} - void RecordBreak(GDBStub::BreakpointAddress bkpt); - - void SaveContext(ThreadContext32& ctx) override {} - void SaveContext(ThreadContext64& ctx) override; - void LoadContext(const ThreadContext32& ctx) override {} - void LoadContext(const ThreadContext64& ctx) override; - -private: - static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data); - - uc_engine* uc{}; - GDBStub::BreakpointAddress last_bkpt{}; - bool last_bkpt_hit = false; - std::size_t core_index; -}; - -} // namespace Core diff --git a/src/core/core.cpp b/src/core/core.cpp index c2c0eec0b..30f5e1128 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -25,21 +25,20 @@ #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" -#include "core/gdbstub/gdbstub.h" #include "core/hardware_interrupt_manager.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" #include "core/hle/service/am/applets/applets.h" #include "core/hle/service/apm/controller.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/manager.h" -#include "core/hle/service/lm/manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" +#include "core/hle/service/time/time_manager.h" #include "core/loader/loader.h" #include "core/memory.h" #include "core/memory/cheat_engine.h" @@ -91,37 +90,47 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, std::string dir_name; std::string filename; Common::SplitPath(path, &dir_name, &filename, nullptr); + if (filename == "00") { const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read); std::vector<FileSys::VirtualFile> concat; - for (u8 i = 0; i < 0x10; ++i) { - auto next = dir->GetFile(fmt::format("{:02X}", i)); - if (next != nullptr) + + for (u32 i = 0; i < 0x10; ++i) { + const auto file_name = fmt::format("{:02X}", i); + auto next = dir->GetFile(file_name); + + if (next != nullptr) { concat.push_back(std::move(next)); - else { - next = dir->GetFile(fmt::format("{:02x}", i)); - if (next != nullptr) - concat.push_back(std::move(next)); - else + } else { + next = dir->GetFile(file_name); + + if (next == nullptr) { break; + } + + concat.push_back(std::move(next)); } } - if (concat.empty()) + if (concat.empty()) { return nullptr; + } - return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); + return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(std::move(concat), + dir->GetName()); } - if (Common::FS::IsDirectory(path)) - return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); + if (Common::FS::IsDirectory(path)) { + return vfs->OpenFile(path + "/main", FileSys::Mode::Read); + } return vfs->OpenFile(path, FileSys::Mode::Read); } + struct System::Impl { explicit Impl(System& system) : kernel{system}, fs_controller{system}, memory{system}, - cpu_manager{system}, reporter{system}, applet_manager{system} {} + cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} ResultStatus Run() { status = ResultStatus::Success; @@ -144,12 +153,12 @@ struct System::Impl { } ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { - LOG_DEBUG(HW_Memory, "initialized OK"); + LOG_DEBUG(Core, "initialized OK"); device_memory = std::make_unique<Core::DeviceMemory>(); is_multicore = Settings::values.use_multi_core.GetValue(); - is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue(); + is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); kernel.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore); @@ -178,17 +187,18 @@ struct System::Impl { arp_manager.ResetAll(); telemetry_session = std::make_unique<Core::TelemetrySession>(); - service_manager = std::make_shared<Service::SM::ServiceManager>(); - Service::Init(service_manager, system); - GDBStub::DeferStart(); - - interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system); gpu_core = VideoCore::CreateGPU(emu_window, system); if (!gpu_core) { return ResultStatus::ErrorVideoCore; } - gpu_core->Renderer().Rasterizer().SetupDirtyFlags(); + + service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); + services = std::make_unique<Service::Services>(service_manager, system); + interrupt_manager = std::make_unique<Hardware::InterruptManager>(system); + + // Initialize time manager, which must happen after kernel is created + time_manager.Initialize(); is_powered_on = true; exit_lock = false; @@ -203,9 +213,11 @@ struct System::Impl { return ResultStatus::Success; } - ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, - const std::string& filepath) { - app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); + ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index) { + app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath), + program_index); + if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); return ResultStatus::ErrorGetLoader; @@ -219,12 +231,12 @@ struct System::Impl { return init_result; } - telemetry_session->AddInitialInfo(*app_loader); + telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider); auto main_process = Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland); - const auto [load_result, load_parameters] = app_loader->Load(*main_process); + const auto [load_result, load_parameters] = app_loader->Load(*main_process, system); if (load_result != Loader::ResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); + LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result); Shutdown(); return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + @@ -232,6 +244,7 @@ struct System::Impl { } AddGlueRegistrationForProcess(*app_loader, *main_process); kernel.MakeCurrentProcess(main_process.get()); + kernel.InitializeCores(); // Initialize cheat engine if (cheat_engine) { @@ -253,8 +266,7 @@ struct System::Impl { u64 title_id{0}; if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { - LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", - static_cast<u32>(load_result)); + LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", load_result); } perf_stats = std::make_unique<PerfStats>(title_id); // Reset counters and set time origin to current frame @@ -280,8 +292,6 @@ struct System::Impl { perf_stats->GetMeanFrametime()); } - lm_manager.Flush(); - is_powered_on = false; exit_lock = false; @@ -290,19 +300,17 @@ struct System::Impl { } // Shutdown emulation session - GDBStub::Shutdown(); - Service::Shutdown(); + services.reset(); service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); - device_memory.reset(); // Close all CPU/threading state cpu_manager.Shutdown(); // Shutdown kernel and core timing - kernel.Shutdown(); core_timing.Shutdown(); + kernel.Shutdown(); // Close app loader app_loader.reset(); @@ -333,7 +341,7 @@ struct System::Impl { Service::Glue::ApplicationLaunchProperty launch{}; launch.title_id = process.GetTitleID(); - FileSys::PatchManager pm{launch.title_id}; + FileSys::PatchManager pm{launch.title_id, fs_controller, *content_provider}; launch.version = pm.GetGameVersion().value_or(0); // TODO(DarkLordZach): When FSController/Game Card Support is added, if @@ -387,11 +395,14 @@ struct System::Impl { /// Service State Service::Glue::ARPManager arp_manager; - Service::LM::Manager lm_manager{reporter}; + Service::Time::TimeManager time_manager; /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; + /// Services + std::unique_ptr<Service::Services> services; + /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; @@ -407,6 +418,8 @@ struct System::Impl { bool is_multicore{}; bool is_async_gpu{}; + ExecuteProgramCallback execute_program_callback; + std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{}; }; @@ -438,8 +451,17 @@ void System::InvalidateCpuInstructionCaches() { impl->kernel.InvalidateAllInstructionCaches(); } -System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { - return impl->Load(*this, emu_window, filepath); +void System::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { + impl->kernel.InvalidateCpuInstructionCacheRange(addr, size); +} + +void System::Shutdown() { + impl->Shutdown(); +} + +System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index) { + return impl->Load(*this, emu_window, filepath, program_index); } bool System::IsPoweredOn() const { @@ -467,11 +489,11 @@ const TelemetrySession& System::TelemetrySession() const { } ARM_Interface& System::CurrentArmInterface() { - return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface(); + return impl->kernel.CurrentPhysicalCore().ArmInterface(); } const ARM_Interface& System::CurrentArmInterface() const { - return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface(); + return impl->kernel.CurrentPhysicalCore().ArmInterface(); } std::size_t System::CurrentCoreIndex() const { @@ -480,14 +502,6 @@ std::size_t System::CurrentCoreIndex() const { return core; } -Kernel::Scheduler& System::CurrentScheduler() { - return impl->kernel.CurrentScheduler(); -} - -const Kernel::Scheduler& System::CurrentScheduler() const { - return impl->kernel.CurrentScheduler(); -} - Kernel::PhysicalCore& System::CurrentPhysicalCore() { return impl->kernel.CurrentPhysicalCore(); } @@ -496,22 +510,14 @@ const Kernel::PhysicalCore& System::CurrentPhysicalCore() const { return impl->kernel.CurrentPhysicalCore(); } -Kernel::Scheduler& System::Scheduler(std::size_t core_index) { - return impl->kernel.Scheduler(core_index); -} - -const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const { - return impl->kernel.Scheduler(core_index); -} - /// Gets the global scheduler -Kernel::GlobalScheduler& System::GlobalScheduler() { - return impl->kernel.GlobalScheduler(); +Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() { + return impl->kernel.GlobalSchedulerContext(); } /// Gets the global scheduler -const Kernel::GlobalScheduler& System::GlobalScheduler() const { - return impl->kernel.GlobalScheduler(); +const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const { + return impl->kernel.GlobalSchedulerContext(); } Kernel::Process* System::CurrentProcess() { @@ -531,15 +537,11 @@ const Kernel::Process* System::CurrentProcess() const { } ARM_Interface& System::ArmInterface(std::size_t core_index) { - auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread(); - ASSERT(thread && !thread->IsHLEThread()); - return thread->ArmInterface(); + return impl->kernel.PhysicalCore(core_index).ArmInterface(); } const ARM_Interface& System::ArmInterface(std::size_t core_index) const { - auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread(); - ASSERT(thread && !thread->IsHLEThread()); - return thread->ArmInterface(); + return impl->kernel.PhysicalCore(core_index).ArmInterface(); } ExclusiveMonitor& System::Monitor() { @@ -626,15 +628,19 @@ const std::string& System::GetStatusDetails() const { return impl->status_details; } -Loader::AppLoader& System::GetAppLoader() const { +Loader::AppLoader& System::GetAppLoader() { + return *impl->app_loader; +} + +const Loader::AppLoader& System::GetAppLoader() const { return *impl->app_loader; } -void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { +void System::SetFilesystem(FileSys::VirtualFilesystem vfs) { impl->virtual_filesystem = std::move(vfs); } -std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { +FileSys::VirtualFilesystem System::GetFilesystem() const { return impl->virtual_filesystem; } @@ -710,12 +716,12 @@ const Service::APM::Controller& System::GetAPMController() const { return impl->apm_controller; } -Service::LM::Manager& System::GetLogManager() { - return impl->lm_manager; +Service::Time::TimeManager& System::GetTimeManager() { + return impl->time_manager; } -const Service::LM::Manager& System::GetLogManager() const { - return impl->lm_manager; +const Service::Time::TimeManager& System::GetTimeManager() const { + return impl->time_manager; } void System::SetExitLock(bool locked) { @@ -734,14 +740,6 @@ const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const { return impl->build_id; } -System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { - return impl->Init(*this, emu_window); -} - -void System::Shutdown() { - impl->Shutdown(); -} - Service::SM::ServiceManager& System::ServiceManager() { return *impl->service_manager; } @@ -772,4 +770,16 @@ bool System::IsMulticore() const { return impl->is_multicore; } +void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { + impl->execute_program_callback = std::move(callback); +} + +void System::ExecuteProgram(std::size_t program_index) { + if (impl->execute_program_callback) { + impl->execute_program_callback(program_index); + } else { + LOG_CRITICAL(Core, "execute_program_callback must be initialized by the frontend"); + } +} + } // namespace Core diff --git a/src/core/core.h b/src/core/core.h index 5c6cfbffe..3a8e040c1 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -5,6 +5,7 @@ #pragma once #include <cstddef> +#include <functional> #include <memory> #include <string> #include <vector> @@ -25,11 +26,11 @@ class VfsFilesystem; } // namespace FileSys namespace Kernel { -class GlobalScheduler; +class GlobalSchedulerContext; class KernelCore; class PhysicalCore; class Process; -class Scheduler; +class KScheduler; } // namespace Kernel namespace Loader { @@ -61,14 +62,14 @@ namespace Glue { class ARPManager; } -namespace LM { -class Manager; -} // namespace LM - namespace SM { class ServiceManager; } // namespace SM +namespace Time { +class TimeManager; +} // namespace Time + } // namespace Service namespace Tegra { @@ -120,7 +121,7 @@ public: * Gets the instance of the System singleton class. * @returns Reference to the instance of the System singleton class. */ - static System& GetInstance() { + [[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() { return s_instance; } @@ -140,19 +141,19 @@ public: * Run the OS and Application * This function will start emulation and run the relevant devices */ - ResultStatus Run(); + [[nodiscard]] ResultStatus Run(); /** * Pause the OS and Application * This function will pause emulation and stop the relevant devices */ - ResultStatus Pause(); + [[nodiscard]] ResultStatus Pause(); /** * Step the CPU one instruction * @return Result status, indicating whether or not the operation succeeded. */ - ResultStatus SingleStep(); + [[nodiscard]] ResultStatus SingleStep(); /** * Invalidate the CPU instruction caches @@ -161,6 +162,8 @@ public: */ void InvalidateCpuInstructionCaches(); + void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); + /// Shutdown the emulated system. void Shutdown(); @@ -169,22 +172,24 @@ public: * @param emu_window Reference to the host-system window used for video output and keyboard * input. * @param filepath String path to the executable application to load on the host file system. + * @param program_index Specifies the index within the container of the program to launch. * @returns ResultStatus code, indicating if the operation succeeded. */ - ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath); + [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index = 0); /** * Indicates if the emulated system is powered on (all subsystems initialized and able to run an * application). * @returns True if the emulated system is powered on, otherwise false. */ - bool IsPoweredOn() const; + [[nodiscard]] bool IsPoweredOn() const; /// Gets a reference to the telemetry session for this emulation session. - Core::TelemetrySession& TelemetrySession(); + [[nodiscard]] Core::TelemetrySession& TelemetrySession(); /// Gets a reference to the telemetry session for this emulation session. - const Core::TelemetrySession& TelemetrySession() const; + [[nodiscard]] const Core::TelemetrySession& TelemetrySession() const; /// Prepare the core emulation for a reschedule void PrepareReschedule(); @@ -193,181 +198,163 @@ public: void PrepareReschedule(u32 core_index); /// Gets and resets core performance statistics - PerfStatsResults GetAndResetPerfStats(); + [[nodiscard]] PerfStatsResults GetAndResetPerfStats(); /// Gets an ARM interface to the CPU core that is currently running - ARM_Interface& CurrentArmInterface(); + [[nodiscard]] ARM_Interface& CurrentArmInterface(); /// Gets an ARM interface to the CPU core that is currently running - const ARM_Interface& CurrentArmInterface() const; + [[nodiscard]] const ARM_Interface& CurrentArmInterface() const; /// Gets the index of the currently running CPU core - std::size_t CurrentCoreIndex() const; - - /// Gets the scheduler for the CPU core that is currently running - Kernel::Scheduler& CurrentScheduler(); - - /// Gets the scheduler for the CPU core that is currently running - const Kernel::Scheduler& CurrentScheduler() const; + [[nodiscard]] std::size_t CurrentCoreIndex() const; /// Gets the physical core for the CPU core that is currently running - Kernel::PhysicalCore& CurrentPhysicalCore(); + [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore(); /// Gets the physical core for the CPU core that is currently running - const Kernel::PhysicalCore& CurrentPhysicalCore() const; + [[nodiscard]] const Kernel::PhysicalCore& CurrentPhysicalCore() const; /// Gets a reference to an ARM interface for the CPU core with the specified index - ARM_Interface& ArmInterface(std::size_t core_index); + [[nodiscard]] ARM_Interface& ArmInterface(std::size_t core_index); /// Gets a const reference to an ARM interface from the CPU core with the specified index - const ARM_Interface& ArmInterface(std::size_t core_index) const; + [[nodiscard]] const ARM_Interface& ArmInterface(std::size_t core_index) const; - CpuManager& GetCpuManager(); + /// Gets a reference to the underlying CPU manager. + [[nodiscard]] CpuManager& GetCpuManager(); - const CpuManager& GetCpuManager() const; + /// Gets a const reference to the underlying CPU manager + [[nodiscard]] const CpuManager& GetCpuManager() const; /// Gets a reference to the exclusive monitor - ExclusiveMonitor& Monitor(); + [[nodiscard]] ExclusiveMonitor& Monitor(); /// Gets a constant reference to the exclusive monitor - const ExclusiveMonitor& Monitor() const; + [[nodiscard]] const ExclusiveMonitor& Monitor() const; /// Gets a mutable reference to the system memory instance. - Core::Memory::Memory& Memory(); + [[nodiscard]] Core::Memory::Memory& Memory(); /// Gets a constant reference to the system memory instance. - const Core::Memory::Memory& Memory() const; + [[nodiscard]] const Core::Memory::Memory& Memory() const; /// Gets a mutable reference to the GPU interface - Tegra::GPU& GPU(); + [[nodiscard]] Tegra::GPU& GPU(); /// Gets an immutable reference to the GPU interface. - const Tegra::GPU& GPU() const; + [[nodiscard]] const Tegra::GPU& GPU() const; /// Gets a mutable reference to the renderer. - VideoCore::RendererBase& Renderer(); + [[nodiscard]] VideoCore::RendererBase& Renderer(); /// Gets an immutable reference to the renderer. - const VideoCore::RendererBase& Renderer() const; - - /// Gets the scheduler for the CPU core with the specified index - Kernel::Scheduler& Scheduler(std::size_t core_index); - - /// Gets the scheduler for the CPU core with the specified index - const Kernel::Scheduler& Scheduler(std::size_t core_index) const; + [[nodiscard]] const VideoCore::RendererBase& Renderer() const; /// Gets the global scheduler - Kernel::GlobalScheduler& GlobalScheduler(); + [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); /// Gets the global scheduler - const Kernel::GlobalScheduler& GlobalScheduler() const; + [[nodiscard]] const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const; /// Gets the manager for the guest device memory - Core::DeviceMemory& DeviceMemory(); + [[nodiscard]] Core::DeviceMemory& DeviceMemory(); /// Gets the manager for the guest device memory - const Core::DeviceMemory& DeviceMemory() const; + [[nodiscard]] const Core::DeviceMemory& DeviceMemory() const; /// Provides a pointer to the current process - Kernel::Process* CurrentProcess(); + [[nodiscard]] Kernel::Process* CurrentProcess(); /// Provides a constant pointer to the current process. - const Kernel::Process* CurrentProcess() const; + [[nodiscard]] const Kernel::Process* CurrentProcess() const; /// Provides a reference to the core timing instance. - Timing::CoreTiming& CoreTiming(); + [[nodiscard]] Timing::CoreTiming& CoreTiming(); /// Provides a constant reference to the core timing instance. - const Timing::CoreTiming& CoreTiming() const; + [[nodiscard]] const Timing::CoreTiming& CoreTiming() const; /// Provides a reference to the interrupt manager instance. - Core::Hardware::InterruptManager& InterruptManager(); + [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager(); /// Provides a constant reference to the interrupt manager instance. - const Core::Hardware::InterruptManager& InterruptManager() const; + [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const; /// Provides a reference to the kernel instance. - Kernel::KernelCore& Kernel(); + [[nodiscard]] Kernel::KernelCore& Kernel(); /// Provides a constant reference to the kernel instance. - const Kernel::KernelCore& Kernel() const; + [[nodiscard]] const Kernel::KernelCore& Kernel() const; /// Provides a reference to the internal PerfStats instance. - Core::PerfStats& GetPerfStats(); + [[nodiscard]] Core::PerfStats& GetPerfStats(); /// Provides a constant reference to the internal PerfStats instance. - const Core::PerfStats& GetPerfStats() const; + [[nodiscard]] const Core::PerfStats& GetPerfStats() const; /// Provides a reference to the frame limiter; - Core::FrameLimiter& FrameLimiter(); + [[nodiscard]] Core::FrameLimiter& FrameLimiter(); /// Provides a constant referent to the frame limiter - const Core::FrameLimiter& FrameLimiter() const; + [[nodiscard]] const Core::FrameLimiter& FrameLimiter() const; /// Gets the name of the current game - Loader::ResultStatus GetGameName(std::string& out) const; + [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; void SetStatus(ResultStatus new_status, const char* details); - const std::string& GetStatusDetails() const; + [[nodiscard]] const std::string& GetStatusDetails() const; - Loader::AppLoader& GetAppLoader() const; + [[nodiscard]] Loader::AppLoader& GetAppLoader(); + [[nodiscard]] const Loader::AppLoader& GetAppLoader() const; - Service::SM::ServiceManager& ServiceManager(); - const Service::SM::ServiceManager& ServiceManager() const; + [[nodiscard]] Service::SM::ServiceManager& ServiceManager(); + [[nodiscard]] const Service::SM::ServiceManager& ServiceManager() const; - void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs); + void SetFilesystem(FileSys::VirtualFilesystem vfs); - std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + [[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const; void RegisterCheatList(const std::vector<Memory::CheatEntry>& list, const std::array<u8, 0x20>& build_id, VAddr main_region_begin, u64 main_region_size); void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); - void SetDefaultAppletFrontendSet(); - Service::AM::Applets::AppletManager& GetAppletManager(); - - const Service::AM::Applets::AppletManager& GetAppletManager() const; + [[nodiscard]] Service::AM::Applets::AppletManager& GetAppletManager(); + [[nodiscard]] const Service::AM::Applets::AppletManager& GetAppletManager() const; void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider); - FileSys::ContentProvider& GetContentProvider(); - - const FileSys::ContentProvider& GetContentProvider() const; + [[nodiscard]] FileSys::ContentProvider& GetContentProvider(); + [[nodiscard]] const FileSys::ContentProvider& GetContentProvider() const; - Service::FileSystem::FileSystemController& GetFileSystemController(); - - const Service::FileSystem::FileSystemController& GetFileSystemController() const; + [[nodiscard]] Service::FileSystem::FileSystemController& GetFileSystemController(); + [[nodiscard]] const Service::FileSystem::FileSystemController& GetFileSystemController() const; void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, FileSys::ContentProvider* provider); void ClearContentProvider(FileSys::ContentProviderUnionSlot slot); - const Reporter& GetReporter() const; - - Service::Glue::ARPManager& GetARPManager(); - - const Service::Glue::ARPManager& GetARPManager() const; + [[nodiscard]] const Reporter& GetReporter() const; - Service::APM::Controller& GetAPMController(); + [[nodiscard]] Service::Glue::ARPManager& GetARPManager(); + [[nodiscard]] const Service::Glue::ARPManager& GetARPManager() const; - const Service::APM::Controller& GetAPMController() const; + [[nodiscard]] Service::APM::Controller& GetAPMController(); + [[nodiscard]] const Service::APM::Controller& GetAPMController() const; - Service::LM::Manager& GetLogManager(); - - const Service::LM::Manager& GetLogManager() const; + [[nodiscard]] Service::Time::TimeManager& GetTimeManager(); + [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const; void SetExitLock(bool locked); - - bool GetExitLock() const; + [[nodiscard]] bool GetExitLock() const; void SetCurrentProcessBuildID(const CurrentBuildProcessID& id); - - const CurrentBuildProcessID& GetCurrentProcessBuildID() const; + [[nodiscard]] const CurrentBuildProcessID& GetCurrentProcessBuildID() const; /// Register a host thread as an emulated CPU Core. void RegisterCoreThread(std::size_t id); @@ -382,18 +369,27 @@ public: void ExitDynarmicProfile(); /// Tells if system is running on multicore. - bool IsMulticore() const; + [[nodiscard]] bool IsMulticore() const; -private: - System(); + /// Type used for the frontend to designate a callback for System to re-launch the application + /// using a specified program index. + using ExecuteProgramCallback = std::function<void(std::size_t)>; /** - * Initialize the emulated system. - * @param emu_window Reference to the host-system window used for video output and keyboard - * input. - * @return ResultStatus code, indicating if the operation succeeded. + * Registers a callback from the frontend for System to re-launch the application using a + * specified program index. + * @param callback Callback from the frontend to relaunch the application. */ - ResultStatus Init(Frontend::EmuWindow& emu_window); + void RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback); + + /** + * Instructs the frontend to re-launch the application using the specified program_index. + * @param program_index Specifies the index within the application of the program to launch. + */ + void ExecuteProgram(std::size_t program_index); + +private: + System(); struct Impl; std::unique_ptr<Impl> impl; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index e6c8461a5..874b5673a 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -49,6 +49,7 @@ void CoreTiming::ThreadEntry(CoreTiming& instance) { Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); instance.on_thread_init(); instance.ThreadLoop(); + MicroProfileOnThreadExit(); } void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) { diff --git a/src/core/core_timing.h b/src/core/core_timing.h index b0b6036e4..77ff4c6fe 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -27,8 +27,8 @@ using TimedCallback = /// Contains the characteristics of a particular event. struct EventType { - EventType(TimedCallback&& callback, std::string&& name) - : callback{std::move(callback)}, name{std::move(name)} {} + explicit EventType(TimedCallback&& callback_, std::string&& name_) + : callback{std::move(callback_)}, name{std::move(name_)} {} /// The event's callback function. TimedCallback callback; @@ -67,8 +67,8 @@ public: void Shutdown(); /// Sets if emulation is multicore or single core, must be set before Initialize - void SetMulticore(bool is_multicore) { - this->is_multicore = is_multicore; + void SetMulticore(bool is_multicore_) { + is_multicore = is_multicore_; } /// Check if it's using host timing. diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index ef0bae556..8f04fb8f5 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -4,16 +4,16 @@ #include "common/fiber.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "common/thread.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" -#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" #include "video_core/gpu.h" namespace Core { @@ -109,28 +109,26 @@ void* CpuManager::GetStartFuncParamater() { void CpuManager::MultiCoreRunGuestThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + auto& host_context = thread->GetHostContext(); + host_context->SetRewindPoint(GuestRewindFunction, this); MultiCoreRunGuestLoop(); } void CpuManager::MultiCoreRunGuestLoop() { auto& kernel = system.Kernel(); - auto* thread = kernel.CurrentScheduler().GetCurrentThread(); + while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - auto& arm_interface = thread->ArmInterface(); system.EnterDynarmicProfile(); while (!physical_core->IsInterrupted()) { - arm_interface.Run(); + physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } system.ExitDynarmicProfile(); - arm_interface.ClearExclusiveState(); - auto& scheduler = kernel.CurrentScheduler(); - scheduler.TryDoContextSwitch(); + physical_core->ArmInterface().ClearExclusiveState(); + kernel.CurrentScheduler()->RescheduleCurrentCore(); } } @@ -139,25 +137,21 @@ void CpuManager::MultiCoreRunIdleThread() { while (true) { auto& physical_core = kernel.CurrentPhysicalCore(); physical_core.Idle(); - auto& scheduler = kernel.CurrentScheduler(); - scheduler.TryDoContextSwitch(); + kernel.CurrentScheduler()->RescheduleCurrentCore(); } } void CpuManager::MultiCoreRunSuspendThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); while (true) { auto core = kernel.GetCurrentHostThreadID(); - auto& scheduler = kernel.CurrentScheduler(); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + auto& scheduler = *kernel.CurrentScheduler(); + Kernel::KThread* current_thread = scheduler.GetCurrentThread(); Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context); ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.GetCurrentHostThreadID()); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } @@ -205,32 +199,30 @@ void CpuManager::MultiCorePause(bool paused) { void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + auto& host_context = thread->GetHostContext(); + host_context->SetRewindPoint(GuestRewindFunction, this); SingleCoreRunGuestLoop(); } void CpuManager::SingleCoreRunGuestLoop() { auto& kernel = system.Kernel(); - auto* thread = kernel.CurrentScheduler().GetCurrentThread(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - auto& arm_interface = thread->ArmInterface(); system.EnterDynarmicProfile(); if (!physical_core->IsInterrupted()) { - arm_interface.Run(); + physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } system.ExitDynarmicProfile(); - thread->SetPhantomMode(true); + kernel.SetIsPhantomModeForSingleCore(true); system.CoreTiming().Advance(); - thread->SetPhantomMode(false); - arm_interface.ClearExclusiveState(); + kernel.SetIsPhantomModeForSingleCore(false); + physical_core->ArmInterface().ClearExclusiveState(); PreemptSingleCore(); auto& scheduler = kernel.Scheduler(current_core); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } @@ -242,51 +234,53 @@ void CpuManager::SingleCoreRunIdleThread() { system.CoreTiming().AddTicks(1000U); idle_count++; auto& scheduler = physical_core.Scheduler(); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } void CpuManager::SingleCoreRunSuspendThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); while (true) { auto core = kernel.GetCurrentHostThreadID(); - auto& scheduler = kernel.CurrentScheduler(); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + auto& scheduler = *kernel.CurrentScheduler(); + Kernel::KThread* current_thread = scheduler.GetCurrentThread(); Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context); ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.GetCurrentHostThreadID()); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } void CpuManager::PreemptSingleCore(bool from_running_enviroment) { - std::size_t old_core = current_core; - auto& scheduler = system.Kernel().Scheduler(old_core); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); - if (idle_count >= 4 || from_running_enviroment) { - if (!from_running_enviroment) { - system.CoreTiming().Idle(); - idle_count = 0; + { + auto& kernel = system.Kernel(); + auto& scheduler = kernel.Scheduler(current_core); + Kernel::KThread* current_thread = scheduler.GetCurrentThread(); + if (idle_count >= 4 || from_running_enviroment) { + if (!from_running_enviroment) { + system.CoreTiming().Idle(); + idle_count = 0; + } + kernel.SetIsPhantomModeForSingleCore(true); + system.CoreTiming().Advance(); + kernel.SetIsPhantomModeForSingleCore(false); } - current_thread->SetPhantomMode(true); - system.CoreTiming().Advance(); - current_thread->SetPhantomMode(false); + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); + system.CoreTiming().ResetTicks(); + scheduler.Unload(scheduler.GetCurrentThread()); + + auto& next_scheduler = kernel.Scheduler(current_core); + Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext()); } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); - system.CoreTiming().ResetTicks(); - scheduler.Unload(); - auto& next_scheduler = system.Kernel().Scheduler(current_core); - Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext()); - /// May have changed scheduler - auto& current_scheduler = system.Kernel().Scheduler(current_core); - current_scheduler.Reload(); - auto* currrent_thread2 = current_scheduler.GetCurrentThread(); - if (!currrent_thread2->IsIdleThread()) { - idle_count = 0; + + // May have changed scheduler + { + auto& scheduler = system.Kernel().Scheduler(current_core); + scheduler.Reload(scheduler.GetCurrentThread()); + if (!scheduler.IsIdle()) { + idle_count = 0; + } } } @@ -328,7 +322,7 @@ void CpuManager::RunThread(std::size_t core) { system.RegisterCoreThread(core); std::string name; if (is_multicore) { - name = "yuzu:CoreCPUThread_" + std::to_string(core); + name = "yuzu:CPUCore_" + std::to_string(core); } else { name = "yuzu:CPUThread"; } @@ -343,6 +337,16 @@ void CpuManager::RunThread(std::size_t core) { data.initialized = true; const bool sc_sync = !is_async_gpu && !is_multicore; bool sc_sync_first_use = sc_sync; + + // Cleanup + SCOPE_EXIT({ + data.host_context->Exit(); + data.enter_barrier.reset(); + data.exit_barrier.reset(); + data.initialized = false; + MicroProfileOnThreadExit(); + }); + /// Running while (running_mode) { data.is_running = false; @@ -351,8 +355,13 @@ void CpuManager::RunThread(std::size_t core) { system.GPU().ObtainContext(); sc_sync_first_use = false; } - auto& scheduler = system.Kernel().CurrentScheduler(); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + + // Abort if emulation was killed before the session really starts + if (!system.IsPoweredOn()) { + return; + } + + auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); data.is_running = true; Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext()); data.is_running = false; @@ -360,11 +369,6 @@ void CpuManager::RunThread(std::size_t core) { data.exit_barrier->Wait(); data.is_paused = false; } - /// Time to cleanup - data.host_context->Exit(); - data.enter_barrier.reset(); - data.exit_barrier.reset(); - data.initialized = false; } } // namespace Core diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index dc591c730..ad116dcc0 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -23,7 +23,6 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "core/core.h" #include "core/crypto/aes_util.h" #include "core/crypto/key_manager.h" #include "core/crypto/partition_data_manager.h" @@ -144,6 +143,7 @@ u64 GetSignatureTypeDataSize(SignatureType type) { return 0x3C; } UNREACHABLE(); + return 0; } u64 GetSignatureTypePaddingSize(SignatureType type) { @@ -158,6 +158,7 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { return 0x40; } UNREACHABLE(); + return 0; } SignatureType Ticket::GetSignatureType() const { @@ -170,8 +171,7 @@ SignatureType Ticket::GetSignatureType() const { if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->sig_type; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } TicketData& Ticket::GetData() { @@ -184,8 +184,7 @@ TicketData& Ticket::GetData() { if (auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->data; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } const TicketData& Ticket::GetData() const { @@ -198,8 +197,7 @@ const TicketData& Ticket::GetData() const { if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->data; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } u64 Ticket::GetSize() const { @@ -412,7 +410,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke // Combine sources and seed for (auto& source : sd_key_sources) { for (std::size_t i = 0; i < source.size(); ++i) { - source[i] ^= sd_seed[i & 0xF]; + source[i] = static_cast<u8>(source[i] ^ sd_seed[i & 0xF]); } } @@ -570,6 +568,11 @@ KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); + + if (!Common::FS::Exists(yuzu_keys_dir)) { + Common::FS::CreateDir(yuzu_keys_dir); + } + if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); @@ -1022,10 +1025,10 @@ void KeyManager::DeriveBase() { } } -void KeyManager::DeriveETicket(PartitionDataManager& data) { +void KeyManager::DeriveETicket(PartitionDataManager& data, + const FileSys::ContentProvider& provider) { // ETicket keys - const auto es = Core::System::GetInstance().GetContentProvider().GetEntry( - 0x0100000000000033, FileSys::ContentRecordType::Program); + const auto es = provider.GetEntry(0x0100000000000033, FileSys::ContentRecordType::Program); if (es == nullptr) { return; diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 321b75323..0a7220286 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -20,6 +20,10 @@ namespace Common::FS { class IOFile; } +namespace FileSys { +class ContentProvider; +} + namespace Loader { enum class ResultStatus : u16; } @@ -252,7 +256,7 @@ public: bool BaseDeriveNecessary() const; void DeriveBase(); - void DeriveETicket(PartitionDataManager& data); + void DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider); void PopulateTickets(); void SynthesizeTickets(); diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index 46136d04a..5f1c86a09 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -26,6 +26,7 @@ #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_offset.h" #include "core/file_sys/vfs_vector.h" +#include "core/loader/loader.h" using Common::AsArray; diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 9ffda2e14..7c6304ff0 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -4,11 +4,10 @@ #include <fmt/format.h> #include "common/file_util.h" -#include "core/core.h" #include "core/file_sys/bis_factory.h" #include "core/file_sys/mode.h" #include "core/file_sys/registered_cache.h" -#include "core/settings.h" +#include "core/file_sys/vfs.h" namespace FileSys { @@ -82,11 +81,11 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const { } } -VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { +VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id, + VirtualFilesystem file_system) const { auto& keys = Core::Crypto::KeyManager::Instance(); - Core::Crypto::PartitionDataManager pdm{ - Core::System::GetInstance().GetFilesystem()->OpenDirectory( - Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)}; + Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( + Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)}; keys.PopulateFromPartitionData(pdm); switch (id) { diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index 8f0451c98..136485881 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -6,7 +6,8 @@ #include <memory> -#include "core/file_sys/vfs.h" +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" namespace FileSys { @@ -51,7 +52,7 @@ public: VirtualDir GetModificationDumpRoot(u64 title_id) const; VirtualDir OpenPartition(BisPartitionId id) const; - VirtualFile OpenPartitionStorage(BisPartitionId id) const; + VirtualFile OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const; VirtualDir GetImageDirectory() const; diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 664a47e7f..8dee5590b 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -8,11 +8,11 @@ #include <fmt/ostream.h> #include "common/logging/log.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" -#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_offset.h" @@ -29,9 +29,10 @@ constexpr std::array partition_names{ "logo", }; -XCI::XCI(VirtualFile file_) +XCI::XCI(VirtualFile file_, std::size_t program_index) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, - partitions(partition_names.size()), partitions_raw(partition_names.size()) { + partitions(partition_names.size()), + partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { if (file->ReadObject(&header) != sizeof(GamecardHeader)) { status = Loader::ResultStatus::ErrorBadXCIHeader; return; @@ -61,7 +62,8 @@ XCI::XCI(VirtualFile file_) } secure_partition = std::make_shared<NSP>( - main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); + main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]), + program_index); ncas = secure_partition->GetNCAsCollapsed(); program = diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index e1b136426..4960e90fe 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -9,9 +9,12 @@ #include <vector> #include "common/common_types.h" #include "common/swap.h" -#include "core/crypto/key_manager.h" #include "core/file_sys/vfs.h" +namespace Core::Crypto { +class KeyManager; +} + namespace Loader { enum class ResultStatus : u16; } @@ -75,7 +78,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; class XCI : public ReadOnlyVfsDirectory { public: - explicit XCI(VirtualFile file); + explicit XCI(VirtualFile file, std::size_t program_index = 0); ~XCI() override; Loader::ResultStatus GetStatus() const; @@ -140,6 +143,6 @@ private: u64 update_normal_partition_end; - Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + Core::Crypto::KeyManager& keys; }; } // namespace FileSys diff --git a/src/core/file_sys/common_funcs.h b/src/core/file_sys/common_funcs.h new file mode 100644 index 000000000..7ed97aa50 --- /dev/null +++ b/src/core/file_sys/common_funcs.h @@ -0,0 +1,56 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace FileSys { + +constexpr u64 AOC_TITLE_ID_MASK = 0x7FF; +constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000; +constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; + +/** + * Gets the base title ID from a given title ID. + * + * @param title_id The title ID. + * @returns The base title ID. + */ +[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) { + return title_id & BASE_TITLE_ID_MASK; +} + +/** + * Gets the base title ID with a program index offset from a given title ID. + * + * @param title_id The title ID. + * @param program_index The program index. + * @returns The base title ID with a program index offset. + */ +[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) { + return GetBaseTitleID(title_id) + program_index; +} + +/** + * Gets the AOC (Add-On Content) base title ID from a given title ID. + * + * @param title_id The title ID. + * @returns The AOC base title ID. + */ +[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) { + return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET; +} + +/** + * Gets the AOC (Add-On Content) ID from a given AOC title ID. + * + * @param aoc_title_id The AOC title ID. + * @returns The AOC ID. + */ +[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) { + return aoc_title_id & AOC_TITLE_ID_MASK; +} + +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 5039341c7..d12218fc2 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -10,10 +10,10 @@ #include "common/logging/log.h" #include "core/crypto/aes_util.h" #include "core/crypto/ctr_encryption_layer.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_patch.h" #include "core/file_sys/partition_filesystem.h" -#include "core/file_sys/romfs.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" @@ -43,17 +43,17 @@ static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); struct IVFCHeader { u32_le magic; u32_le magic_number; - INSERT_UNION_PADDING_BYTES(8); + INSERT_PADDING_BYTES_NOINIT(8); std::array<IVFCLevel, 6> levels; - INSERT_UNION_PADDING_BYTES(64); + INSERT_PADDING_BYTES_NOINIT(64); }; static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); struct NCASectionHeaderBlock { - INSERT_UNION_PADDING_BYTES(3); + INSERT_PADDING_BYTES_NOINIT(3); NCASectionFilesystemType filesystem_type; NCASectionCryptoType crypto_type; - INSERT_UNION_PADDING_BYTES(3); + INSERT_PADDING_BYTES_NOINIT(3); }; static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); @@ -61,7 +61,7 @@ struct NCASectionRaw { NCASectionHeaderBlock header; std::array<u8, 0x138> block_data; std::array<u8, 0x8> section_ctr; - INSERT_UNION_PADDING_BYTES(0xB8); + INSERT_PADDING_BYTES_NOINIT(0xB8); }; static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); @@ -69,19 +69,19 @@ struct PFS0Superblock { NCASectionHeaderBlock header_block; std::array<u8, 0x20> hash; u32_le size; - INSERT_UNION_PADDING_BYTES(4); + INSERT_PADDING_BYTES_NOINIT(4); u64_le hash_table_offset; u64_le hash_table_size; u64_le pfs0_header_offset; u64_le pfs0_size; - INSERT_UNION_PADDING_BYTES(0x1B0); + INSERT_PADDING_BYTES_NOINIT(0x1B0); }; static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); struct RomFSSuperblock { NCASectionHeaderBlock header_block; IVFCHeader ivfc; - INSERT_UNION_PADDING_BYTES(0x118); + INSERT_PADDING_BYTES_NOINIT(0x118); }; static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); @@ -89,19 +89,19 @@ struct BKTRHeader { u64_le offset; u64_le size; u32_le magic; - INSERT_UNION_PADDING_BYTES(0x4); + INSERT_PADDING_BYTES_NOINIT(0x4); u32_le number_entries; - INSERT_UNION_PADDING_BYTES(0x4); + INSERT_PADDING_BYTES_NOINIT(0x4); }; static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); struct BKTRSuperblock { NCASectionHeaderBlock header_block; IVFCHeader ivfc; - INSERT_UNION_PADDING_BYTES(0x18); + INSERT_PADDING_BYTES_NOINIT(0x18); BKTRHeader relocation; BKTRHeader subsection; - INSERT_UNION_PADDING_BYTES(0xC0); + INSERT_PADDING_BYTES_NOINIT(0xC0); }; static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); @@ -119,7 +119,8 @@ static bool IsValidNCA(const NCAHeader& header) { } NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) - : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) { + : file(std::move(file_)), + bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { if (file == nullptr) { status = Loader::ResultStatus::ErrorNullFile; return; @@ -322,7 +323,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); subsection_buckets.back().entries.push_back({size, {0}, 0}); - std::optional<Core::Crypto::Key128> key = {}; + std::optional<Core::Crypto::Key128> key; if (encrypted) { if (has_rights_id) { status = Loader::ResultStatus::Success; @@ -409,8 +410,9 @@ u8 NCA::GetCryptoRevision() const { std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { const auto master_key_id = GetCryptoRevision(); - if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) - return {}; + if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { + return std::nullopt; + } std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( @@ -419,15 +421,17 @@ std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); Core::Crypto::Key128 out; - if (type == NCASectionCryptoType::XTS) + if (type == NCASectionCryptoType::XTS) { std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); - else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) + } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); - else + } else { LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", - static_cast<u8>(type)); + type); + } + u128 out_128{}; - memcpy(out_128.data(), out.data(), 16); + std::memcpy(out_128.data(), out.data(), sizeof(u128)); LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", master_key_id, header.key_index, out_128[1], out_128[0]); @@ -441,18 +445,18 @@ std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { memcpy(rights_id.data(), header.rights_id.data(), 16); if (rights_id == u128{}) { status = Loader::ResultStatus::ErrorInvalidRightsID; - return {}; + return std::nullopt; } auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); if (titlekey == Core::Crypto::Key128{}) { status = Loader::ResultStatus::ErrorMissingTitlekey; - return {}; + return std::nullopt; } if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { status = Loader::ResultStatus::ErrorMissingTitlekek; - return {}; + return std::nullopt; } Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( @@ -476,7 +480,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s case NCASectionCryptoType::BKTR: LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { - std::optional<Core::Crypto::Key128> key = {}; + std::optional<Core::Crypto::Key128> key; if (has_rights_id) { status = Loader::ResultStatus::Success; key = GetTitlekey(); @@ -506,7 +510,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs default: LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", - static_cast<u8>(s_header.raw.header.crypto_type)); + s_header.raw.header.crypto_type); return nullptr; } } @@ -515,15 +519,17 @@ Loader::ResultStatus NCA::GetStatus() const { return status; } -std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const { - if (status != Loader::ResultStatus::Success) +std::vector<VirtualFile> NCA::GetFiles() const { + if (status != Loader::ResultStatus::Success) { return {}; + } return files; } -std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const { - if (status != Loader::ResultStatus::Success) +std::vector<VirtualDir> NCA::GetSubdirectories() const { + if (status != Loader::ResultStatus::Success) { return {}; + } return dirs; } @@ -531,7 +537,7 @@ std::string NCA::GetName() const { return file->GetName(); } -std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const { +VirtualDir NCA::GetParentDirectory() const { return file->GetContainingDirectory(); } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index d25cbcf91..e9eccdea3 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -82,7 +82,7 @@ struct NCAHeader { }; static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); -inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { +inline bool IsDirectoryExeFS(const VirtualDir& pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } @@ -104,10 +104,10 @@ public: Loader::ResultStatus GetStatus() const; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualFile> GetFiles() const override; + std::vector<VirtualDir> GetSubdirectories() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; NCAContentType GetType() const; u64 GetTitleId() const; @@ -158,7 +158,7 @@ private: bool encrypted = false; bool is_update = false; - Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + Core::Crypto::KeyManager& keys; }; } // namespace FileSys diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 63cd2eead..b0a130345 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -5,6 +5,7 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/file_sys/control_metadata.h" +#include "core/file_sys/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index e37b2fadf..403c4219a 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -10,7 +10,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" namespace FileSys { @@ -83,7 +83,7 @@ enum class Language : u8 { Italian = 7, Dutch = 8, CanadianFrench = 9, - Portugese = 10, + Portuguese = 10, Russian = 11, Korean = 12, Taiwanese = 13, diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 2aff2708a..c52fafb6f 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -266,8 +266,9 @@ std::multimap<u64, VirtualFile> RomFSBuildContext::Build() { cur_file->offset = file_partition_size; file_partition_size += cur_file->size; cur_file->entry_offset = entry_offset; - entry_offset += sizeof(RomFSFileEntry) + - Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4); + entry_offset += + static_cast<u32>(sizeof(RomFSFileEntry) + + Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4)); prev_file = cur_file; } // Assign deferred parent/sibling ownership. @@ -284,8 +285,9 @@ std::multimap<u64, VirtualFile> RomFSBuildContext::Build() { for (const auto& it : directories) { cur_dir = it.second; cur_dir->entry_offset = entry_offset; - entry_offset += sizeof(RomFSDirectoryEntry) + - Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4); + entry_offset += + static_cast<u32>(sizeof(RomFSDirectoryEntry) + + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4)); } // Assign deferred parent/sibling ownership. for (auto it = directories.rbegin(); it->second != root; ++it) { diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index a08a70efd..a6101f1c0 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -245,9 +245,11 @@ void IPSwitchCompiler::Parse() { // Read rest of patch while (true) { - if (i + 1 >= lines.size()) + if (i + 1 >= lines.size()) { break; - const auto patch_line = lines[++i]; + } + + const auto& patch_line = lines[++i]; // Start of new patch if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { @@ -297,7 +299,7 @@ void IPSwitchCompiler::Parse() { patch_text->GetName(), offset, Common::HexToString(replace)); } - patch.records.insert_or_assign(offset, std::move(replace)); + patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace)); } patches.push_back(std::move(patch)); diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp index 76313679d..ef93ef3ed 100644 --- a/src/core/file_sys/kernel_executable.cpp +++ b/src/core/file_sys/kernel_executable.cpp @@ -2,9 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cstring> + #include "common/string_util.h" #include "core/file_sys/kernel_executable.h" #include "core/file_sys/vfs_offset.h" +#include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h index 324a57384..044c554d3 100644 --- a/src/core/file_sys/kernel_executable.h +++ b/src/core/file_sys/kernel_executable.h @@ -4,10 +4,17 @@ #pragma once +#include <array> +#include <vector> + #include "common/common_funcs.h" +#include "common/common_types.h" #include "common/swap.h" #include "core/file_sys/vfs_types.h" -#include "core/loader/loader.h" + +namespace Loader { +enum class ResultStatus : u16; +} namespace FileSys { diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 93d0df6b9..3596541b2 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/vfs.h" namespace FileSys { @@ -107,7 +108,7 @@ std::vector<u8> CNMT::Serialize() const { memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader)); } - auto offset = header.table_offset; + u64_le offset = header.table_offset; for (const auto& rec : content_records) { memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 1f82fff0a..53535e5f5 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -10,7 +10,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" namespace FileSys { class CNMT; diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp index fe7375e84..a65ec6798 100644 --- a/src/core/file_sys/nca_patch.cpp +++ b/src/core/file_sys/nca_patch.cpp @@ -12,6 +12,49 @@ #include "core/file_sys/nca_patch.h" namespace FileSys { +namespace { +template <bool Subsection, typename BlockType, typename BucketType> +std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, + const BucketType& buckets) { + if constexpr (Subsection) { + const auto& last_bucket = buckets[block.number_buckets - 1]; + if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { + return {block.number_buckets - 1, last_bucket.number_entries}; + } + } else { + ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); + } + + std::size_t bucket_id = std::count_if( + block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, + [&offset](u64 base_offset) { return base_offset <= offset; }); + + const auto& bucket = buckets[bucket_id]; + + if (bucket.number_entries == 1) { + return {bucket_id, 0}; + } + + std::size_t low = 0; + std::size_t mid = 0; + std::size_t high = bucket.number_entries - 1; + while (low <= high) { + mid = (low + high) / 2; + if (bucket.entries[mid].address_patch > offset) { + high = mid - 1; + } else { + if (mid == bucket.number_entries - 1 || + bucket.entries[mid + 1].address_patch > offset) { + return {bucket_id, mid}; + } + + low = mid + 1; + } + } + UNREACHABLE_MSG("Offset could not be found in BKTR block."); + return {0, 0}; +} +} // Anonymous namespace BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, @@ -110,46 +153,6 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { return raw_read; } -template <bool Subsection, typename BlockType, typename BucketType> -std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, - BucketType buckets) const { - if constexpr (Subsection) { - const auto last_bucket = buckets[block.number_buckets - 1]; - if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) - return {block.number_buckets - 1, last_bucket.number_entries}; - } else { - ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); - } - - std::size_t bucket_id = std::count_if( - block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, - [&offset](u64 base_offset) { return base_offset <= offset; }); - - const auto bucket = buckets[bucket_id]; - - if (bucket.number_entries == 1) - return {bucket_id, 0}; - - std::size_t low = 0; - std::size_t mid = 0; - std::size_t high = bucket.number_entries - 1; - while (low <= high) { - mid = (low + high) / 2; - if (bucket.entries[mid].address_patch > offset) { - high = mid - 1; - } else { - if (mid == bucket.number_entries - 1 || - bucket.entries[mid + 1].address_patch > offset) { - return {bucket_id, mid}; - } - - low = mid + 1; - } - } - - UNREACHABLE_MSG("Offset could not be found in BKTR block."); -} - RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); return relocation_buckets[res.first].entries[res.second]; @@ -188,7 +191,7 @@ bool BKTR::Resize(std::size_t new_size) { return false; } -std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const { +VirtualDir BKTR::GetContainingDirectory() const { return base_romfs->GetContainingDirectory(); } diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h index 8e64e8378..503cf473e 100644 --- a/src/core/file_sys/nca_patch.h +++ b/src/core/file_sys/nca_patch.h @@ -106,7 +106,7 @@ public: bool Resize(std::size_t new_size) override; - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; @@ -117,10 +117,6 @@ public: bool Rename(std::string_view name) override; private: - template <bool Subsection, typename BlockType, typename BucketType> - std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block, - BucketType buckets) const; - RelocationEntry GetRelocationEntry(u64 offset) const; RelocationEntry GetNextRelocationEntry(u64 offset) const; diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 846986736..48a2ed4d4 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -21,7 +21,7 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const { magic == Common::MakeMagic('P', 'F', 'S', '0'); } -PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { +PartitionFilesystem::PartitionFilesystem(VirtualFile file) { // At least be as large as the header if (file->GetSize() < sizeof(Header)) { status = Loader::ResultStatus::ErrorBadPFSHeader; @@ -89,11 +89,11 @@ std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const { return sizes; } -std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const { +std::vector<VirtualFile> PartitionFilesystem::GetFiles() const { return pfs_files; } -std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const { +std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const { return {}; } @@ -101,7 +101,7 @@ std::string PartitionFilesystem::GetName() const { return is_hfs ? "HFS0" : "PFS0"; } -std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const { +VirtualDir PartitionFilesystem::GetParentDirectory() const { // TODO(DarkLordZach): Add support for nested containers. return nullptr; } diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index 279193b19..0f831148e 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -24,7 +24,7 @@ namespace FileSys { */ class PartitionFilesystem : public ReadOnlyVfsDirectory { public: - explicit PartitionFilesystem(std::shared_ptr<VfsFile> file); + explicit PartitionFilesystem(VirtualFile file); ~PartitionFilesystem() override; Loader::ResultStatus GetStatus() const; @@ -32,10 +32,10 @@ public: std::map<std::string, u64> GetFileOffsets() const; std::map<std::string, u64> GetFileSizes() const; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualFile> GetFiles() const override; + std::vector<VirtualDir> GetSubdirectories() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; void PrintDebugInfo() const; private: diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 729dbb5f4..7c3284df8 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -12,6 +12,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/ips_layer.h" @@ -27,30 +28,38 @@ #include "core/settings.h" namespace FileSys { +namespace { -constexpr u64 SINGLE_BYTE_MODULUS = 0x100; -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; +constexpr u32 SINGLE_BYTE_MODULUS = 0x100; constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ "main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", }; -std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { +enum class TitleVersionFormat : u8 { + ThreeElements, ///< vX.Y.Z + FourElements, ///< vX.Y.Z.W +}; + +std::string FormatTitleVersion(u32 version, + TitleVersionFormat format = TitleVersionFormat::ThreeElements) { std::array<u8, sizeof(u32)> bytes{}; - bytes[0] = version % SINGLE_BYTE_MODULUS; + bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS); for (std::size_t i = 1; i < bytes.size(); ++i) { version /= SINGLE_BYTE_MODULUS; - bytes[i] = version % SINGLE_BYTE_MODULUS; + bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS); } - if (format == TitleVersionFormat::FourElements) + if (format == TitleVersionFormat::FourElements) { return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); + } return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); } -std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir, - std::string_view name) { +// Returns a directory with name matching name case-insensitive. Returns nullptr if directory +// doesn't have a directory with name. +VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) { #ifdef _WIN32 return dir->GetSubdirectory(name); #else @@ -66,7 +75,47 @@ std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<Vfs #endif } -PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} +std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder( + u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexToString(build_id_, upper); + const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); + const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); + + if (file == nullptr) { + LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + std::vector<u8> data(file->GetSize()); + if (file->Read(data.data(), data.size()) != data.size()) { + LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + const Core::Memory::TextCheatParser parser; + return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size())); +} + +void AppendCommaIfNotEmpty(std::string& to, std::string_view with) { + if (to.empty()) { + to += with; + } else { + to += ", "; + to += with; + } +} + +bool IsDirValidAndNonEmpty(const VirtualDir& dir) { + return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); +} +} // Anonymous namespace + +PatchManager::PatchManager(u64 title_id_, + const Service::FileSystem::FileSystemController& fs_controller_, + const ContentProvider& content_provider_) + : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {} PatchManager::~PatchManager() = default; @@ -82,34 +131,30 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (Settings::values.dump_exefs) { LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); - const auto dump_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); + const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); VfsRawCopyD(exefs, exefs_dir); } } - const auto& installed = Core::System::GetInstance().GetContentProvider(); - const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); + const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", - FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); } // LayeredExeFS - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir != nullptr && load_dir->GetSize() > 0) { auto patch_dirs = load_dir->GetSubdirectories(); std::sort( @@ -195,8 +240,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st if (Settings::values.dump_nso) { LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id, title_id); - const auto dump_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); + const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id)); @@ -208,8 +252,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return nso; @@ -246,14 +289,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st return out; } -bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { +bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { const auto build_id_raw = Common::HexToString(build_id_); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return false; @@ -266,37 +308,9 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } -namespace { -std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder( - const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_, - const VirtualDir& base_path, bool upper) { - const auto build_id_raw = Common::HexToString(build_id_, upper); - const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); - const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); - - if (file == nullptr) { - LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", - title_id, build_id); - return std::nullopt; - } - - std::vector<u8> data(file->GetSize()); - if (file->Read(data.data(), data.size()) != data.size()) { - LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", - title_id, build_id); - return std::nullopt; - } - - Core::Memory::TextCheatParser parser; - return parser.Parse(system, - std::string_view(reinterpret_cast<const char*>(data.data()), data.size())); -} - -} // Anonymous namespace - std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( - const Core::System& system, const std::array<u8, 32>& build_id_) const { - const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); + const BuildID& build_id_) const { + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; @@ -315,14 +329,12 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); if (cheats_dir != nullptr) { - auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); - if (res.has_value()) { + if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) { std::copy(res->begin(), res->end(), std::back_inserter(out)); continue; } - res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); - if (res.has_value()) { + if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) { std::copy(res->begin(), res->end(), std::back_inserter(out)); } } @@ -331,9 +343,9 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( return out; } -static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); +static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, + const Service::FileSystem::FileSystemController& fs_controller) { + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || load_dir == nullptr || load_dir->GetSize() <= 0) { return; @@ -395,19 +407,19 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", title_id, static_cast<u8>(type)); - if (type == ContentRecordType::Program || type == ContentRecordType::Data) + if (type == ContentRecordType::Program || type == ContentRecordType::Data) { LOG_INFO(Loader, "{}", log_string); - else + } else { LOG_DEBUG(Loader, "{}", log_string); + } - if (romfs == nullptr) + if (romfs == nullptr) { return romfs; - - const auto& installed = Core::System::GetInstance().GetContentProvider(); + } // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed.GetEntryRaw(update_tid, type); + const auto update = content_provider.GetEntryRaw(update_tid, type); const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = @@ -418,7 +430,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", - FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } } else if (!update_disabled && update_raw != nullptr) { @@ -431,33 +443,22 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content } // LayeredFS - ApplyLayeredFS(romfs, title_id, type); + ApplyLayeredFS(romfs, title_id, type, fs_controller); return romfs; } -static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) { - if (to.empty()) - to += with; - else - to += ", " + with; -} - -static bool IsDirValidAndNonEmpty(const VirtualDir& dir) { - return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); -} - -std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( - VirtualFile update_raw) const { - if (title_id == 0) +PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const { + if (title_id == 0) { return {}; + } + std::map<std::string, std::string, std::less<>> out; - const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto& disabled = Settings::values.disabled_addons[title_id]; // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - PatchManager update{update_tid}; + PatchManager update{update_tid, fs_controller, content_provider}; const auto metadata = update.GetControlMetadata(); const auto& nacp = metadata.first; @@ -468,13 +469,12 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (nacp != nullptr) { out.insert_or_assign(update_label, nacp->GetVersionString()); } else { - if (installed.HasEntry(update_tid, ContentRecordType::Program)) { - const auto meta_ver = installed.GetEntryVersion(update_tid); + if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { + const auto meta_ver = content_provider.GetEntryVersion(update_tid); if (meta_ver.value_or(0) == 0) { out.insert_or_assign(update_label, ""); } else { - out.insert_or_assign( - update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); + out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver)); } } else if (update_raw != nullptr) { out.insert_or_assign(update_label, "PACKED"); @@ -482,8 +482,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } // General Mods (LayeredFS and IPS) - const auto mod_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; @@ -527,13 +526,15 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } // DLC - const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); + const auto dlc_entries = + content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); std::vector<ContentProviderEntry> dlc_match; dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), - [this, &installed](const ContentProviderEntry& entry) { - return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id && - installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; + [this](const ContentProviderEntry& entry) { + return GetBaseTitleID(entry.title_id) == title_id && + content_provider.GetEntry(entry)->GetStatus() == + Loader::ResultStatus::Success; }); if (!dlc_match.empty()) { // Ensure sorted so DLC IDs show in order. @@ -554,49 +555,52 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } std::optional<u32> PatchManager::GetGameVersion() const { - const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto update_tid = GetUpdateTitleID(title_id); - if (installed.HasEntry(update_tid, ContentRecordType::Program)) { - return installed.GetEntryVersion(update_tid); + if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { + return content_provider.GetEntryVersion(update_tid); } - return installed.GetEntryVersion(title_id); + return content_provider.GetEntryVersion(title_id); } -std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { - const auto& installed = Core::System::GetInstance().GetContentProvider(); - - const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control); - if (base_control_nca == nullptr) +PatchManager::Metadata PatchManager::GetControlMetadata() const { + const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); + if (base_control_nca == nullptr) { return {}; + } return ParseControlNCA(*base_control_nca); } -std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { +PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { const auto base_romfs = nca.GetRomFS(); - if (base_romfs == nullptr) + if (base_romfs == nullptr) { return {}; + } const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); - if (romfs == nullptr) + if (romfs == nullptr) { return {}; + } const auto extracted = ExtractRomFS(romfs); - if (extracted == nullptr) + if (extracted == nullptr) { return {}; + } auto nacp_file = extracted->GetFile("control.nacp"); - if (nacp_file == nullptr) + if (nacp_file == nullptr) { nacp_file = extracted->GetFile("Control.nacp"); + } auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); VirtualFile icon_file; for (const auto& language : FileSys::LANGUAGE_NAMES) { - icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat"); - if (icon_file != nullptr) + icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat")); + if (icon_file != nullptr) { break; + } } return {std::move(nacp), icon_file}; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index f4cb918dd..fb1853035 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -6,88 +6,89 @@ #include <map> #include <memory> +#include <optional> #include <string> #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" #include "core/memory/dmnt_cheat_types.h" namespace Core { class System; } +namespace Service::FileSystem { +class FileSystemController; +} + namespace FileSys { +class ContentProvider; class NCA; class NACP; -enum class TitleVersionFormat : u8 { - ThreeElements, ///< vX.Y.Z - FourElements, ///< vX.Y.Z.W -}; - -std::string FormatTitleVersion(u32 version, - TitleVersionFormat format = TitleVersionFormat::ThreeElements); - -// Returns a directory with name matching name case-insensitive. Returns nullptr if directory -// doesn't have a directory with name. -std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir, - std::string_view name); - // A centralized class to manage patches to games. class PatchManager { public: - explicit PatchManager(u64 title_id); + using BuildID = std::array<u8, 0x20>; + using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>; + using PatchVersionNames = std::map<std::string, std::string, std::less<>>; + + explicit PatchManager(u64 title_id_, + const Service::FileSystem::FileSystemController& fs_controller_, + const ContentProvider& content_provider_); ~PatchManager(); - u64 GetTitleID() const; + [[nodiscard]] u64 GetTitleID() const; // Currently tracked ExeFS patches: // - Game Updates - VirtualDir PatchExeFS(VirtualDir exefs) const; + [[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const; // Currently tracked NSO patches: // - IPS // - IPSwitch - std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const; + [[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso, + const std::string& name) const; // Checks to see if PatchNSO() will have any effect given the NSO's build ID. // Used to prevent expensive copies in NSO loader. - bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; + [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; // Creates a CheatList object with all - std::vector<Core::Memory::CheatEntry> CreateCheatList( - const Core::System& system, const std::array<u8, 0x20>& build_id) const; + [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( + const BuildID& build_id) const; // Currently tracked RomFS patches: // - Game Updates // - LayeredFS - VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, - ContentRecordType type = ContentRecordType::Program, - VirtualFile update_raw = nullptr) const; + [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, + ContentRecordType type = ContentRecordType::Program, + VirtualFile update_raw = nullptr) const; // Returns a vector of pairs between patch names and patch versions. // i.e. Update 3.2.2 will return {"Update", "3.2.2"} - std::map<std::string, std::string, std::less<>> GetPatchVersionNames( - VirtualFile update_raw = nullptr) const; + [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const; // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be // std::nullopt - std::optional<u32> GetGameVersion() const; + [[nodiscard]] std::optional<u32> GetGameVersion() const; // Given title_id of the program, attempts to get the control data of the update and parse // it, falling back to the base control data. - std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; + [[nodiscard]] Metadata GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA - std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; + [[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const; private: - std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, - const std::string& build_id) const; + [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) const; u64 title_id; + const Service::FileSystem::FileSystemController& fs_controller; + const ContentProvider& content_provider; }; } // namespace FileSys diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 43169bf9f..9cf49bf44 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "core/file_sys/program_metadata.h" +#include "core/file_sys/vfs.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 35069972b..455532567 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -9,7 +9,7 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index da01002d5..431302f55 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -105,7 +105,8 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. return ContentRecordType::HtmlDocument; default: - UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type)); + UNREACHABLE_MSG("Invalid NCAContentType={:02X}", type); + return ContentRecordType{}; } } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 5b414b0f0..b08a1687a 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -67,18 +67,18 @@ public: virtual void Refresh() = 0; virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0; - virtual bool HasEntry(ContentProviderEntry entry) const; + bool HasEntry(ContentProviderEntry entry) const; virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0; virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0; - virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const; + VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const; virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0; - virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const; + VirtualFile GetEntryRaw(ContentProviderEntry entry) const; virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0; - virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const; + std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const; virtual std::vector<ContentProviderEntry> ListEntries() const; diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index 2fd07ed04..82e683782 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -4,7 +4,6 @@ #pragma once -#include <array> #include "core/file_sys/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 418a39a7e..f4e16e4be 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -6,8 +6,8 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "core/core.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" @@ -19,7 +19,9 @@ namespace FileSys { -RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { +RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider, + Service::FileSystem::FileSystemController& controller) + : content_provider{provider}, filesystem_controller{controller} { // Load the RomFS from the app if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) { LOG_ERROR(Service_FS, "Unable to read RomFS!"); @@ -36,49 +38,71 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { } ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { - if (!updatable) + if (!updatable) { return MakeResult<VirtualFile>(file); + } - const PatchManager patch_manager(current_process_title_id); + const PatchManager patch_manager{current_process_title_id, filesystem_controller, + content_provider}; return MakeResult<VirtualFile>( patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, - ContentRecordType type) const { - std::shared_ptr<NCA> res; +ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { + auto nca = content_provider.GetEntry(title_id, type); - switch (storage) { - case StorageId::None: - res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type); - break; - case StorageId::NandSystem: - res = - Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry( - title_id, type); - break; - case StorageId::NandUser: - res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry( - title_id, type); - break; - case StorageId::SdCard: - res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry( - title_id, type); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); + if (nca == nullptr) { + // TODO: Find the right error code to use here + return RESULT_UNKNOWN; } + const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; + + return MakeResult<VirtualFile>( + patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type)); +} + +ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, ContentRecordType type) const { + const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index); + + return OpenPatchedRomFS(res_title_id, type); +} + +ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, + ContentRecordType type) const { + const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type); if (res == nullptr) { // TODO(DarkLordZach): Find the right error code to use here return RESULT_UNKNOWN; } + const auto romfs = res->GetRomFS(); if (romfs == nullptr) { // TODO(DarkLordZach): Find the right error code to use here return RESULT_UNKNOWN; } + return MakeResult<VirtualFile>(romfs); } +std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage, + ContentRecordType type) const { + switch (storage) { + case StorageId::None: + return content_provider.GetEntry(title_id, type); + case StorageId::NandSystem: + return filesystem_controller.GetSystemNANDContents()->GetEntry(title_id, type); + case StorageId::NandUser: + return filesystem_controller.GetUserNANDContents()->GetEntry(title_id, type); + case StorageId::SdCard: + return filesystem_controller.GetSDMCContents()->GetEntry(title_id, type); + case StorageId::Host: + case StorageId::GameCard: + default: + UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); + return nullptr; + } +} + } // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index c5d40285c..96dd0d578 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -13,8 +13,15 @@ namespace Loader { class AppLoader; } // namespace Loader +namespace Service::FileSystem { +class FileSystemController; +} + namespace FileSys { +class ContentProvider; +class NCA; + enum class ContentRecordType : u8; enum class StorageId : u8 { @@ -29,18 +36,30 @@ enum class StorageId : u8 { /// File system interface to the RomFS archive class RomFSFactory { public: - explicit RomFSFactory(Loader::AppLoader& app_loader); + explicit RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider, + Service::FileSystem::FileSystemController& controller); ~RomFSFactory(); void SetPackedUpdate(VirtualFile update_raw); - ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; - ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; + [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; + [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id, + ContentRecordType type) const; + [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, ContentRecordType type) const; + [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, + ContentRecordType type) const; private: + [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, + ContentRecordType type) const; + VirtualFile file; VirtualFile update_raw; bool updatable; u64 ivfc_offset; + + ContentProvider& content_provider; + Service::FileSystem::FileSystemController& filesystem_controller; }; } // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index ba4efee3a..b7bfe0928 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -70,7 +70,8 @@ std::string SaveDataAttribute::DebugInfo() const { static_cast<u8>(rank), index); } -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { +SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_) + : dir{std::move(save_directory_)}, system{system_} { // Delete all temporary storages // On hardware, it is expected that temporary storage be empty at first use. dir->DeleteSubdirectoryRecursive("temp"); @@ -83,7 +84,7 @@ ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, PrintSaveDataAttributeWarnings(meta); const auto save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->CreateDirectoryRelative(save_directory); @@ -100,7 +101,7 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { const auto save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->GetDirectoryRelative(save_directory); @@ -135,13 +136,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) { } } -std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, - u128 user_id, u64 save_id) { +std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId space, + SaveDataType type, u64 title_id, u128 user_id, + u64 save_id) { // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should // be interpreted as the title id of the current process. if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) { if (title_id == 0) { - title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + title_id = system.CurrentProcess()->GetTitleID(); } } @@ -167,7 +169,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const { - const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); const auto size_file = dir->GetFile(SAVE_DATA_SIZE_FILENAME); @@ -182,7 +184,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value) const { - const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); const auto size_file = dir->CreateFile(SAVE_DATA_SIZE_FILENAME); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 6625bbbd8..86c9f5350 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -12,6 +12,10 @@ #include "core/file_sys/vfs.h" #include "core/hle/result.h" +namespace Core { +class System; +} + namespace FileSys { enum class SaveDataSpaceId : u8 { @@ -54,7 +58,7 @@ struct SaveDataAttribute { SaveDataType type; SaveDataRank rank; u16 index; - INSERT_PADDING_BYTES(4); + INSERT_PADDING_BYTES_NOINIT(4); u64 zero_1; u64 zero_2; u64 zero_3; @@ -68,7 +72,7 @@ struct SaveDataExtraData { u64 owner_id; s64 timestamp; SaveDataFlags flags; - INSERT_PADDING_BYTES(4); + INSERT_PADDING_BYTES_NOINIT(4); s64 available_size; s64 journal_size; s64 commit_id; @@ -84,7 +88,7 @@ struct SaveDataSize { /// File system interface to the SaveData archive class SaveDataFactory { public: - explicit SaveDataFactory(VirtualDir dir); + explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_); ~SaveDataFactory(); ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; @@ -93,8 +97,8 @@ public: VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space); - static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, - u128 user_id, u64 save_id); + static std::string GetFullPath(Core::System& system, SaveDataSpaceId space, SaveDataType type, + u64 title_id, u128 user_id, u64 save_id); SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, @@ -102,6 +106,7 @@ public: private: VirtualDir dir; + Core::System& system; }; } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index 6f732e4d8..cb56d8f2d 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -5,8 +5,8 @@ #include <memory> #include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" +#include "core/file_sys/vfs.h" #include "core/file_sys/xts_archive.h" -#include "core/settings.h" namespace FileSys { diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 42dc4e08a..2bb92ba93 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -5,7 +5,7 @@ #pragma once #include <memory> -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" #include "core/hle/result.h" namespace FileSys { diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 175a8266a..c05735ddd 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -19,42 +19,10 @@ #include "core/loader/loader.h" namespace FileSys { -namespace { -void SetTicketKeys(const std::vector<VirtualFile>& files) { - auto& keys = Core::Crypto::KeyManager::Instance(); - for (const auto& ticket_file : files) { - if (ticket_file == nullptr) { - continue; - } - - if (ticket_file->GetExtension() != "tik") { - continue; - } - - if (ticket_file->GetSize() < - Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { - continue; - } - - Core::Crypto::Key128 key{}; - ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); - - // We get the name without the extension in order to create the rights ID. - std::string name_only(ticket_file->GetName()); - name_only.erase(name_only.size() - 4); - - const auto rights_id_raw = Common::HexStringToArray<16>(name_only); - u128 rights_id; - std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); - keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); - } -} -} // Anonymous namespace - -NSP::NSP(VirtualFile file_) - : file(std::move(file_)), status{Loader::ResultStatus::Success}, - pfs(std::make_shared<PartitionFilesystem>(file)) { +NSP::NSP(VirtualFile file_, std::size_t program_index) + : file(std::move(file_)), program_index(program_index), status{Loader::ResultStatus::Success}, + pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} { if (pfs->GetStatus() != Loader::ResultStatus::Success) { status = pfs->GetStatus(); return; @@ -178,7 +146,7 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType if (extracted) LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); - const auto title_id_iter = ncas.find(title_id); + const auto title_id_iter = ncas.find(title_id + program_index); if (title_id_iter == ncas.end()) return nullptr; @@ -232,6 +200,35 @@ VirtualDir NSP::GetParentDirectory() const { return file->GetContainingDirectory(); } +void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) { + for (const auto& ticket_file : files) { + if (ticket_file == nullptr) { + continue; + } + + if (ticket_file->GetExtension() != "tik") { + continue; + } + + if (ticket_file->GetSize() < + Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { + continue; + } + + Core::Crypto::Key128 key{}; + ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); + + // We get the name without the extension in order to create the rights ID. + std::string name_only(ticket_file->GetName()); + name_only.erase(name_only.size() - 4); + + const auto rights_id_raw = Common::HexStringToArray<16>(name_only); + u128 rights_id; + std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); + keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + } +} + void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) { exefs = pfs; @@ -267,9 +264,9 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { } const CNMT cnmt(inner_file); - auto& ncas_title = ncas[cnmt.GetTitleID()]; - ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca; + ncas[cnmt.GetTitleID()][{cnmt.GetType(), ContentRecordType::Meta}] = nca; + for (const auto& rec : cnmt.GetContentRecords()) { const auto id_string = Common::HexToString(rec.nca_id, false); auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); @@ -286,13 +283,32 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { } auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); + if (next_nca->GetType() == NCAContentType::Program) { - program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); + program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); } - if (next_nca->GetStatus() == Loader::ResultStatus::Success || - (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && - (cnmt.GetTitleID() & 0x800) != 0)) { - ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca); + + if (next_nca->GetStatus() != Loader::ResultStatus::Success && + next_nca->GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + continue; + } + + // If the last 3 hexadecimal digits of the CNMT TitleID is 0x800 or is missing the + // BKTRBaseRomFS, this is an update NCA. Otherwise, this is a base NCA. + if ((cnmt.GetTitleID() & 0x800) != 0 || + next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and + // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular + // update NCA. + if ((next_nca->GetTitleId() & 0x7FF) != 0 && + (next_nca->GetTitleId() & 0x800) == 0) { + ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = + std::move(next_nca); + } else { + ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca); + } + } else { + ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca); } } diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index cf89de6a9..54581a6f3 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -10,6 +10,10 @@ #include "common/common_types.h" #include "core/file_sys/vfs.h" +namespace Core::Crypto { +class KeyManager; +} + namespace Loader { enum class ResultStatus : u16; } @@ -23,7 +27,7 @@ enum class ContentRecordType : u8; class NSP : public ReadOnlyVfsDirectory { public: - explicit NSP(VirtualFile file); + explicit NSP(VirtualFile file, std::size_t program_index = 0); ~NSP() override; Loader::ResultStatus GetStatus() const; @@ -59,11 +63,14 @@ public: VirtualDir GetParentDirectory() const override; private: + void SetTicketKeys(const std::vector<VirtualFile>& files); void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files); void ReadNCAs(const std::vector<VirtualFile>& files); VirtualFile file; + const std::size_t program_index; + bool extracted = false; Loader::ResultStatus status; std::map<u64, Loader::ResultStatus> program_status; @@ -73,7 +80,7 @@ private: std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas; std::vector<VirtualFile> ticket_files; - Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + Core::Crypto::KeyManager& keys; VirtualFile romfs; VirtualDir exefs; diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp index 69d62ce8f..29ef110a6 100644 --- a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp +++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp @@ -6,191 +6,384 @@ namespace FileSys::SystemArchive::SharedFontData { -const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED{{ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x03, 0x00, 0x70, 0x44, 0x53, 0x49, 0x47, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x4f, 0x53, 0x2f, 0x32, - 0x33, 0x86, 0x1d, 0x9b, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70, - 0xc2, 0x06, 0x20, 0xde, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x63, 0x76, 0x74, 0x20, - 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6d, - 0x06, 0x59, 0x9c, 0x37, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70, - 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0b, 0x64, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66, - 0x10, 0x31, 0x88, 0x00, 0x00, 0x00, 0x04, 0x34, 0x00, 0x00, 0x04, 0x64, 0x68, 0x65, 0x61, 0x64, - 0x15, 0x9d, 0xef, 0x91, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, - 0x09, 0x60, 0x03, 0x71, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78, - 0x0d, 0x2e, 0x03, 0xa7, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x6f, 0x63, 0x61, - 0x05, 0xc0, 0x04, 0x6c, 0x00, 0x00, 0x08, 0x98, 0x00, 0x00, 0x00, 0x1e, 0x6d, 0x61, 0x78, 0x70, - 0x02, 0x1c, 0x00, 0x5f, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x7c, 0xe0, 0x84, 0x5c, 0x00, 0x00, 0x08, 0xb8, 0x00, 0x00, 0x02, 0x09, 0x70, 0x6f, 0x73, 0x74, - 0x47, 0x4e, 0x74, 0x19, 0x00, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x72, 0x65, 0x70, - 0x1c, 0xfc, 0x7d, 0x9c, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x7c, 0xc7, 0xb1, 0x63, 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x1b, 0x03, 0xe8, - 0x00, 0x00, 0x00, 0x00, 0xd9, 0x44, 0x2f, 0x5d, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x45, 0x7b, 0x69, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x84, 0xff, 0x83, 0x01, 0xf4, 0x03, 0xe8, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x5e, - 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x74, 0x01, 0x90, 0x00, 0x05, - 0x00, 0x04, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, 0x01, 0x1f, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, - 0x03, 0xc3, 0x00, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED{{ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x80, 0x00, 0x03, 0x00, 0x60, 0x4F, 0x53, 0x2F, 0x32, + 0x34, 0x00, 0x1E, 0x26, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70, + 0xC1, 0xE7, 0xC8, 0xF3, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x01, 0x72, 0x63, 0x76, 0x74, 0x20, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6D, + 0x06, 0x59, 0x9C, 0x37, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x80, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6C, 0x79, 0x66, + 0x50, 0x0B, 0xEA, 0xFA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x0F, 0x04, 0x68, 0x65, 0x61, 0x64, + 0x18, 0x65, 0x81, 0x09, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, + 0x09, 0x88, 0x03, 0x86, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78, + 0x0A, 0xF0, 0x01, 0x94, 0x00, 0x00, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x42, 0x6C, 0x6F, 0x63, 0x61, + 0x34, 0x80, 0x30, 0x6E, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x00, 0x3A, 0x6D, 0x61, 0x78, 0x70, + 0x02, 0x2C, 0x00, 0x72, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65, + 0xDB, 0xC5, 0x42, 0x4D, 0x00, 0x00, 0x14, 0x54, 0x00, 0x00, 0x01, 0xFE, 0x70, 0x6F, 0x73, 0x74, + 0xF4, 0xB4, 0xAC, 0xAB, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x01, 0x2A, 0x70, 0x72, 0x65, 0x70, + 0x1C, 0xFC, 0x7D, 0x9C, 0x00, 0x00, 0x04, 0xF4, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0xC9, 0x16, 0x5B, 0x71, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x0B, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xD9, 0x44, 0x2F, 0x5D, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x02, 0x0D, 0xA7, + 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x9A, 0xFF, 0x80, 0x02, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xEC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x71, + 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xC4, 0x01, 0x90, 0x00, 0x05, + 0x00, 0x04, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, 0x01, 0x26, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, + 0x03, 0xDA, 0x00, 0x68, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0xe9, 0x03, 0x84, 0xff, 0x83, - 0x01, 0xf4, 0x02, 0xee, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, - 0x02, 0xbc, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x03, 0xe8, 0x00, 0xeb, 0x01, 0x21, 0x00, 0xff, - 0x00, 0xff, 0x01, 0x3d, 0x01, 0x17, 0x00, 0x42, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x17, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x06, 0x00, 0x4c, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xC0, 0x00, 0x0D, 0xE0, 0xF0, 0x03, 0x9A, 0xFF, 0x80, + 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x02, 0xCD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0x04, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA9, 0xE0, 0xB4, + 0xE0, 0xE9, 0xE0, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA0, + 0xE0, 0xB3, 0xE0, 0xE0, 0xE0, 0xEF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF5, 0xFF, 0xE3, 0x1F, 0x64, + 0x1F, 0x5B, 0x1F, 0x30, 0x1F, 0x2B, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a, - 0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe9, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe0, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf5, - 0xff, 0xe3, 0x1f, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xb8, 0x00, 0x00, 0x2c, 0x4b, 0xb8, 0x00, 0x09, 0x50, 0x58, 0xb1, 0x01, 0x01, 0x8e, 0x59, 0xb8, - 0x01, 0xff, 0x85, 0xb8, 0x00, 0x44, 0x1d, 0xb9, 0x00, 0x09, 0x00, 0x03, 0x5f, 0x5e, 0x2d, 0xb8, - 0x00, 0x01, 0x2c, 0x20, 0x20, 0x45, 0x69, 0x44, 0xb0, 0x01, 0x60, 0x2d, 0xb8, 0x00, 0x02, 0x2c, - 0xb8, 0x00, 0x01, 0x2a, 0x21, 0x2d, 0xb8, 0x00, 0x03, 0x2c, 0x20, 0x46, 0xb0, 0x03, 0x25, 0x46, - 0x52, 0x58, 0x23, 0x59, 0x20, 0x8a, 0x20, 0x8a, 0x49, 0x64, 0x8a, 0x20, 0x46, 0x20, 0x68, 0x61, - 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8a, 0x59, 0x2f, - 0x20, 0xb0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x59, 0x1b, - 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x65, 0x59, 0x59, 0x3a, 0x2d, 0xb8, 0x00, - 0x04, 0x2c, 0x20, 0x46, 0xb0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8a, 0x59, 0x20, 0x46, 0x20, - 0x6a, 0x61, 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x6a, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8a, 0x59, - 0x2f, 0xfd, 0x2d, 0xb8, 0x00, 0x05, 0x2c, 0x4b, 0x20, 0xb0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58, - 0xb0, 0x80, 0x44, 0x1b, 0xb0, 0x40, 0x44, 0x59, 0x1b, 0x21, 0x21, 0x20, 0x45, 0xb0, 0xc0, 0x50, - 0x58, 0xb0, 0xc0, 0x44, 0x1b, 0x21, 0x59, 0x59, 0x2d, 0xb8, 0x00, 0x06, 0x2c, 0x20, 0x20, 0x45, - 0x69, 0x44, 0xb0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0xb0, 0x01, 0x60, 0x2d, - 0xb8, 0x00, 0x07, 0x2c, 0xb8, 0x00, 0x06, 0x2a, 0x2d, 0xb8, 0x00, 0x08, 0x2c, 0x4b, 0x20, 0xb0, - 0x03, 0x26, 0x53, 0x58, 0xb0, 0x40, 0x1b, 0xb0, 0x00, 0x59, 0x8a, 0x8a, 0x20, 0xb0, 0x03, 0x26, - 0x53, 0x58, 0x23, 0x21, 0xb0, 0x80, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, 0x26, - 0x53, 0x58, 0x23, 0x21, 0xb8, 0x00, 0xc0, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, - 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x00, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, - 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x40, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, - 0xb8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xb0, 0x03, 0x25, 0x45, 0xb8, 0x01, 0x80, 0x50, 0x58, 0x23, - 0x21, 0xb8, 0x01, 0x80, 0x23, 0x21, 0x1b, 0xb0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59, - 0x1b, 0x21, 0x59, 0x44, 0x2d, 0xb8, 0x00, 0x09, 0x2c, 0x4b, 0x53, 0x58, 0x45, 0x44, 0x1b, 0x21, - 0x21, 0x59, 0x2d, 0x00, 0xb8, 0x00, 0x00, 0x2b, 0x00, 0xba, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, - 0x2b, 0xb8, 0x00, 0x00, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x06, - 0x00, 0x00, 0x35, 0x01, 0x33, 0x15, 0x01, 0x23, 0x35, 0x03, 0x52, 0x94, 0xfc, 0xa6, 0x8c, 0x90, - 0x03, 0x58, 0x86, 0xfc, 0xa0, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0xeb, 0x00, 0xcc, 0x02, 0xfb, - 0x03, 0x1e, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x33, 0x13, 0x23, 0x27, 0x23, 0x07, 0x23, - 0x13, 0x17, 0x07, 0x06, 0x15, 0x33, 0x27, 0x07, 0x01, 0xbc, 0x6d, 0xd2, 0x7c, 0x26, 0xcc, 0x26, - 0x7c, 0xd1, 0x35, 0x40, 0x02, 0x89, 0x45, 0x02, 0x03, 0x1e, 0xfd, 0xae, 0x77, 0x77, 0x02, 0x52, - 0x9b, 0xcc, 0x08, 0x04, 0xda, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0x21, 0x00, 0xcc, 0x02, 0xc5, - 0x03, 0x1e, 0x00, 0x15, 0x00, 0x1f, 0x00, 0x2b, 0x00, 0x00, 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, - 0x15, 0x14, 0x0e, 0x02, 0x07, 0x1e, 0x01, 0x15, 0x14, 0x0e, 0x02, 0x2b, 0x01, 0x13, 0x33, 0x32, - 0x36, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x1d, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, - 0x01, 0x15, 0x01, 0x21, 0xea, 0x25, 0x3f, 0x2e, 0x1a, 0x0e, 0x15, 0x1b, 0x0e, 0x2d, 0x2d, 0x1a, - 0x2e, 0x3f, 0x25, 0xf8, 0x76, 0x62, 0x20, 0x2a, 0x28, 0x22, 0x62, 0x76, 0x10, 0x18, 0x11, 0x09, - 0x22, 0x22, 0x74, 0xcc, 0x02, 0x52, 0x18, 0x2b, 0x3c, 0x24, 0x1d, 0x1f, 0x17, 0x17, 0x14, 0x0f, - 0x48, 0x2f, 0x24, 0x3f, 0x2e, 0x1a, 0x01, 0x5b, 0x29, 0x20, 0x20, 0x2b, 0x94, 0xf8, 0x0e, 0x16, - 0x1c, 0x0e, 0x1f, 0x31, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, - 0x03, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x33, 0x17, 0x37, 0x33, 0x03, 0x13, 0x23, 0x27, 0x07, - 0x23, 0x13, 0x03, 0x01, 0x04, 0x86, 0x69, 0x69, 0x86, 0xa3, 0xa8, 0x88, 0x6c, 0x6c, 0x88, 0xa8, - 0xa3, 0x03, 0x1e, 0xcb, 0xcb, 0xfe, 0xda, 0xfe, 0xd4, 0xcf, 0xcf, 0x01, 0x2c, 0x01, 0x26, 0x00, - 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, 0x03, 0x1e, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x03, - 0x33, 0x17, 0x32, 0x15, 0x1e, 0x01, 0x15, 0x1b, 0x01, 0x33, 0x03, 0x15, 0x23, 0x35, 0x01, 0xb8, - 0xb9, 0x7e, 0x01, 0x01, 0x01, 0x03, 0x70, 0x75, 0x7f, 0xb9, 0x76, 0x01, 0xa3, 0x01, 0x7b, 0x01, - 0x01, 0x01, 0x05, 0x02, 0xff, 0x00, 0x01, 0x0a, 0xfe, 0x85, 0xd7, 0xd7, 0x00, 0x01, 0x01, 0x3d, - 0x00, 0xcc, 0x02, 0xa9, 0x03, 0x1e, 0x00, 0x06, 0x00, 0x00, 0x25, 0x11, 0x33, 0x11, 0x33, 0x15, - 0x21, 0x01, 0x3d, 0x75, 0xf7, 0xfe, 0x94, 0xcc, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x01, 0x17, 0x00, 0xbc, 0x02, 0xcf, 0x03, 0x0e, 0x00, 0x15, 0x00, 0x21, 0x00, 0x00, - 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x1d, 0x01, 0x0e, 0x03, 0x1d, 0x01, 0x17, 0x15, 0x23, 0x27, - 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x15, 0x01, 0x17, - 0xf4, 0x27, 0x40, 0x2e, 0x19, 0x01, 0x1f, 0x24, 0x1e, 0x78, 0x7d, 0x6a, 0x5c, 0x75, 0x76, 0x72, - 0x12, 0x19, 0x11, 0x08, 0x26, 0x26, 0x6a, 0xbc, 0x02, 0x52, 0x1d, 0x31, 0x42, 0x25, 0x16, 0x18, - 0x32, 0x2a, 0x1b, 0x02, 0x01, 0xef, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x10, 0x1a, 0x1e, 0x0f, 0x23, - 0x36, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x42, 0x00, 0xbc, 0x03, 0xa4, 0x03, 0x0e, 0x00, 0x0a, - 0x00, 0x11, 0x00, 0x00, 0x13, 0x35, 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, - 0x11, 0x33, 0x11, 0x33, 0x15, 0x21, 0x42, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, 0xfe, 0x53, 0x01, - 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0x75, 0xf6, 0xfe, 0x95, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, - 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, - 0x00, 0xbc, 0x03, 0xca, 0x03, 0x0e, 0x00, 0x0a, 0x00, 0x21, 0x00, 0x2f, 0x00, 0x00, 0x13, 0x35, - 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, - 0x14, 0x06, 0x07, 0x0e, 0x03, 0x15, 0x17, 0x15, 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, - 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, 0x15, 0x1c, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, - 0xfe, 0x53, 0x01, 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x01, - 0x0d, 0x0e, 0x0a, 0x78, 0x7d, 0x69, 0x5c, 0x75, 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, - 0x1d, 0x13, 0x69, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, - 0x52, 0x1d, 0x31, 0x42, 0x25, 0x2b, 0x44, 0x1d, 0x01, 0x08, 0x09, 0x07, 0x01, 0xf1, 0x06, 0xd7, - 0xd7, 0x01, 0x3f, 0x11, 0x19, 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x02, 0x00, 0x3e, - 0x00, 0xb3, 0x03, 0xa8, 0x03, 0x17, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, - 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x27, 0x2e, 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, - 0x14, 0x16, 0x15, 0x1e, 0x05, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, 0x02, 0x35, 0x33, 0x1e, - 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x04, 0x35, 0x01, 0x11, 0x33, 0x11, 0x33, 0x15, - 0x21, 0x50, 0x24, 0x3b, 0x4a, 0x27, 0x28, 0x4b, 0x39, 0x22, 0x73, 0x01, 0x01, 0x08, 0x2b, 0x29, - 0x10, 0x20, 0x19, 0x0f, 0x01, 0x0b, 0x35, 0x41, 0x46, 0x3b, 0x25, 0x23, 0x3a, 0x4b, 0x27, 0x2b, - 0x50, 0x3f, 0x26, 0x74, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x11, 0x2c, 0x42, 0x4d, 0x42, 0x2c, - 0x01, 0xef, 0x73, 0xf6, 0xfe, 0x97, 0x02, 0x70, 0x2a, 0x3f, 0x2a, 0x14, 0x18, 0x2e, 0x44, 0x2c, - 0x02, 0x03, 0x01, 0x27, 0x27, 0x07, 0x10, 0x1a, 0x12, 0x02, 0x0b, 0x02, 0x1f, 0x22, 0x19, 0x17, - 0x27, 0x3f, 0x34, 0x2c, 0x3e, 0x28, 0x13, 0x1a, 0x32, 0x48, 0x2e, 0x30, 0x30, 0x06, 0x0f, 0x1a, - 0x13, 0x21, 0x27, 0x1e, 0x1b, 0x29, 0x3e, 0x31, 0xfe, 0x4c, 0x02, 0x53, 0xfe, 0x10, 0x63, 0x00, - 0x00, 0x03, 0x00, 0x17, 0x00, 0xb3, 0x03, 0xce, 0x03, 0x17, 0x00, 0x38, 0x00, 0x4f, 0x00, 0x5d, - 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x23, 0x2e, - 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, 0x14, 0x1e, 0x04, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, - 0x02, 0x35, 0x33, 0x1e, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x27, 0x2e, 0x03, 0x35, - 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x14, 0x06, 0x07, 0x30, 0x0e, 0x02, 0x31, 0x17, 0x15, - 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, - 0x15, 0x2a, 0x24, 0x3a, 0x4a, 0x26, 0x29, 0x4b, 0x39, 0x23, 0x73, 0x01, 0x01, 0x08, 0x2a, 0x2a, - 0x10, 0x1f, 0x1a, 0x10, 0x2c, 0x42, 0x4d, 0x42, 0x2c, 0x23, 0x39, 0x4b, 0x27, 0x2b, 0x51, 0x3f, - 0x27, 0x75, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x10, 0x1f, 0x1c, 0x25, 0x53, 0x47, 0x2e, 0x01, - 0xed, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x0c, 0x0e, 0x0c, 0x78, 0x7d, 0x68, 0x5d, 0x75, - 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, 0x1d, 0x13, 0x69, 0x02, 0x71, 0x2a, 0x3e, 0x2a, - 0x14, 0x18, 0x2e, 0x44, 0x2c, 0x02, 0x02, 0x27, 0x29, 0x07, 0x11, 0x1a, 0x12, 0x1d, 0x24, 0x1c, - 0x1d, 0x2b, 0x40, 0x32, 0x2c, 0x3f, 0x29, 0x13, 0x1a, 0x31, 0x49, 0x2e, 0x30, 0x30, 0x06, 0x0f, - 0x19, 0x13, 0x1e, 0x22, 0x0b, 0x0e, 0x20, 0x2f, 0x43, 0x30, 0xfe, 0x4b, 0x02, 0x52, 0x1d, 0x32, - 0x42, 0x25, 0x2c, 0x42, 0x1d, 0x08, 0x0a, 0x08, 0xf1, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x11, 0x19, - 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, - 0x00, 0x12, 0x00, 0x32, 0x00, 0x72, 0x00, 0x8e, 0x00, 0xac, 0x00, 0xbe, 0x00, 0xf0, 0x01, 0x14, - 0x01, 0x5c, 0x01, 0xb6, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0xa2, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x2f, - 0x00, 0x17, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x46, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x58, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x06, 0x00, 0x12, 0x00, 0x65, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x20, - 0x00, 0x77, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x97, 0x00, 0x03, - 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x5e, 0x00, 0xa5, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, - 0x00, 0x04, 0x00, 0x24, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1a, - 0x01, 0x27, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x24, 0x01, 0x41, 0x00, 0x03, - 0x00, 0x01, 0x04, 0x09, 0x00, 0x11, 0x00, 0x02, 0x01, 0x65, 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, - 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, - 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x3b, 0x3b, - 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x2d, 0x52, 0x3b, 0x32, 0x30, 0x31, 0x39, 0x3b, 0x46, 0x4c, 0x56, 0x49, 0x2d, 0x36, 0x31, 0x34, - 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x52, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x59, - 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d, - 0x52, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, - 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, - 0x6e, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x72, 0x00, - 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, - 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0x59, 0x00, - 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, - 0x52, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x39, 0x00, 0x3b, 0x00, 0x46, 0x00, - 0x4c, 0x00, 0x56, 0x00, 0x49, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x31, 0x00, 0x34, 0x00, 0x59, 0x00, - 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, - 0x52, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, - 0x20, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x59, 0x00, 0x75, 0x00, - 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, - 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x52, 0x00, - 0x52, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04, - 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0a, 0x01, 0x0b, 0x01, 0x0c, - 0x01, 0x0d, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, - 0x30, 0x44, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x31, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x33, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x35, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x37, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB8, 0x00, 0x00, 0x2C, 0x4B, 0xB8, 0x00, 0x09, 0x50, 0x58, 0xB1, 0x01, 0x01, 0x8E, 0x59, 0xB8, + 0x01, 0xFF, 0x85, 0xB8, 0x00, 0x44, 0x1D, 0xB9, 0x00, 0x09, 0x00, 0x03, 0x5F, 0x5E, 0x2D, 0xB8, + 0x00, 0x01, 0x2C, 0x20, 0x20, 0x45, 0x69, 0x44, 0xB0, 0x01, 0x60, 0x2D, 0xB8, 0x00, 0x02, 0x2C, + 0xB8, 0x00, 0x01, 0x2A, 0x21, 0x2D, 0xB8, 0x00, 0x03, 0x2C, 0x20, 0x46, 0xB0, 0x03, 0x25, 0x46, + 0x52, 0x58, 0x23, 0x59, 0x20, 0x8A, 0x20, 0x8A, 0x49, 0x64, 0x8A, 0x20, 0x46, 0x20, 0x68, 0x61, + 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8A, 0x59, 0x2F, + 0x20, 0xB0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x59, 0x1B, + 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x65, 0x59, 0x59, 0x3A, 0x2D, 0xB8, 0x00, + 0x04, 0x2C, 0x20, 0x46, 0xB0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8A, 0x59, 0x20, 0x46, 0x20, + 0x6A, 0x61, 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x6A, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8A, 0x59, + 0x2F, 0xFD, 0x2D, 0xB8, 0x00, 0x05, 0x2C, 0x4B, 0x20, 0xB0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58, + 0xB0, 0x80, 0x44, 0x1B, 0xB0, 0x40, 0x44, 0x59, 0x1B, 0x21, 0x21, 0x20, 0x45, 0xB0, 0xC0, 0x50, + 0x58, 0xB0, 0xC0, 0x44, 0x1B, 0x21, 0x59, 0x59, 0x2D, 0xB8, 0x00, 0x06, 0x2C, 0x20, 0x20, 0x45, + 0x69, 0x44, 0xB0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0xB0, 0x01, 0x60, 0x2D, + 0xB8, 0x00, 0x07, 0x2C, 0xB8, 0x00, 0x06, 0x2A, 0x2D, 0xB8, 0x00, 0x08, 0x2C, 0x4B, 0x20, 0xB0, + 0x03, 0x26, 0x53, 0x58, 0xB0, 0x40, 0x1B, 0xB0, 0x00, 0x59, 0x8A, 0x8A, 0x20, 0xB0, 0x03, 0x26, + 0x53, 0x58, 0x23, 0x21, 0xB0, 0x80, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, 0x26, + 0x53, 0x58, 0x23, 0x21, 0xB8, 0x00, 0xC0, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, + 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x00, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, + 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x40, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, + 0xB8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xB0, 0x03, 0x25, 0x45, 0xB8, 0x01, 0x80, 0x50, 0x58, 0x23, + 0x21, 0xB8, 0x01, 0x80, 0x23, 0x21, 0x1B, 0xB0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59, + 0x1B, 0x21, 0x59, 0x44, 0x2D, 0xB8, 0x00, 0x09, 0x2C, 0x4B, 0x53, 0x58, 0x45, 0x44, 0x1B, 0x21, + 0x21, 0x59, 0x2D, 0x00, 0xB8, 0x00, 0x00, 0x2B, 0x00, 0xBA, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0x2B, 0xB8, 0x00, 0x00, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x70, + 0x00, 0xDC, 0x01, 0x34, 0x01, 0x7C, 0x01, 0xA2, 0x01, 0xF4, 0x02, 0x3C, 0x02, 0xA8, 0x03, 0x4C, + 0x03, 0xE2, 0x04, 0x20, 0x04, 0x58, 0x04, 0x9A, 0x04, 0xEE, 0x05, 0x32, 0x05, 0x64, 0x05, 0x80, + 0x05, 0xC6, 0x05, 0xF6, 0x06, 0x54, 0x06, 0xB2, 0x07, 0x38, 0x07, 0x60, 0x07, 0x82, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xA4, 0xFF, 0xFF, 0x03, 0x5C, 0x03, 0x09, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x13, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0xCD, 0x02, 0x66, 0xFD, 0x71, 0x02, 0xB8, 0xFD, + 0x48, 0x02, 0xE0, 0xFD, 0x48, 0x02, 0xB8, 0x29, 0xFC, 0xF6, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, + 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x39, 0x00, 0x00, + 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x32, 0x1E, + 0x02, 0x14, 0x0E, 0x02, 0x22, 0x2E, 0x02, 0x34, 0x3E, 0x01, 0x13, 0x12, 0x37, 0x33, 0x13, 0x12, + 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, 0x27, 0x26, 0x2F, 0x01, + 0x06, 0x07, 0x06, 0x32, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, + 0x46, 0x46, 0x77, 0xFE, 0x9E, 0xC8, 0xB7, 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0x4E, + 0x4E, 0x83, 0x23, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, 0xD1, 0x2B, 0x37, 0x33, + 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, 0x3F, 0x46, 0x77, 0xA4, + 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, + 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0xFD, 0x64, 0x01, 0x1A, 0xEB, 0xFE, 0xFE, 0xFE, + 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, 0x02, 0xBE, 0x02, 0x00, + 0x00, 0x05, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2F, + 0x00, 0x3A, 0x00, 0x44, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x02, + 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x01, + 0x16, 0x17, 0x14, 0x06, 0x07, 0x06, 0x2B, 0x01, 0x19, 0x01, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, + 0x07, 0x06, 0x0F, 0x01, 0x36, 0x37, 0x34, 0x2E, 0x01, 0x27, 0x23, 0x15, 0x33, 0x32, 0x27, 0x32, + 0x37, 0x36, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x45, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x01, 0xF7, 0x61, 0x01, 0x4E, 0x3E, 0x29, 0xAF, 0x4E, 0x81, 0x8B, 0x1D, 0x3C, 0x1F, + 0x19, 0x04, 0x06, 0x39, 0x57, 0x44, 0x01, 0x1B, 0x2D, 0x51, 0x46, 0x46, 0x47, 0x66, 0x70, 0x16, + 0x1F, 0x01, 0x2C, 0x08, 0x4B, 0x4C, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, + 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, + 0x84, 0x84, 0x01, 0x6D, 0x21, 0x5B, 0x40, 0x50, 0x05, 0x03, 0x01, 0x03, 0x01, 0x05, 0x01, 0x05, + 0x09, 0x30, 0x25, 0x29, 0x40, 0x21, 0xC2, 0x06, 0x3E, 0x1A, 0x21, 0x0B, 0x01, 0x8C, 0xE1, 0x0A, + 0x0E, 0x54, 0x0B, 0x02, 0x79, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, + 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x38, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, + 0x02, 0x34, 0x2E, 0x02, 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, + 0x01, 0x20, 0x26, 0x36, 0x34, 0x3F, 0x01, 0x27, 0x26, 0x27, 0x33, 0x17, 0x16, 0x33, 0x36, 0x3F, + 0x02, 0x32, 0x14, 0x06, 0x16, 0x12, 0x14, 0x2B, 0x01, 0x27, 0x26, 0x06, 0x0F, 0x01, 0x23, 0x45, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x7B, 0x58, 0x58, 0x4D, 0x4F, 0x05, 0x7A, + 0x34, 0x34, 0x02, 0x01, 0x33, 0x32, 0x3C, 0x3C, 0xA1, 0x01, 0xB0, 0x3E, 0x3F, 0x39, 0x3B, 0x02, + 0x3A, 0x38, 0x3F, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x60, + 0x02, 0x87, 0x88, 0x79, 0x7A, 0x06, 0x54, 0x54, 0x01, 0x53, 0x53, 0x01, 0x01, 0xFB, 0x04, 0xFE, + 0xF8, 0x02, 0x5B, 0x5A, 0x03, 0x59, 0x59, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, + 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, + 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, + 0x01, 0x10, 0x36, 0x01, 0x35, 0x27, 0x26, 0x34, 0x3B, 0x01, 0x17, 0x16, 0x36, 0x3F, 0x01, 0x33, + 0x03, 0x15, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, + 0x36, 0x5E, 0x5F, 0x3C, 0x3D, 0x3D, 0x3D, 0x03, 0x3B, 0x3B, 0x77, 0xBE, 0x68, 0x03, 0x3F, 0x46, + 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6E, 0x96, 0x95, 0x01, 0x67, 0x67, + 0x03, 0x66, 0x65, 0xFE, 0xD3, 0xDA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, + 0x03, 0x4B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x00, 0x01, 0x21, 0x22, 0x15, 0x30, 0x11, + 0x21, 0x17, 0x21, 0x11, 0x10, 0x25, 0x21, 0x01, 0x11, 0x33, 0x11, 0x21, 0x15, 0x03, 0xBB, 0xFD, + 0x77, 0xED, 0x03, 0x76, 0x31, 0xFC, 0x28, 0x01, 0x1E, 0x02, 0xBA, 0xFD, 0x5C, 0x68, 0x01, 0x08, + 0x03, 0x1A, 0xEE, 0xFD, 0xC2, 0x31, 0x02, 0x6F, 0x01, 0x1E, 0x01, 0xFD, 0x36, 0x02, 0x07, 0xFE, + 0x50, 0x57, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, 0x03, 0x4B, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x27, 0x00, 0x32, 0x00, 0x00, 0x05, 0x11, 0x34, 0x27, 0x30, 0x21, 0x11, 0x07, + 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x07, + 0x06, 0x07, 0x06, 0x07, 0x1E, 0x02, 0x15, 0x07, 0x23, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x13, + 0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x23, 0x15, 0x33, 0x36, 0x03, 0xBB, 0xED, 0xFD, 0x77, 0x31, + 0x02, 0xBA, 0x01, 0x1E, 0xFD, 0x2A, 0x77, 0x76, 0x15, 0x49, 0x20, 0x35, 0x08, 0x04, 0x06, 0x13, + 0x66, 0x0C, 0x01, 0x1F, 0x2E, 0x65, 0x3D, 0x3D, 0x2A, 0x56, 0x28, 0x2E, 0x19, 0x99, 0x3C, 0x20, + 0x10, 0x56, 0x4F, 0x46, 0x47, 0x12, 0x02, 0x3E, 0xED, 0x01, 0xFC, 0xD4, 0x31, 0x03, 0x8E, 0xFE, + 0xE1, 0xFD, 0x91, 0xC4, 0x02, 0x07, 0x01, 0x04, 0x13, 0x21, 0x44, 0x1D, 0x19, 0x58, 0x15, 0x02, + 0x01, 0x13, 0x2D, 0xA2, 0x01, 0x01, 0x3D, 0x81, 0x1A, 0x01, 0x01, 0xDA, 0x01, 0x2D, 0x08, 0x3A, + 0x29, 0x0F, 0x08, 0x01, 0x85, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC, + 0x03, 0x13, 0x00, 0x09, 0x00, 0x11, 0x00, 0x26, 0x00, 0x32, 0x00, 0x00, 0x37, 0x21, 0x34, 0x10, + 0x35, 0x34, 0x27, 0x21, 0x04, 0x11, 0x23, 0x10, 0x25, 0x21, 0x16, 0x15, 0x11, 0x21, 0x37, 0x35, + 0x37, 0x36, 0x22, 0x2B, 0x01, 0x3D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x0F, 0x01, 0x3B, 0x01, 0x1D, + 0x01, 0x2B, 0x01, 0x25, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x45, + 0x03, 0x76, 0x45, 0xFE, 0x2D, 0xFE, 0xA2, 0x31, 0x01, 0x8F, 0x01, 0xD3, 0x76, 0xFC, 0x28, 0xA7, + 0x68, 0x68, 0x01, 0x5B, 0x5D, 0x90, 0x91, 0x6C, 0x6D, 0x71, 0x70, 0xA0, 0xA0, 0x01, 0x75, 0x27, + 0x28, 0x63, 0x63, 0x8B, 0x8A, 0x27, 0x69, 0x01, 0xA4, 0x69, 0x44, 0x01, 0x02, 0xFE, 0xA4, 0x01, + 0x8C, 0x03, 0x01, 0x75, 0xFD, 0x58, 0xBB, 0x24, 0x80, 0x80, 0x21, 0x21, 0x1F, 0x1E, 0x85, 0x86, + 0x20, 0x22, 0xC3, 0xC3, 0xA1, 0xA3, 0x20, 0x22, 0x00, 0x05, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC, + 0x03, 0x13, 0x00, 0x08, 0x00, 0x10, 0x00, 0x2B, 0x00, 0x37, 0x00, 0x44, 0x00, 0x00, 0x37, 0x21, + 0x11, 0x10, 0x25, 0x30, 0x21, 0x06, 0x15, 0x03, 0x11, 0x34, 0x37, 0x21, 0x04, 0x19, 0x01, 0x01, + 0x35, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, + 0x16, 0x23, 0x2F, 0x01, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x23, 0x37, 0x32, 0x36, 0x37, 0x36, 0x35, + 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x05, 0x35, 0x37, 0x36, 0x26, 0x2B, 0x01, 0x35, 0x21, 0x15, + 0x03, 0x17, 0x15, 0x45, 0x03, 0x76, 0xFE, 0xA2, 0xFE, 0x2D, 0x45, 0x31, 0x76, 0x01, 0xD3, 0x01, + 0x8F, 0xFE, 0x1E, 0x65, 0x6F, 0x15, 0x46, 0x10, 0x05, 0x04, 0x0D, 0x4F, 0x09, 0x09, 0x1F, 0x1D, + 0x3A, 0x06, 0x01, 0x30, 0x2F, 0x22, 0x37, 0x1E, 0x29, 0x14, 0x4E, 0x82, 0x34, 0x19, 0x0E, 0x13, + 0x0A, 0x22, 0x07, 0x38, 0x37, 0xFE, 0x3E, 0x68, 0x68, 0x01, 0x5C, 0x5C, 0x01, 0x20, 0xD8, 0xE1, + 0x27, 0x01, 0x5D, 0x01, 0x5B, 0x03, 0x01, 0x44, 0xFD, 0x58, 0x02, 0xA8, 0x75, 0x01, 0x03, 0xFE, + 0x74, 0xFE, 0x71, 0x01, 0x5C, 0xC5, 0x01, 0x04, 0x0C, 0x43, 0x15, 0x1D, 0x44, 0x10, 0x04, 0x06, + 0x14, 0x2B, 0x56, 0x10, 0x01, 0x01, 0x34, 0x52, 0x1C, 0x01, 0x01, 0xA5, 0xE3, 0x04, 0x06, 0x0A, + 0x20, 0x2C, 0x04, 0x01, 0x65, 0xE3, 0x47, 0x80, 0x80, 0x01, 0x42, 0x3D, 0xFE, 0xF5, 0x01, 0x41, + 0x00, 0x04, 0x00, 0x14, 0x00, 0x52, 0x03, 0xEC, 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x64, + 0x00, 0x70, 0x00, 0x00, 0x25, 0x11, 0x21, 0x22, 0x15, 0x30, 0x15, 0x14, 0x33, 0x11, 0x21, 0x32, + 0x15, 0x11, 0x14, 0x27, 0x21, 0x22, 0x26, 0x3D, 0x01, 0x34, 0x36, 0x13, 0x26, 0x27, 0x26, 0x27, + 0x26, 0x37, 0x33, 0x36, 0x37, 0x36, 0x33, 0x16, 0x17, 0x16, 0x17, 0x16, 0x37, 0x36, 0x37, 0x36, + 0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x34, 0x37, 0x36, 0x37, + 0x36, 0x37, 0x36, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x0F, 0x01, 0x22, 0x06, 0x23, + 0x27, 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, + 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x27, 0x37, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, + 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x03, 0xBB, 0xFD, 0x2A, 0xA0, 0xA0, 0x02, 0xEE, 0x19, 0x19, 0xFD, + 0x12, 0x57, 0x7A, 0x7A, 0xCA, 0x38, 0x1D, 0x16, 0x08, 0x03, 0x01, 0x02, 0x0F, 0x0C, 0x1E, 0x01, + 0x02, 0x04, 0x0C, 0x2B, 0x0F, 0x0E, 0x18, 0x0C, 0x09, 0x04, 0x15, 0x32, 0x23, 0x12, 0x1C, 0x0E, + 0x09, 0x03, 0x01, 0x01, 0x09, 0x21, 0x0F, 0x14, 0x2E, 0x2A, 0x13, 0x0F, 0x0C, 0x08, 0x0B, 0x05, + 0x02, 0x01, 0x02, 0x03, 0x36, 0x03, 0x02, 0x03, 0x08, 0x0D, 0x23, 0x16, 0x0E, 0x10, 0x01, 0x01, + 0x07, 0x0B, 0x32, 0x25, 0x13, 0x26, 0x0F, 0x09, 0x01, 0x01, 0x0F, 0x11, 0x24, 0x21, 0x2A, 0xE3, + 0x20, 0x20, 0x52, 0x50, 0x71, 0x71, 0x84, 0x02, 0x00, 0xAF, 0xA2, 0xAF, 0x02, 0x32, 0x19, 0xFD, + 0xCE, 0x19, 0x01, 0x84, 0x5C, 0xA2, 0x5C, 0x85, 0xFE, 0x29, 0x04, 0x1E, 0x18, 0x26, 0x0F, 0x01, + 0x02, 0x01, 0x03, 0x05, 0x0B, 0x29, 0x06, 0x02, 0x03, 0x04, 0x11, 0x0B, 0x0D, 0x0A, 0x06, 0x12, + 0x0D, 0x0A, 0x07, 0x0C, 0x18, 0x0D, 0x10, 0x06, 0x18, 0x05, 0x27, 0x14, 0x09, 0x03, 0x0A, 0x0D, + 0x06, 0x09, 0x09, 0x0D, 0x0F, 0x14, 0x0C, 0x06, 0x03, 0x02, 0x04, 0x10, 0x0A, 0x11, 0x08, 0x09, + 0x0E, 0x0C, 0x07, 0x0C, 0x0C, 0x0A, 0x07, 0x0F, 0x20, 0x11, 0x18, 0x1E, 0x1A, 0x1E, 0x0C, 0x0B, + 0x03, 0xAA, 0xA5, 0x89, 0x8A, 0x1C, 0x1B, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, 0x53, 0x03, 0xEC, + 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x38, 0x00, 0x65, 0x00, 0x00, 0x01, 0x30, + 0x21, 0x11, 0x21, 0x32, 0x3D, 0x01, 0x34, 0x27, 0x32, 0x16, 0x1D, 0x01, 0x14, 0x06, 0x23, 0x21, + 0x26, 0x35, 0x11, 0x34, 0x33, 0x01, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, + 0x17, 0x1E, 0x01, 0x1F, 0x01, 0x23, 0x2A, 0x01, 0x2E, 0x01, 0x23, 0x27, 0x15, 0x37, 0x32, 0x37, + 0x36, 0x27, 0x2E, 0x01, 0x2B, 0x01, 0x15, 0x05, 0x26, 0x27, 0x37, 0x32, 0x3F, 0x01, 0x16, 0x17, + 0x1E, 0x01, 0x37, 0x36, 0x27, 0x2E, 0x04, 0x37, 0x3E, 0x01, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, + 0x06, 0x27, 0x26, 0x27, 0x26, 0x0E, 0x01, 0x1E, 0x02, 0x17, 0x16, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x03, 0x1B, 0xFD, 0x2A, 0x02, 0xD6, 0xA0, 0xA0, 0x57, 0x7A, 0x7A, 0x57, 0xFD, 0x12, 0x19, 0x19, + 0x01, 0xD3, 0x47, 0x44, 0x11, 0x3E, 0x18, 0x21, 0x0B, 0x0C, 0x43, 0x04, 0x17, 0x1C, 0x1E, 0x16, + 0x26, 0x26, 0x03, 0x4D, 0x18, 0x1E, 0x11, 0x25, 0x3A, 0x0C, 0x22, 0x08, 0x03, 0x1B, 0x3E, 0x29, + 0xFE, 0xAC, 0x0D, 0x04, 0x02, 0x02, 0x1E, 0x1D, 0x03, 0x02, 0x0C, 0x4C, 0x13, 0x20, 0x07, 0x04, + 0x1B, 0x56, 0x2D, 0x1C, 0x01, 0x02, 0x44, 0x35, 0x49, 0x1F, 0x10, 0x03, 0x41, 0x01, 0x06, 0x0A, + 0x16, 0x3C, 0x18, 0x0C, 0x16, 0x5D, 0x15, 0x33, 0x03, 0x2B, 0x1E, 0x34, 0x59, 0x02, 0x84, 0xFE, + 0x00, 0xAF, 0xA2, 0xAF, 0x32, 0x85, 0x5C, 0xA2, 0x5C, 0x84, 0x01, 0x17, 0x02, 0x32, 0x19, 0xFE, + 0x2F, 0x01, 0x45, 0x01, 0x02, 0x19, 0x22, 0x32, 0x39, 0x0B, 0x08, 0x0F, 0x27, 0x2F, 0x24, 0x75, + 0x12, 0x01, 0x88, 0xBB, 0x04, 0x09, 0x2A, 0x0F, 0x0D, 0x53, 0x8A, 0x17, 0x1E, 0x04, 0x03, 0x03, + 0x0C, 0x04, 0x26, 0x0E, 0x0C, 0x14, 0x1A, 0x0E, 0x0E, 0x16, 0x16, 0x2C, 0x1A, 0x2D, 0x2D, 0x2A, + 0x16, 0x1D, 0x06, 0x04, 0x01, 0x1A, 0x09, 0x11, 0x09, 0x17, 0x18, 0x0D, 0x17, 0x0C, 0x1B, 0x71, + 0x1B, 0x12, 0x01, 0x03, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, + 0x00, 0x1B, 0x00, 0x27, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, + 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, + 0x33, 0x35, 0x33, 0x15, 0x33, 0x15, 0x23, 0x15, 0x23, 0x35, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, + 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, + 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC5, 0x4E, 0xC5, 0xC4, 0x50, 0xC4, 0x03, 0x3F, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0xC4, 0xC5, 0x4E, 0xC5, 0xC5, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, + 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x35, 0x21, 0x15, 0x02, + 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, 0x03, 0x3F, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0x71, 0x4E, 0x4E, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x1B, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x20, 0x0E, 0x01, 0x10, 0x1E, 0x01, 0x20, 0x3E, 0x01, 0x10, 0x26, 0x01, 0x12, + 0x37, 0x33, 0x13, 0x12, 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, + 0x27, 0x26, 0x2F, 0x01, 0x06, 0x07, 0x06, 0x32, 0x02, 0x86, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xFD, 0xA0, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, + 0xD1, 0x2B, 0x37, 0x33, 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, + 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0x9A, 0x01, 0x1A, + 0xEB, 0xFE, 0xFE, 0xFE, 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, + 0x02, 0xBE, 0x02, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, + 0x00, 0x20, 0x00, 0x2B, 0x00, 0x35, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, + 0x0E, 0x01, 0x20, 0x26, 0x01, 0x30, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x23, 0x27, + 0x19, 0x01, 0x33, 0x32, 0x37, 0x3E, 0x01, 0x35, 0x26, 0x07, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x1E, + 0x02, 0x15, 0x06, 0x27, 0x23, 0x35, 0x33, 0x16, 0x17, 0x16, 0x14, 0x07, 0x06, 0x14, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x01, 0xF7, 0x0A, 0x3A, 0x05, 0x04, 0x19, + 0x20, 0x3B, 0x1D, 0x8B, 0x81, 0x4E, 0xAF, 0x29, 0x3E, 0x4E, 0x01, 0xAE, 0x0D, 0x47, 0x46, 0x46, + 0x52, 0x2C, 0x1B, 0x01, 0xB7, 0x27, 0x4C, 0x4C, 0x07, 0x2C, 0x1E, 0x16, 0xFE, 0x01, 0x0C, 0xE2, + 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x6D, 0x06, 0x21, 0x40, 0x2A, 0x24, 0x30, + 0x09, 0x05, 0x01, 0xFE, 0xFB, 0xFE, 0xFD, 0x03, 0x05, 0x4F, 0x41, 0x5B, 0x9B, 0x01, 0x8C, 0x01, + 0x0B, 0x21, 0x1A, 0x3E, 0xDA, 0x79, 0x01, 0x01, 0x0B, 0x54, 0x0E, 0x0A, 0x00, 0x02, 0x00, 0x14, + 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x29, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, + 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x36, 0x14, 0x3B, 0x01, 0x37, 0x36, 0x37, 0x36, + 0x1F, 0x01, 0x33, 0x32, 0x34, 0x02, 0x26, 0x36, 0x34, 0x23, 0x0F, 0x01, 0x06, 0x07, 0x22, 0x2F, + 0x01, 0x23, 0x16, 0x1F, 0x01, 0x07, 0x14, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x7B, 0x3D, 0x3F, 0x38, 0x3A, 0x01, 0x02, 0x3A, 0x39, 0x3F, 0x3E, 0xB0, 0x01, 0xA1, + 0x3C, 0x3C, 0x32, 0x33, 0x01, 0x02, 0x34, 0x34, 0x7A, 0x05, 0x4F, 0x4D, 0x58, 0xFE, 0x01, 0x0C, + 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x62, 0x02, 0x59, 0x59, 0x02, 0x01, 0x5A, + 0x5B, 0x02, 0x01, 0x08, 0x04, 0xFB, 0x01, 0x01, 0x53, 0x53, 0x01, 0x54, 0x54, 0x06, 0x7A, 0x79, + 0x88, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, + 0x00, 0x1B, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, + 0x01, 0x15, 0x33, 0x35, 0x13, 0x23, 0x07, 0x0E, 0x01, 0x2F, 0x01, 0x23, 0x22, 0x16, 0x1F, 0x01, + 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x36, 0x68, + 0xBE, 0x77, 0x3B, 0x3C, 0x02, 0x3D, 0x3D, 0x3D, 0x3D, 0x01, 0x5F, 0x5E, 0x03, 0x70, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6D, 0xDA, 0x01, 0x2D, 0x65, + 0x66, 0x03, 0x67, 0x67, 0x01, 0x95, 0x96, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, + 0x03, 0x4A, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x05, 0x21, 0x11, 0x10, 0x05, 0x21, 0x01, 0x21, + 0x35, 0x21, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x14, 0x02, 0xC4, 0xFD, 0x5C, 0x01, 0x70, + 0xFE, 0xF8, 0x68, 0x41, 0x02, 0x77, 0x01, 0x14, 0x01, 0xFD, 0x38, 0x57, 0x01, 0xB0, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, 0x03, 0x49, 0x00, 0x05, 0x00, 0x20, 0x00, 0x2B, + 0x00, 0x00, 0x17, 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, 0x01, + 0x33, 0x37, 0x2E, 0x02, 0x27, 0x34, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, + 0x2B, 0x01, 0x05, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x16, 0x17, 0x16, 0x15, 0x14, 0x14, 0x02, 0xC4, + 0x01, 0x14, 0xFD, 0x2A, 0x69, 0x19, 0x2E, 0x28, 0x56, 0x2A, 0x3D, 0x3D, 0x01, 0x65, 0x2C, 0x20, + 0x0D, 0x66, 0x13, 0x06, 0x04, 0x09, 0x34, 0x20, 0x49, 0x15, 0x76, 0x77, 0x01, 0x02, 0x0C, 0x47, + 0x46, 0x4F, 0x56, 0x10, 0x20, 0x41, 0x03, 0x8A, 0xFE, 0xED, 0xFD, 0x89, 0xC2, 0xDA, 0x01, 0x01, + 0x1A, 0x81, 0x3D, 0x01, 0x01, 0xA3, 0x2C, 0x13, 0x01, 0x02, 0x13, 0x5A, 0x1A, 0x1C, 0x44, 0x21, + 0x13, 0x04, 0x01, 0xDA, 0x02, 0x85, 0x01, 0x08, 0x0F, 0x29, 0x3A, 0x00, 0x00, 0x03, 0x00, 0x14, + 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x08, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x00, 0x05, 0x21, + 0x11, 0x10, 0x21, 0x30, 0x21, 0x32, 0x15, 0x01, 0x21, 0x35, 0x23, 0x13, 0x35, 0x21, 0x15, 0x33, + 0x32, 0x22, 0x0F, 0x01, 0x05, 0x21, 0x35, 0x23, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x8A, + 0x01, 0xEC, 0x62, 0xFC, 0xCF, 0x01, 0x40, 0xE1, 0xD9, 0xFE, 0xDF, 0x5D, 0x5C, 0x01, 0x67, 0x68, + 0x01, 0x75, 0x01, 0x15, 0xC6, 0x4F, 0x05, 0x01, 0x89, 0x01, 0x8A, 0x63, 0xFD, 0xE1, 0x42, 0x01, + 0x0B, 0x3D, 0x42, 0x80, 0x80, 0x48, 0x42, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, + 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x07, 0x00, 0x22, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x00, + 0x17, 0x11, 0x34, 0x37, 0x21, 0x20, 0x19, 0x01, 0x01, 0x15, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, + 0x02, 0x32, 0x35, 0x26, 0x27, 0x26, 0x27, 0x26, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, + 0x23, 0x27, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, 0x07, 0x0E, 0x01, 0x05, + 0x21, 0x35, 0x27, 0x13, 0x35, 0x21, 0x15, 0x33, 0x32, 0x14, 0x0F, 0x01, 0x14, 0x62, 0x01, 0xEC, + 0x01, 0x8A, 0xFE, 0x1E, 0x4E, 0x14, 0x29, 0x1E, 0x37, 0x22, 0x2F, 0x2F, 0x06, 0x3A, 0x1D, 0x1F, + 0x09, 0x09, 0x4E, 0x0E, 0x04, 0x05, 0x0F, 0x47, 0x15, 0x6F, 0x65, 0x82, 0x34, 0x37, 0x38, 0x07, + 0x23, 0x09, 0x13, 0x0D, 0x1A, 0xFD, 0xD6, 0x01, 0x40, 0xE1, 0xD8, 0xFE, 0xE0, 0x5C, 0x5C, 0x67, + 0x68, 0x05, 0x02, 0xB0, 0x62, 0x01, 0xFE, 0x76, 0xFE, 0x77, 0x01, 0x56, 0xC5, 0xA5, 0x01, 0x01, + 0x1C, 0x52, 0x34, 0x01, 0x01, 0x0E, 0x58, 0x2C, 0x13, 0x06, 0x04, 0x0F, 0x45, 0x1E, 0x14, 0x42, + 0x0D, 0x04, 0x01, 0xA7, 0x65, 0x01, 0x04, 0x2C, 0x21, 0x09, 0x07, 0x03, 0xE3, 0x41, 0x01, 0x01, + 0x0B, 0x3D, 0x42, 0x01, 0x80, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, + 0x02, 0xAB, 0x00, 0x08, 0x00, 0x37, 0x00, 0x3D, 0x00, 0x00, 0x13, 0x30, 0x21, 0x11, 0x21, 0x22, + 0x3D, 0x01, 0x34, 0x05, 0x37, 0x34, 0x27, 0x26, 0x27, 0x26, 0x07, 0x06, 0x07, 0x0E, 0x01, 0x17, + 0x1E, 0x01, 0x17, 0x16, 0x14, 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x22, 0x17, + 0x1E, 0x01, 0x17, 0x16, 0x37, 0x36, 0x27, 0x26, 0x27, 0x2E, 0x02, 0x37, 0x36, 0x33, 0x32, 0x1F, + 0x02, 0x33, 0x35, 0x23, 0x11, 0x23, 0xD6, 0x03, 0x16, 0xFC, 0xEA, 0xC2, 0x01, 0xC6, 0x02, 0x01, + 0x0C, 0x3A, 0x2B, 0x2D, 0x13, 0x10, 0x2B, 0x01, 0x33, 0x17, 0x55, 0x15, 0x04, 0x09, 0x14, 0x58, + 0x0C, 0x04, 0x02, 0x02, 0x26, 0x14, 0x01, 0x03, 0x08, 0x33, 0x38, 0x5F, 0x20, 0x10, 0x01, 0x03, + 0x3C, 0x12, 0x59, 0x11, 0x01, 0x02, 0x39, 0x2C, 0x09, 0x02, 0x9D, 0xE2, 0xA2, 0x40, 0x02, 0xAB, + 0xFD, 0xB2, 0xD2, 0xAA, 0xD2, 0xDC, 0x03, 0x07, 0x0B, 0x38, 0x10, 0x0C, 0x09, 0x04, 0x08, 0x19, + 0x6C, 0x17, 0x0B, 0x17, 0x11, 0x07, 0x17, 0x0A, 0x1A, 0x0A, 0x29, 0x0C, 0x04, 0x04, 0x02, 0x10, + 0x25, 0x37, 0x04, 0x06, 0x37, 0x1D, 0x1C, 0x3F, 0x19, 0x08, 0x16, 0x13, 0x0B, 0x1F, 0x2B, 0x04, + 0xE9, 0x37, 0x01, 0x13, 0x00, 0x04, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, 0x02, 0xAB, 0x00, 0x07, + 0x00, 0x1F, 0x00, 0x2A, 0x00, 0x58, 0x00, 0x00, 0x01, 0x32, 0x1D, 0x01, 0x14, 0x23, 0x21, 0x11, + 0x01, 0x33, 0x35, 0x17, 0x1E, 0x03, 0x3B, 0x01, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x36, 0x37, 0x36, + 0x27, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x16, 0x17, 0x16, 0x07, + 0x06, 0x05, 0x16, 0x37, 0x36, 0x37, 0x3E, 0x01, 0x27, 0x2E, 0x03, 0x3E, 0x01, 0x17, 0x16, 0x17, + 0x30, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x06, 0x1E, 0x03, 0x17, 0x16, + 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x07, 0x06, 0x23, 0x07, 0x16, 0x03, 0x2A, 0xC2, 0xC2, 0xFC, + 0xEA, 0x01, 0xEC, 0x41, 0x11, 0x1F, 0x17, 0x4D, 0x02, 0x27, 0x26, 0x16, 0x1E, 0x1C, 0x17, 0x04, + 0x43, 0x0C, 0x0B, 0x21, 0x18, 0x3E, 0x0F, 0x46, 0x47, 0x66, 0x25, 0x29, 0x3E, 0x1B, 0x03, 0x08, + 0x22, 0x0C, 0xFE, 0x4D, 0x22, 0x59, 0x34, 0x1E, 0x2B, 0x03, 0x33, 0x16, 0x5C, 0x16, 0x0C, 0x18, + 0x3C, 0x16, 0x0B, 0x05, 0x22, 0x21, 0x01, 0x03, 0x10, 0x1F, 0x49, 0x36, 0x43, 0x02, 0x01, 0x1C, + 0x2D, 0x56, 0x1B, 0x04, 0x07, 0x20, 0x13, 0x4B, 0x0D, 0x01, 0x04, 0x1D, 0x1E, 0x02, 0x02, 0x04, + 0x02, 0xAB, 0xD2, 0xAA, 0xD2, 0x02, 0x4E, 0xFE, 0x39, 0x89, 0x01, 0x01, 0x11, 0x75, 0x01, 0x25, + 0x2F, 0x27, 0x0F, 0x08, 0x0C, 0x38, 0x33, 0x21, 0x19, 0x02, 0x01, 0x8A, 0x53, 0x0D, 0x0F, 0x2A, + 0x09, 0x04, 0x8A, 0x3A, 0x03, 0x01, 0x12, 0x1B, 0x71, 0x1B, 0x0C, 0x17, 0x0D, 0x18, 0x17, 0x09, + 0x11, 0x09, 0x1A, 0x01, 0x01, 0x07, 0x1E, 0x15, 0x29, 0x01, 0x2D, 0x2D, 0x1A, 0x2C, 0x16, 0x16, + 0x0D, 0x0F, 0x1A, 0x14, 0x0C, 0x0D, 0x27, 0x04, 0x0C, 0x03, 0x03, 0x04, 0x1E, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x15, 0x33, 0x15, + 0x33, 0x35, 0x33, 0x35, 0x23, 0x35, 0x23, 0x15, 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC4, 0x50, 0xC4, 0xC5, 0x4E, 0x03, 0x70, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0x4F, 0xC5, 0xC5, 0x4E, 0xC5, 0xC4, + 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00, + 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x21, 0x35, 0x21, + 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, + 0xFE, 0x28, 0x03, 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, + 0x71, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, + 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x85, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0xAF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x10, 0x00, 0xE2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0D, + 0x01, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x01, 0x3F, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x01, 0x00, 0x20, 0x00, 0x42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E, + 0x00, 0x75, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x20, 0x00, 0x8D, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x05, 0x00, 0x1A, 0x00, 0xF3, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x20, + 0x01, 0x1D, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6D, + 0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x20, 0x00, 0x50, + 0x00, 0x72, 0x00, 0x6F, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x00, 0x59, 0x75, + 0x7A, 0x75, 0x20, 0x45, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x50, 0x72, 0x6F, 0x6A, + 0x65, 0x63, 0x74, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, + 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, + 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, + 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, + 0x6C, 0x00, 0x61, 0x00, 0x72, 0x00, 0x00, 0x52, 0x65, 0x67, 0x75, 0x6C, 0x61, 0x72, 0x00, 0x00, + 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, + 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F, + 0x6E, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, + 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, + 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, + 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, + 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x00, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x00, 0x00, + 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, + 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F, + 0x6E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xB5, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04, + 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B, 0x01, 0x0C, + 0x01, 0x0D, 0x01, 0x0E, 0x01, 0x0F, 0x01, 0x10, 0x01, 0x11, 0x01, 0x12, 0x01, 0x13, 0x01, 0x14, + 0x01, 0x15, 0x01, 0x16, 0x01, 0x17, 0x01, 0x18, 0x01, 0x19, 0x01, 0x1A, 0x01, 0x1B, 0x07, 0x75, + 0x6E, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x30, 0x30, 0x30, 0x44, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x31, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x33, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x35, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x37, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x39, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x42, 0x33, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x42, 0x34, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x31, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x33, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x35, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x37, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x39, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x46, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x46, 0x30, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F, }}; } // namespace FileSys::SystemArchive::SharedFontData diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.h b/src/core/file_sys/system_archive/data/font_nintendo_extended.h index 2089f3db9..edb9df914 100644 --- a/src/core/file_sys/system_archive/data/font_nintendo_extended.h +++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.h @@ -8,6 +8,6 @@ namespace FileSys::SystemArchive::SharedFontData { -extern const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED; +extern const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED; } // namespace FileSys::SystemArchive::SharedFontData diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index aa313de66..7bfbc9a67 100644 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -12,17 +12,17 @@ namespace SystemVersionData { // This section should reflect the best system version to describe yuzu's HLE api. // TODO(DarkLordZach): Update when HLE gets better. -constexpr u8 VERSION_MAJOR = 10; +constexpr u8 VERSION_MAJOR = 11; constexpr u8 VERSION_MINOR = 0; -constexpr u8 VERSION_MICRO = 2; +constexpr u8 VERSION_MICRO = 0; -constexpr u8 REVISION_MAJOR = 1; +constexpr u8 REVISION_MAJOR = 5; constexpr u8 REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "f90143fa8bbc061d4f68c35f95f04f8080c0ecdc"; -constexpr char DISPLAY_VERSION[] = "10.0.2"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 10.0.2-1.0"; +constexpr char VERSION_HASH[] = "34197eba8810e2edd5e9dfcfbde7b340882e856d"; +constexpr char DISPLAY_VERSION[] = "11.0.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.0-5.0"; } // namespace SystemVersionData diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index a4c3f67c4..f497e9396 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -169,11 +169,12 @@ VfsDirectory::~VfsDirectory() = default; std::optional<u8> VfsFile::ReadByte(std::size_t offset) const { u8 out{}; - std::size_t size = Read(&out, 1, offset); - if (size == 1) + const std::size_t size = Read(&out, sizeof(u8), offset); + if (size == 1) { return out; + } - return {}; + return std::nullopt; } std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const { @@ -202,7 +203,7 @@ std::string VfsFile::GetFullPath() const { return GetContainingDirectory()->GetFullPath() + "/" + GetName(); } -std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -230,7 +231,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co return dir->GetFile(vec.back()); } -std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) const { +VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const { if (IsRoot()) { return GetFileRelative(path); } @@ -238,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co return GetParentDirectory()->GetFileAbsolute(path); } -std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const { +VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -260,7 +261,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie return dir; } -std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { +VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { if (IsRoot()) { return GetDirectoryRelative(path); } @@ -268,14 +269,14 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_vie return GetParentDirectory()->GetDirectoryAbsolute(path); } -std::shared_ptr<VfsFile> VfsDirectory::GetFile(std::string_view name) const { +VirtualFile VfsDirectory::GetFile(std::string_view name) const { const auto& files = GetFiles(); const auto iter = std::find_if(files.begin(), files.end(), [&name](const auto& file1) { return name == file1->GetName(); }); return iter == files.end() ? nullptr : *iter; } -std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const { const auto& subs = GetSubdirectories(); const auto iter = std::find_if(subs.begin(), subs.end(), [&name](const auto& file1) { return name == file1->GetName(); }); @@ -300,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const { return file_total + subdir_total; } -std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -323,7 +324,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path)); } -std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) { +VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) { if (IsRoot()) { return CreateFileRelative(path); } @@ -331,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) return GetParentDirectory()->CreateFileAbsolute(path); } -std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -354,7 +355,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_ return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path)); } -std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { +VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { if (IsRoot()) { return CreateDirectoryRelative(path); } @@ -445,27 +446,27 @@ bool ReadOnlyVfsDirectory::IsReadable() const { return true; } -std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name) { +VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) { return nullptr; } -std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { +VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { return nullptr; } -std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { return nullptr; } -std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { return nullptr; } -std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { return nullptr; } diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 954094772..afd64e95c 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -91,7 +91,7 @@ public: // Resizes the file to new_size. Returns whether or not the operation was successful. virtual bool Resize(std::size_t new_size) = 0; // Gets a pointer to the directory containing this file, returning nullptr if there is none. - virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0; + virtual VirtualDir GetContainingDirectory() const = 0; // Returns whether or not the file can be written to. virtual bool IsWritable() const = 0; @@ -183,27 +183,27 @@ public: // Retrives the file located at path as if the current directory was root. Returns nullptr if // not found. - virtual std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const; + virtual VirtualFile GetFileRelative(std::string_view path) const; // Calls GetFileRelative(path) on the root of the current directory. - virtual std::shared_ptr<VfsFile> GetFileAbsolute(std::string_view path) const; + virtual VirtualFile GetFileAbsolute(std::string_view path) const; // Retrives the directory located at path as if the current directory was root. Returns nullptr // if not found. - virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const; + virtual VirtualDir GetDirectoryRelative(std::string_view path) const; // Calls GetDirectoryRelative(path) on the root of the current directory. - virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(std::string_view path) const; + virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const; // Returns a vector containing all of the files in this directory. - virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0; + virtual std::vector<VirtualFile> GetFiles() const = 0; // Returns the file with filename matching name. Returns nullptr if directory dosen't have a // file with name. - virtual std::shared_ptr<VfsFile> GetFile(std::string_view name) const; + virtual VirtualFile GetFile(std::string_view name) const; // Returns a vector containing all of the subdirectories in this directory. - virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0; + virtual std::vector<VirtualDir> GetSubdirectories() const = 0; // Returns the directory with name matching name. Returns nullptr if directory dosen't have a // directory with name. - virtual std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const; + virtual VirtualDir GetSubdirectory(std::string_view name) const; // Returns whether or not the directory can be written to. virtual bool IsWritable() const = 0; @@ -219,31 +219,31 @@ public: virtual std::size_t GetSize() const; // Returns the parent directory of this directory. Returns nullptr if this directory is root or // has no parent. - virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0; + virtual VirtualDir GetParentDirectory() const = 0; // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr // if the operation failed. - virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) = 0; + virtual VirtualDir CreateSubdirectory(std::string_view name) = 0; // Creates a new file with name name. Returns a pointer to the new file or nullptr if the // operation failed. - virtual std::shared_ptr<VfsFile> CreateFile(std::string_view name) = 0; + virtual VirtualFile CreateFile(std::string_view name) = 0; // Creates a new file at the path relative to this directory. Also creates directories if // they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path); + virtual VirtualFile CreateFileRelative(std::string_view path); // Creates a new file at the path relative to root of this directory. Also creates directories // if they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path); + virtual VirtualFile CreateFileAbsolute(std::string_view path); // Creates a new directory at the path relative to this directory. Also creates directories if // they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path); + virtual VirtualDir CreateDirectoryRelative(std::string_view path); // Creates a new directory at the path relative to root of this directory. Also creates // directories if they do not exist and is supported by this implementation. Returns nullptr on // any failure. - virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path); + virtual VirtualDir CreateDirectoryAbsolute(std::string_view path); // Deletes the subdirectory with the given name and returns true on success. virtual bool DeleteSubdirectory(std::string_view name) = 0; @@ -280,12 +280,12 @@ class ReadOnlyVfsDirectory : public VfsDirectory { public: bool IsWritable() const override; bool IsReadable() const override; - std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; - std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; - std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override; - std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override; - std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override; - std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; + VirtualFile CreateFileAbsolute(std::string_view path) override; + VirtualFile CreateFileRelative(std::string_view path) override; + VirtualDir CreateDirectoryAbsolute(std::string_view path) override; + VirtualDir CreateDirectoryRelative(std::string_view path) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteSubdirectoryRecursive(std::string_view name) override; bool CleanSubdirectoryRecursive(std::string_view name) override; diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index e0ff70174..3c5a7d87a 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp @@ -46,7 +46,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> f if (files.size() == 1) return files[0]; - return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); } VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, @@ -71,20 +71,23 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, if (files.begin()->first != 0) files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first)); - return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); } std::string ConcatenatedVfsFile::GetName() const { - if (files.empty()) + if (files.empty()) { return ""; - if (!name.empty()) + } + if (!name.empty()) { return name; + } return files.begin()->second->GetName(); } std::size_t ConcatenatedVfsFile::GetSize() const { - if (files.empty()) + if (files.empty()) { return 0; + } return files.rbegin()->first + files.rbegin()->second->GetSize(); } @@ -92,9 +95,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) { return false; } -std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const { - if (files.empty()) +VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const { + if (files.empty()) { return nullptr; + } return files.begin()->second->GetContainingDirectory(); } diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h index 7a26343c0..287c72555 100644 --- a/src/core/file_sys/vfs_concat.h +++ b/src/core/file_sys/vfs_concat.h @@ -31,7 +31,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp index 338e398da..434b03cec 100644 --- a/src/core/file_sys/vfs_layered.cpp +++ b/src/core/file_sys/vfs_layered.cpp @@ -20,10 +20,10 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir if (dirs.size() == 1) return dirs[0]; - return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name))); + return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name))); } -std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const { for (const auto& layer : dirs) { const auto file = layer->GetFileRelative(path); if (file != nullptr) @@ -33,23 +33,23 @@ std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view p return nullptr; } -std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative( - std::string_view path) const { +VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const { std::vector<VirtualDir> out; for (const auto& layer : dirs) { auto dir = layer->GetDirectoryRelative(path); - if (dir != nullptr) + if (dir != nullptr) { out.push_back(std::move(dir)); + } } return MakeLayeredDirectory(std::move(out)); } -std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const { +VirtualFile LayeredVfsDirectory::GetFile(std::string_view name) const { return GetFileRelative(name); } -std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { return GetDirectoryRelative(name); } @@ -57,7 +57,7 @@ std::string LayeredVfsDirectory::GetFullPath() const { return dirs[0]->GetFullPath(); } -std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const { +std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const { std::vector<VirtualFile> out; for (const auto& layer : dirs) { for (const auto& file : layer->GetFiles()) { @@ -72,7 +72,7 @@ std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const { return out; } -std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const { +std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const { std::vector<std::string> names; for (const auto& layer : dirs) { for (const auto& sd : layer->GetSubdirectories()) { @@ -101,15 +101,15 @@ std::string LayeredVfsDirectory::GetName() const { return name.empty() ? dirs[0]->GetName() : name; } -std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const { +VirtualDir LayeredVfsDirectory::GetParentDirectory() const { return dirs[0]->GetParentDirectory(); } -std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) { +VirtualFile LayeredVfsDirectory::CreateFile(std::string_view name) { return nullptr; } diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h index 8a25c3428..6d7513ac6 100644 --- a/src/core/file_sys/vfs_layered.h +++ b/src/core/file_sys/vfs_layered.h @@ -21,20 +21,20 @@ public: /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = ""); - std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; - std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; - std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; - std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override; + VirtualFile GetFileRelative(std::string_view path) const override; + VirtualDir GetDirectoryRelative(std::string_view path) const override; + VirtualFile GetFile(std::string_view name) const override; + VirtualDir GetSubdirectory(std::string_view name) const override; std::string GetFullPath() const override; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualFile> GetFiles() const override; + std::vector<VirtualDir> GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; - std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; - std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index c96f88488..056737b54 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -9,7 +9,7 @@ namespace FileSys { -OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_, +OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_, std::string name_, VirtualDir parent_) : file(file_), offset(offset_), size(size_), name(std::move(name_)), parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} @@ -37,7 +37,7 @@ bool OffsetVfsFile::Resize(std::size_t new_size) { return true; } -std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const { +VirtualDir OffsetVfsFile::GetContainingDirectory() const { return parent; } @@ -58,10 +58,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t } std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const { - if (r_offset < size) - return file->ReadByte(offset + r_offset); + if (r_offset >= size) { + return std::nullopt; + } - return {}; + return file->ReadByte(offset + r_offset); } std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const { diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h index f7b7a3256..b2ccc5c7b 100644 --- a/src/core/file_sys/vfs_offset.h +++ b/src/core/file_sys/vfs_offset.h @@ -17,14 +17,14 @@ namespace FileSys { // the size of this wrapper. class OffsetVfsFile : public VfsFile { public: - OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0, + OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0, std::string new_name = "", VirtualDir new_parent = nullptr); ~OffsetVfsFile() override; std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -42,7 +42,7 @@ public: private: std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const; - std::shared_ptr<VfsFile> file; + VirtualFile file; std::size_t offset; std::size_t size; std::string name; diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 488687ba9..a44ce6288 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -133,8 +133,11 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ } cache.erase(old_path); - file->Open(new_path, "r+b"); - cache.insert_or_assign(new_path, std::move(file)); + if (file->Open(new_path, "r+b")) { + cache.insert_or_assign(new_path, std::move(file)); + } else { + LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); + } } else { UNREACHABLE(); return nullptr; @@ -214,9 +217,12 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, } auto file = cached.lock(); - file->Open(file_new_path, "r+b"); cache.erase(file_old_path); - cache.insert_or_assign(std::move(file_new_path), std::move(file)); + if (file->Open(file_new_path, "r+b")) { + cache.insert_or_assign(std::move(file_new_path), std::move(file)); + } else { + LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); + } } return OpenDirectory(new_path, Mode::ReadWrite); @@ -263,7 +269,7 @@ bool RealVfsFile::Resize(std::size_t new_size) { return backing->Resize(new_size); } -std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const { +VirtualDir RealVfsFile::GetContainingDirectory() const { return base.OpenDirectory(parent_path, perms); } @@ -352,7 +358,7 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& RealVfsDirectory::~RealVfsDirectory() = default; -std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile RealVfsDirectory::GetFileRelative(std::string_view path) const { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { return nullptr; @@ -360,7 +366,7 @@ std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path return base.OpenFile(full_path, perms); } -std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { +VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { return nullptr; @@ -368,20 +374,20 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string return base.OpenDirectory(full_path, perms); } -std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const { +VirtualFile RealVfsDirectory::GetFile(std::string_view name) const { return GetFileRelative(name); } -std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { return GetDirectoryRelative(name); } -std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view path) { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); return base.CreateFile(full_path, perms); } -std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); return base.CreateDirectory(full_path, perms); } @@ -391,11 +397,11 @@ bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { return base.DeleteDirectory(full_path); } -std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const { +std::vector<VirtualFile> RealVfsDirectory::GetFiles() const { return IterateEntries<RealVfsFile, VfsFile>(); } -std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const { +std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const { return IterateEntries<RealVfsDirectory, VfsDirectory>(); } @@ -411,7 +417,7 @@ std::string RealVfsDirectory::GetName() const { return path_components.back(); } -std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { +VirtualDir RealVfsDirectory::GetParentDirectory() const { if (path_components.size() <= 1) { return nullptr; } @@ -419,12 +425,12 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { return base.OpenDirectory(parent_path, perms); } -std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { const std::string subdir_path = (path + DIR_SEP).append(name); return base.CreateDirectory(subdir_path, perms); } -std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) { +VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { const std::string file_path = (path + DIR_SEP).append(name); return base.CreateFile(file_path, perms); } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 0b537b22c..23e99865e 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -50,7 +50,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -79,21 +79,21 @@ class RealVfsDirectory : public VfsDirectory { public: ~RealVfsDirectory() override; - std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; - std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; - std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; - std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override; - std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override; - std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override; + VirtualFile GetFileRelative(std::string_view path) const override; + VirtualDir GetDirectoryRelative(std::string_view path) const override; + VirtualFile GetFile(std::string_view name) const override; + VirtualDir GetSubdirectory(std::string_view name) const override; + VirtualFile CreateFileRelative(std::string_view path) override; + VirtualDir CreateDirectoryRelative(std::string_view path) override; bool DeleteSubdirectoryRecursive(std::string_view name) override; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualFile> GetFiles() const override; + std::vector<VirtualDir> GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; - std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; - std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h index 9f5a90b1b..c840b24b9 100644 --- a/src/core/file_sys/vfs_static.h +++ b/src/core/file_sys/vfs_static.h @@ -31,7 +31,7 @@ public: return true; } - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override { + VirtualDir GetContainingDirectory() const override { return parent; } @@ -54,9 +54,11 @@ public: } std::optional<u8> ReadByte(std::size_t offset) const override { - if (offset < size) - return value; - return {}; + if (offset >= size) { + return std::nullopt; + } + + return value; } std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override { diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 75fc04302..c1ec1e645 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -25,7 +25,7 @@ bool VectorVfsFile::Resize(size_t new_size) { return true; } -std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const { +VirtualDir VectorVfsFile::GetContainingDirectory() const { return parent; } @@ -68,11 +68,11 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, VectorVfsDirectory::~VectorVfsDirectory() = default; -std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const { +std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const { return files; } -std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const { +std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const { return dirs; } @@ -88,7 +88,7 @@ std::string VectorVfsDirectory::GetName() const { return name; } -std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const { +VirtualDir VectorVfsDirectory::GetParentDirectory() const { return parent; } @@ -116,11 +116,11 @@ bool VectorVfsDirectory::Rename(std::string_view name_) { return true; } -std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) { +VirtualFile VectorVfsDirectory::CreateFile(std::string_view name) { return nullptr; } diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index 95d3da2f2..2aff9ca34 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -17,9 +17,9 @@ namespace FileSys { template <std::size_t size> class ArrayVfsFile : public VfsFile { public: - explicit ArrayVfsFile(const std::array<u8, size>& data, std::string name = "", - VirtualDir parent = nullptr) - : data(data), name(std::move(name)), parent(std::move(parent)) {} + explicit ArrayVfsFile(const std::array<u8, size>& data_, std::string name_ = "", + VirtualDir parent_ = nullptr) + : data(data_), name(std::move(name_)), parent(std::move(parent_)) {} std::string GetName() const override { return name; @@ -33,7 +33,7 @@ public: return false; } - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override { + VirtualDir GetContainingDirectory() const override { return parent; } @@ -51,12 +51,12 @@ public: return read; } - std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override { return 0; } - bool Rename(std::string_view name) override { - this->name = name; + bool Rename(std::string_view new_name) override { + name = new_name; return true; } @@ -82,7 +82,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -106,17 +106,17 @@ public: VirtualDir parent = nullptr); ~VectorVfsDirectory() override; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualFile> GetFiles() const override; + std::vector<VirtualDir> GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; - std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; - std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; virtual void AddFile(VirtualFile file); virtual void AddDirectory(VirtualDir dir); diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index ccf5966d0..814fd5680 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -15,8 +15,9 @@ #include "common/hex_util.h" #include "common/string_util.h" #include "core/crypto/aes_util.h" +#include "core/crypto/key_manager.h" #include "core/crypto/xts_encryption_layer.h" -#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/content_archive.h" #include "core/file_sys/vfs_offset.h" #include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" @@ -43,7 +44,9 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t return true; } -NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { +NAX::NAX(VirtualFile file_) + : header(std::make_unique<NAXHeader>()), + file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { std::string path = Common::FS::SanitizePath(file->GetFullPath()); static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", std::regex_constants::ECMAScript | @@ -60,7 +63,8 @@ NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::m } NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) - : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { + : header(std::make_unique<NAXHeader>()), + file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { Core::Crypto::SHA256Hash hash{}; mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], @@ -148,11 +152,11 @@ NAXContentType NAX::GetContentType() const { return type; } -std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const { +std::vector<VirtualFile> NAX::GetFiles() const { return {dec_file}; } -std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const { +std::vector<VirtualDir> NAX::GetSubdirectories() const { return {}; } @@ -160,7 +164,7 @@ std::string NAX::GetName() const { return file->GetName(); } -std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const { +VirtualDir NAX::GetParentDirectory() const { return file->GetContainingDirectory(); } diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 563531bb6..63a032b68 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -9,12 +9,16 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/crypto/key_manager.h" -#include "core/file_sys/content_archive.h" #include "core/file_sys/vfs.h" -#include "core/loader/loader.h" + +namespace Loader { +enum class ResultStatus : u16; +} namespace FileSys { +class NCA; + struct NAXHeader { std::array<u8, 0x20> hmac; u64_le magic; @@ -43,13 +47,13 @@ public: NAXContentType GetContentType() const; - std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; + std::vector<VirtualFile> GetFiles() const override; - std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + std::vector<VirtualDir> GetSubdirectories() const override; std::string GetName() const override; - std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; private: Loader::ResultStatus Parse(std::string_view path); @@ -62,6 +66,6 @@ private: VirtualFile dec_file; - Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + Core::Crypto::KeyManager& keys; }; } // namespace FileSys diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp new file mode 100644 index 000000000..03bbedf8b --- /dev/null +++ b/src/core/frontend/applets/controller.cpp @@ -0,0 +1,81 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace Core::Frontend { + +ControllerApplet::~ControllerApplet() = default; + +DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_) + : service_manager{service_manager_} {} + +DefaultControllerApplet::~DefaultControllerApplet() = default; + +void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback, + const ControllerParameters& parameters) const { + LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!"); + + auto& npad = + service_manager.GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + + auto& players = Settings::values.players.GetValue(); + + const std::size_t min_supported_players = + parameters.enable_single_mode ? 1 : parameters.min_players; + + // Disconnect Handheld first. + npad.DisconnectNpadAtIndex(8); + + // Deduce the best configuration based on the input parameters. + for (std::size_t index = 0; index < players.size() - 2; ++index) { + // First, disconnect all controllers regardless of the value of keep_controllers_connected. + // This makes it easy to connect the desired controllers. + npad.DisconnectNpadAtIndex(index); + + // Only connect the minimum number of required players. + if (index >= min_supported_players) { + continue; + } + + // Connect controllers based on the following priority list from highest to lowest priority: + // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld + if (parameters.allow_pro_controller) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index); + } else if (parameters.allow_dual_joycons) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index); + } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) { + // Assign left joycons to even player indices and right joycons to odd player indices. + // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and + // a right Joycon for Player 2 in 2 Player Assist mode. + if (index % 2 == 0) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index); + } else { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); + } + } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && + !Settings::values.use_docked_mode.GetValue()) { + // We should *never* reach here under any normal circumstances. + npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld), + index); + } else { + UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!"); + } + } + + callback(); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h new file mode 100644 index 000000000..b0626a0f9 --- /dev/null +++ b/src/core/frontend/applets/controller.h @@ -0,0 +1,57 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "common/common_types.h" + +namespace Service::SM { +class ServiceManager; +} + +namespace Core::Frontend { + +using BorderColor = std::array<u8, 4>; +using ExplainText = std::array<char, 0x81>; + +struct ControllerParameters { + s8 min_players{}; + s8 max_players{}; + bool keep_controllers_connected{}; + bool enable_single_mode{}; + bool enable_border_color{}; + std::vector<BorderColor> border_colors{}; + bool enable_explain_text{}; + std::vector<ExplainText> explain_text{}; + bool allow_pro_controller{}; + bool allow_handheld{}; + bool allow_dual_joycons{}; + bool allow_left_joycon{}; + bool allow_right_joycon{}; + bool allow_gamecube_controller{}; +}; + +class ControllerApplet { +public: + virtual ~ControllerApplet(); + + virtual void ReconfigureControllers(std::function<void()> callback, + const ControllerParameters& parameters) const = 0; +}; + +class DefaultControllerApplet final : public ControllerApplet { +public: + explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_); + ~DefaultControllerApplet() override; + + void ReconfigureControllers(std::function<void()> callback, + const ControllerParameters& parameters) const override; + +private: + Service::SM::ServiceManager& service_manager; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp index 4002a9211..dceb20ff8 100644 --- a/src/core/frontend/applets/error.cpp +++ b/src/core/frontend/applets/error.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" #include "core/frontend/applets/error.h" namespace Core::Frontend { @@ -10,7 +11,7 @@ ErrorApplet::~ErrorApplet() = default; void DefaultErrorApplet::ShowError(ResultCode error, std::function<void()> finished) const { LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})", - static_cast<u32>(error.module.Value()), error.description.Value(), error.raw); + error.module.Value(), error.description.Value(), error.raw); } void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, @@ -18,7 +19,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::s LOG_CRITICAL( Service_Fatal, "Application requested error display: {:04X}-{:04X} (raw={:08X}) with timestamp={:016X}", - static_cast<u32>(error.module.Value()), error.description.Value(), error.raw, time.count()); + error.module.Value(), error.description.Value(), error.raw, time.count()); } void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_text, @@ -26,7 +27,7 @@ void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_ std::function<void()> finished) const { LOG_CRITICAL(Service_Fatal, "Application requested custom error with error_code={:04X}-{:04X} (raw={:08X})", - static_cast<u32>(error.module.Value()), error.description.Value(), error.raw); + error.module.Value(), error.description.Value(), error.raw); LOG_CRITICAL(Service_Fatal, " Main Text: {}", main_text); LOG_CRITICAL(Service_Fatal, " Detail Text: {}", detail_text); } diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp index c30b36de7..7483ffb76 100644 --- a/src/core/frontend/applets/general_frontend.cpp +++ b/src/core/frontend/applets/general_frontend.cpp @@ -53,72 +53,4 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con finished(); } -ECommerceApplet::~ECommerceApplet() = default; - -DefaultECommerceApplet::~DefaultECommerceApplet() = default; - -void DefaultECommerceApplet::ShowApplicationInformation( - std::function<void()> finished, u64 title_id, std::optional<u128> user_id, - std::optional<bool> full_display, std::optional<std::string> extra_parameter) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show application information for EShop, " - "title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}", - title_id, value[1], value[0], - full_display.has_value() ? fmt::format("{}", *full_display) : "null", - extra_parameter.value_or("null")); - finished(); -} - -void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id, - std::optional<bool> full_display) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show add on content list for EShop, " - "title_id={:016X}, user_id={:016X}{:016X}, full_display={}", - title_id, value[1], value[0], - full_display.has_value() ? fmt::format("{}", *full_display) : "null"); - finished(); -} - -void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show subscription list for EShop, title_id={:016X}, " - "user_id={:016X}{:016X}", - title_id, value[1], value[0]); - finished(); -} - -void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id) { - const auto value = user_id.value_or(u128{}); - LOG_INFO( - Service_AM, - "Application requested frontend show consumable item list for EShop, title_id={:016X}, " - "user_id={:016X}{:016X}", - title_id, value[1], value[0]); - finished(); -} - -void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id, - bool full_display) { - LOG_INFO(Service_AM, - "Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, " - "full_display={}", - user_id[1], user_id[0], full_display); - finished(); -} - -void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id, - bool full_display) { - LOG_INFO(Service_AM, - "Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, " - "full_display={}", - user_id[1], user_id[0], full_display); - finished(); -} - } // namespace Core::Frontend diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h index 4b63f828e..b713b14ee 100644 --- a/src/core/frontend/applets/general_frontend.h +++ b/src/core/frontend/applets/general_frontend.h @@ -58,55 +58,4 @@ public: void ShowAllPhotos(std::function<void()> finished) const override; }; -class ECommerceApplet { -public: - virtual ~ECommerceApplet(); - - // Shows a page with application icons, description, name, and price. - virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id = {}, - std::optional<bool> full_display = {}, - std::optional<std::string> extra_parameter = {}) = 0; - - // Shows a page with all of the add on content available for a game, with name, description, and - // price. - virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id = {}, - std::optional<bool> full_display = {}) = 0; - - // Shows a page with all of the subscriptions (recurring payments) for a game, with name, - // description, price, and renewal period. - virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id = {}) = 0; - - // Shows a page with a list of any additional game related purchasable items (DLC, - // subscriptions, etc) for a particular game, with name, description, type, and price. - virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id = {}) = 0; - - // Shows the home page of the shop. - virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0; - - // Shows the user settings page of the shop. - virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0; -}; - -class DefaultECommerceApplet : public ECommerceApplet { -public: - ~DefaultECommerceApplet() override; - - void ShowApplicationInformation(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id, std::optional<bool> full_display, - std::optional<std::string> extra_parameter) override; - void ShowAddOnContentList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id, - std::optional<bool> full_display) override; - void ShowSubscriptionList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id) override; - void ShowConsumableItemList(std::function<void()> finished, u64 title_id, - std::optional<u128> user_id) override; - void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override; - void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override; -}; - } // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp index 528295ffc..50db6a654 100644 --- a/src/core/frontend/applets/web_browser.cpp +++ b/src/core/frontend/applets/web_browser.cpp @@ -11,14 +11,22 @@ WebBrowserApplet::~WebBrowserApplet() = default; DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; -void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename, - std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { - LOG_INFO(Service_AM, - "(STUBBED) called - No suitable web browser implementation found to open website page " - "at '{}'!", - filename); - finished_callback(); +void DefaultWebBrowserApplet::OpenLocalWebPage( + std::string_view local_url, std::function<void()> extract_romfs_callback, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open local web page at {}", + local_url); + + callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); +} + +void DefaultWebBrowserApplet::OpenExternalWebPage( + std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}", + external_url); + + callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); } } // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index 110e33bc4..1c5ef19a9 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -7,22 +7,34 @@ #include <functional> #include <string_view> +#include "core/hle/service/am/applets/web_types.h" + namespace Core::Frontend { class WebBrowserApplet { public: virtual ~WebBrowserApplet(); - virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) = 0; + virtual void OpenLocalWebPage( + std::string_view local_url, std::function<void()> extract_romfs_callback, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0; + + virtual void OpenExternalWebPage( + std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0; }; class DefaultWebBrowserApplet final : public WebBrowserApplet { public: ~DefaultWebBrowserApplet() override; - void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback) const override; + + void OpenExternalWebPage(std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback) const override; }; } // namespace Core::Frontend diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 9a081fbd4..ee7a58b1c 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -21,21 +21,18 @@ public: std::mutex mutex; - bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false - - float touch_x = 0.0f; ///< Touchpad X-position - float touch_y = 0.0f; ///< Touchpad Y-position + Input::TouchStatus status; private: class Device : public Input::TouchDevice { public: explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {} - std::tuple<float, float, bool> GetStatus() const override { + Input::TouchStatus GetStatus() const override { if (auto state = touch_state.lock()) { std::lock_guard guard{state->mutex}; - return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed); + return state->status; } - return std::make_tuple(0.0f, 0.0f, false); + return {}; } private: @@ -79,34 +76,44 @@ std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsi return std::make_tuple(new_x, new_y); } -void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { - if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) +void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id) { + if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) { return; + } + if (id >= touch_state->status.size()) { + return; + } std::lock_guard guard{touch_state->mutex}; - touch_state->touch_x = static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) / - (framebuffer_layout.screen.right - framebuffer_layout.screen.left); - touch_state->touch_y = static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) / - (framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); - - touch_state->touch_pressed = true; + const float x = + static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) / + static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left); + const float y = + static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) / + static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); + + touch_state->status[id] = std::make_tuple(x, y, true); } -void EmuWindow::TouchReleased() { +void EmuWindow::TouchReleased(std::size_t id) { + if (id >= touch_state->status.size()) { + return; + } std::lock_guard guard{touch_state->mutex}; - touch_state->touch_pressed = false; - touch_state->touch_x = 0; - touch_state->touch_y = 0; + touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false); } -void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { - if (!touch_state->touch_pressed) +void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id) { + if (id >= touch_state->status.size()) { + return; + } + if (!std::get<2>(touch_state->status[id])) return; if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y); - TouchPressed(framebuffer_x, framebuffer_y); + TouchPressed(framebuffer_x, framebuffer_y, id); } void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 3e8780243..2436c6580 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -102,8 +102,8 @@ public: float render_surface_scale = 1.0f; }; - /// Polls window events - virtual void PollEvents() = 0; + /// Called from GPU thread when a frame is displayed. + virtual void OnFrameDisplayed() {} /** * Returns a GraphicsContext that the frontend provides to be used for rendering. @@ -117,18 +117,23 @@ public: * Signal that a touch pressed event has occurred (e.g. mouse click pressed) * @param framebuffer_x Framebuffer x-coordinate that was pressed * @param framebuffer_y Framebuffer y-coordinate that was pressed + * @param id Touch event ID */ - void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y); + void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id); - /// Signal that a touch released event has occurred (e.g. mouse click released) - void TouchReleased(); + /** + * Signal that a touch released event has occurred (e.g. mouse click released) + * @param id Touch event ID + */ + void TouchReleased(std::size_t id); /** * Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window) * @param framebuffer_x Framebuffer x-coordinate * @param framebuffer_y Framebuffer y-coordinate + * @param id Touch event ID */ - void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); + void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id); /** * Returns currently active configuration. diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index c1fbc235b..b9a270a55 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -14,8 +14,8 @@ namespace Layout { template <class T> static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area, float screen_aspect_ratio) { - float scale = std::min(static_cast<float>(window_area.GetWidth()), - window_area.GetHeight() / screen_aspect_ratio); + const float scale = std::min(static_cast<float>(window_area.GetWidth()), + static_cast<float>(window_area.GetHeight()) / screen_aspect_ratio); return Common::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)), static_cast<T>(std::round(scale * screen_aspect_ratio))}; } @@ -27,7 +27,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { // so just calculate them both even if the other isn't showing. FramebufferLayout res{width, height, false, {}}; - const float window_aspect_ratio = static_cast<float>(height) / width; + const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width); const float emulation_aspect_ratio = EmulationAspectRatio( static_cast<AspectRatio>(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio); @@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) { u32 width, height; - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { width = ScreenDocked::Width * res_scale; height = ScreenDocked::Height * res_scale; } else { diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 91ecc30ab..e2e3bbbb3 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -4,6 +4,7 @@ #pragma once +#include "common/common_types.h" #include "common/math_util.h" namespace Layout { diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 2b098b7c6..88ebc6497 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -21,6 +21,11 @@ enum class AnalogDirection : u8 { UP, DOWN, }; +struct AnalogProperties { + float deadzone; + float range; + float threshold; +}; /// An abstract class template for an input device (a button, an analog input, etc.). template <typename StatusType> @@ -30,7 +35,18 @@ public: virtual StatusType GetStatus() const { return {}; } - virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { + virtual StatusType GetRawStatus() const { + return GetStatus(); + } + virtual AnalogProperties GetAnalogProperties() const { + return {}; + } + virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const { + return {}; + } + virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low, + [[maybe_unused]] f32 amp_high, + [[maybe_unused]] f32 freq_high) const { return {}; } }; @@ -119,11 +135,18 @@ using ButtonDevice = InputDevice<bool>; using AnalogDevice = InputDevice<std::tuple<float, float>>; /** - * A motion device is an input device that returns a tuple of accelerometer state vector and - * gyroscope state vector. + * A vibration device is an input device that returns an unsigned byte as status. + * It represents whether the vibration device supports vibration or not. + * If the status returns 1, it supports vibration. Otherwise, it does not support vibration. + */ +using VibrationDevice = InputDevice<u8>; + +/** + * A motion status is an object that returns a tuple of accelerometer state vector, + * gyroscope state vector, rotation state vector and orientation state matrix. * * For both vectors: - * x+ is the same direction as LEFT on D-pad. + * x+ is the same direction as RIGHT on D-pad. * y+ is normal to the touch screen, pointing outward. * z+ is the same direction as UP on D-pad. * @@ -133,14 +156,34 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>; * For gyroscope state vector: * Orientation is determined by right-hand rule. * Units: deg/sec + * + * For rotation state vector + * Units: rotations + * + * For orientation state matrix + * x vector + * y vector + * z vector + */ +using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>, + std::array<Common::Vec3f, 3>>; + +/** + * A motion device is an input device that returns a motion status object + */ +using MotionDevice = InputDevice<MotionStatus>; + +/** + * A touch status is an object that returns an array of 16 tuple elements of two floats and a bool. + * The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is + * pressed. */ -using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; +using TouchStatus = std::array<std::tuple<float, float, bool>, 16>; /** - * A touch device is an input device that returns a tuple of two floats and a bool. The floats are - * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed. + * A touch device is an input device that returns a touch status object */ -using TouchDevice = InputDevice<std::tuple<float, float, bool>>; +using TouchDevice = InputDevice<TouchStatus>; /** * A mouse device is an input device that returns a tuple of two floats and four ints. diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp new file mode 100644 index 000000000..ec5fe660e --- /dev/null +++ b/src/core/frontend/input_interpreter.cpp @@ -0,0 +1,49 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +InputInterpreter::InputInterpreter(Core::System& system) + : npad{system.ServiceManager() + .GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {} + +InputInterpreter::~InputInterpreter() = default; + +void InputInterpreter::PollInput() { + const u32 button_state = npad.GetAndResetPressState(); + + previous_index = current_index; + current_index = (current_index + 1) % button_states.size(); + + button_states[current_index] = button_state; +} + +bool InputInterpreter::IsButtonPressed(HIDButton button) const { + return (button_states[current_index] & (1U << static_cast<u8>(button))) != 0; +} + +bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const { + const bool current_press = + (button_states[current_index] & (1U << static_cast<u8>(button))) != 0; + const bool previous_press = + (button_states[previous_index] & (1U << static_cast<u8>(button))) != 0; + + return current_press && !previous_press; +} + +bool InputInterpreter::IsButtonHeld(HIDButton button) const { + u32 held_buttons{button_states[0]}; + + for (std::size_t i = 1; i < button_states.size(); ++i) { + held_buttons &= button_states[i]; + } + + return (held_buttons & (1U << static_cast<u8>(button))) != 0; +} diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h new file mode 100644 index 000000000..73fc47ffb --- /dev/null +++ b/src/core/frontend/input_interpreter.h @@ -0,0 +1,141 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_types.h" + +namespace Core { +class System; +} + +namespace Service::HID { +class Controller_NPad; +} + +enum class HIDButton : u8 { + A, + B, + X, + Y, + LStick, + RStick, + L, + R, + ZL, + ZR, + Plus, + Minus, + + DLeft, + DUp, + DRight, + DDown, + + LStickLeft, + LStickUp, + LStickRight, + LStickDown, + + RStickLeft, + RStickUp, + RStickRight, + RStickDown, + + LeftSL, + LeftSR, + + RightSL, + RightSR, +}; + +/** + * The InputInterpreter class interfaces with HID to retrieve button press states. + * Input is intended to be polled every 50ms so that a button is considered to be + * held down after 400ms has elapsed since the initial button press and subsequent + * repeated presses occur every 50ms. + */ +class InputInterpreter { +public: + explicit InputInterpreter(Core::System& system); + virtual ~InputInterpreter(); + + /// Gets a button state from HID and inserts it into the array of button states. + void PollInput(); + + /** + * Checks whether the button is pressed. + * + * @param button The button to check. + * + * @returns True when the button is pressed. + */ + [[nodiscard]] bool IsButtonPressed(HIDButton button) const; + + /** + * Checks whether any of the buttons in the parameter list is pressed. + * + * @tparam HIDButton The buttons to check. + * + * @returns True when at least one of the buttons is pressed. + */ + template <HIDButton... T> + [[nodiscard]] bool IsAnyButtonPressed() { + return (IsButtonPressed(T) || ...); + } + + /** + * The specified button is considered to be pressed once + * if it is currently pressed and not pressed previously. + * + * @param button The button to check. + * + * @returns True when the button is pressed once. + */ + [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const; + + /** + * Checks whether any of the buttons in the parameter list is pressed once. + * + * @tparam T The buttons to check. + * + * @returns True when at least one of the buttons is pressed once. + */ + template <HIDButton... T> + [[nodiscard]] bool IsAnyButtonPressedOnce() const { + return (IsButtonPressedOnce(T) || ...); + } + + /** + * The specified button is considered to be held down if it is pressed in all 9 button states. + * + * @param button The button to check. + * + * @returns True when the button is held down. + */ + [[nodiscard]] bool IsButtonHeld(HIDButton button) const; + + /** + * Checks whether any of the buttons in the parameter list is held down. + * + * @tparam T The buttons to check. + * + * @returns True when at least one of the buttons is held down. + */ + template <HIDButton... T> + [[nodiscard]] bool IsAnyButtonHeld() const { + return (IsButtonHeld(T) || ...); + } + +private: + Service::HID::Controller_NPad& npad; + + /// Stores 9 consecutive button states polled from HID. + std::array<u32, 9> button_states{}; + + std::size_t previous_index{}; + std::size_t current_index{}; +}; diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp deleted file mode 100644 index 79f22a403..000000000 --- a/src/core/gdbstub/gdbstub.cpp +++ /dev/null @@ -1,1397 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic. - -#include <algorithm> -#include <atomic> -#include <climits> -#include <csignal> -#include <cstdarg> -#include <cstdio> -#include <cstring> -#include <map> -#include <numeric> -#include <fcntl.h> - -#ifdef _WIN32 -#include <winsock2.h> -// winsock2.h needs to be included first to prevent winsock.h being included by other includes -#include <io.h> -#include <iphlpapi.h> -#include <ws2tcpip.h> -#define SHUT_RDWR 2 -#else -#include <netinet/in.h> -#include <sys/select.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <unistd.h> -#endif - -#include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/gdbstub/gdbstub.h" -#include "core/hle/kernel/memory/page_table.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/loader/loader.h" -#include "core/memory.h" - -namespace GDBStub { -namespace { -constexpr int GDB_BUFFER_SIZE = 10000; - -constexpr char GDB_STUB_START = '$'; -constexpr char GDB_STUB_END = '#'; -constexpr char GDB_STUB_ACK = '+'; -constexpr char GDB_STUB_NACK = '-'; - -#ifndef SIGTRAP -constexpr u32 SIGTRAP = 5; -#endif - -#ifndef SIGTERM -constexpr u32 SIGTERM = 15; -#endif - -#ifndef MSG_WAITALL -constexpr u32 MSG_WAITALL = 8; -#endif - -constexpr u32 LR_REGISTER = 30; -constexpr u32 SP_REGISTER = 31; -constexpr u32 PC_REGISTER = 32; -constexpr u32 PSTATE_REGISTER = 33; -constexpr u32 UC_ARM64_REG_Q0 = 34; -constexpr u32 FPCR_REGISTER = 66; - -// For sample XML files see the GDB source /gdb/features -// GDB also wants the l character at the start -// This XML defines what the registers are for this specific ARM device -constexpr char target_xml[] = - R"(l<?xml version="1.0"?> -<!DOCTYPE target SYSTEM "gdb-target.dtd"> -<target version="1.0"> - <feature name="org.gnu.gdb.aarch64.core"> - <reg name="x0" bitsize="64"/> - <reg name="x1" bitsize="64"/> - <reg name="x2" bitsize="64"/> - <reg name="x3" bitsize="64"/> - <reg name="x4" bitsize="64"/> - <reg name="x5" bitsize="64"/> - <reg name="x6" bitsize="64"/> - <reg name="x7" bitsize="64"/> - <reg name="x8" bitsize="64"/> - <reg name="x9" bitsize="64"/> - <reg name="x10" bitsize="64"/> - <reg name="x11" bitsize="64"/> - <reg name="x12" bitsize="64"/> - <reg name="x13" bitsize="64"/> - <reg name="x14" bitsize="64"/> - <reg name="x15" bitsize="64"/> - <reg name="x16" bitsize="64"/> - <reg name="x17" bitsize="64"/> - <reg name="x18" bitsize="64"/> - <reg name="x19" bitsize="64"/> - <reg name="x20" bitsize="64"/> - <reg name="x21" bitsize="64"/> - <reg name="x22" bitsize="64"/> - <reg name="x23" bitsize="64"/> - <reg name="x24" bitsize="64"/> - <reg name="x25" bitsize="64"/> - <reg name="x26" bitsize="64"/> - <reg name="x27" bitsize="64"/> - <reg name="x28" bitsize="64"/> - <reg name="x29" bitsize="64"/> - <reg name="x30" bitsize="64"/> - <reg name="sp" bitsize="64" type="data_ptr"/> - - <reg name="pc" bitsize="64" type="code_ptr"/> - - <flags id="pstate_flags" size="4"> - <field name="SP" start="0" end="0"/> - <field name="" start="1" end="1"/> - <field name="EL" start="2" end="3"/> - <field name="nRW" start="4" end="4"/> - <field name="" start="5" end="5"/> - <field name="F" start="6" end="6"/> - <field name="I" start="7" end="7"/> - <field name="A" start="8" end="8"/> - <field name="D" start="9" end="9"/> - - <field name="IL" start="20" end="20"/> - <field name="SS" start="21" end="21"/> - - <field name="V" start="28" end="28"/> - <field name="C" start="29" end="29"/> - <field name="Z" start="30" end="30"/> - <field name="N" start="31" end="31"/> - </flags> - <reg name="pstate" bitsize="32" type="pstate_flags"/> - </feature> - <feature name="org.gnu.gdb.aarch64.fpu"> - </feature> -</target> -)"; - -int gdbserver_socket = -1; -bool defer_start = false; - -u8 command_buffer[GDB_BUFFER_SIZE]; -u32 command_length; - -u32 latest_signal = 0; -bool memory_break = false; - -Kernel::Thread* current_thread = nullptr; -u32 current_core = 0; - -// Binding to a port within the reserved ports range (0-1023) requires root permissions, -// so default to a port outside of that range. -u16 gdbstub_port = 24689; - -bool halt_loop = true; -bool step_loop = false; -bool send_trap = false; - -// If set to false, the server will never be started and no -// gdbstub-related functions will be executed. -std::atomic<bool> server_enabled(false); - -#ifdef _WIN32 -WSADATA InitData; -#endif - -struct Breakpoint { - bool active; - VAddr addr; - u64 len; - std::array<u8, 4> inst; -}; - -using BreakpointMap = std::map<VAddr, Breakpoint>; -BreakpointMap breakpoints_execute; -BreakpointMap breakpoints_read; -BreakpointMap breakpoints_write; - -struct Module { - std::string name; - VAddr beg; - VAddr end; -}; - -std::vector<Module> modules; -} // Anonymous namespace - -void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) { - Module module; - if (add_elf_ext) { - Common::SplitPath(name, nullptr, &module.name, nullptr); - module.name += ".elf"; - } else { - module.name = std::move(name); - } - module.beg = beg; - module.end = end; - modules.push_back(std::move(module)); -} - -static Kernel::Thread* FindThreadById(s64 id) { - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (auto& thread : threads) { - if (thread->GetThreadID() == static_cast<u64>(id)) { - current_core = thread->GetProcessorID(); - return thread.get(); - } - } - return nullptr; -} - -static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) { - if (!thread) { - return 0; - } - - const auto& thread_context = thread->GetContext64(); - - if (id < SP_REGISTER) { - return thread_context.cpu_registers[id]; - } else if (id == SP_REGISTER) { - return thread_context.sp; - } else if (id == PC_REGISTER) { - return thread_context.pc; - } else if (id == PSTATE_REGISTER) { - return thread_context.pstate; - } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { - return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0]; - } else { - return 0; - } -} - -static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) { - if (!thread) { - return; - } - - auto& thread_context = thread->GetContext64(); - - if (id < SP_REGISTER) { - thread_context.cpu_registers[id] = val; - } else if (id == SP_REGISTER) { - thread_context.sp = val; - } else if (id == PC_REGISTER) { - thread_context.pc = val; - } else if (id == PSTATE_REGISTER) { - thread_context.pstate = static_cast<u32>(val); - } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { - thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val; - } -} - -static u128 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) { - if (!thread) { - return u128{0}; - } - - auto& thread_context = thread->GetContext64(); - - if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - return thread_context.vector_registers[id - UC_ARM64_REG_Q0]; - } else if (id == FPCR_REGISTER) { - return u128{thread_context.fpcr, 0}; - } else { - return u128{0}; - } -} - -static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr) { - if (!thread) { - return; - } - - auto& thread_context = thread->GetContext64(); - - if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - thread_context.vector_registers[id - UC_ARM64_REG_Q0] = val; - } else if (id == FPCR_REGISTER) { - thread_context.fpcr = static_cast<u32>(val[0]); - } -} - -/** - * Turns hex string character into the equivalent byte. - * - * @param hex Input hex character to be turned into byte. - */ -static u8 HexCharToValue(u8 hex) { - if (hex >= '0' && hex <= '9') { - return hex - '0'; - } else if (hex >= 'a' && hex <= 'f') { - return hex - 'a' + 0xA; - } else if (hex >= 'A' && hex <= 'F') { - return hex - 'A' + 0xA; - } - - LOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex); - return 0; -} - -/** - * Turn nibble of byte into hex string character. - * - * @param n Nibble to be turned into hex character. - */ -static u8 NibbleToHex(u8 n) { - n &= 0xF; - if (n < 0xA) { - return '0' + n; - } else { - return 'a' + n - 0xA; - } -} - -/** - * Converts input hex string characters into an array of equivalent of u8 bytes. - * - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static u32 HexToInt(const u8* src, std::size_t len) { - u32 output = 0; - while (len-- > 0) { - output = (output << 4) | HexCharToValue(src[0]); - src++; - } - return output; -} - -/** - * Converts input hex string characters into an array of equivalent of u8 bytes. - * - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static u64 HexToLong(const u8* src, std::size_t len) { - u64 output = 0; - while (len-- > 0) { - output = (output << 4) | HexCharToValue(src[0]); - src++; - } - return output; -} - -/** - * Converts input array of u8 bytes into their equivalent hex string characters. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param src Pointer to array of u8 bytes. - * @param len Length of src array. - */ -static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) { - while (len-- > 0) { - u8 tmp = *src++; - *dest++ = NibbleToHex(tmp >> 4); - *dest++ = NibbleToHex(tmp); - } -} - -/** - * Converts input gdb-formatted hex string characters into an array of equivalent of u8 bytes. - * - * @param dest Pointer to buffer to store u8 bytes. - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) { - while (len-- > 0) { - *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); - src += 2; - } -} - -/** - * Convert a u32 into a gdb-formatted hex string. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param v Value to convert. - */ -static void IntToGdbHex(u8* dest, u32 v) { - for (int i = 0; i < 8; i += 2) { - dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i))); - dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1)))); - } -} - -/** - * Convert a u64 into a gdb-formatted hex string. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param v Value to convert. - */ -static void LongToGdbHex(u8* dest, u64 v) { - for (int i = 0; i < 16; i += 2) { - dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i))); - dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1)))); - } -} - -/** - * Convert a gdb-formatted hex string into a u32. - * - * @param src Pointer to hex string. - */ -static u32 GdbHexToInt(const u8* src) { - u32 output = 0; - - for (int i = 0; i < 8; i += 2) { - output = (output << 4) | HexCharToValue(src[7 - i - 1]); - output = (output << 4) | HexCharToValue(src[7 - i]); - } - - return output; -} - -/** - * Convert a gdb-formatted hex string into a u64. - * - * @param src Pointer to hex string. - */ -static u64 GdbHexToLong(const u8* src) { - u64 output = 0; - - for (int i = 0; i < 16; i += 2) { - output = (output << 4) | HexCharToValue(src[15 - i - 1]); - output = (output << 4) | HexCharToValue(src[15 - i]); - } - - return output; -} - -/** - * Convert a gdb-formatted hex string into a u128. - * - * @param src Pointer to hex string. - */ -static u128 GdbHexToU128(const u8* src) { - u128 output; - - for (int i = 0; i < 16; i += 2) { - output[0] = (output[0] << 4) | HexCharToValue(src[15 - i - 1]); - output[0] = (output[0] << 4) | HexCharToValue(src[15 - i]); - } - - for (int i = 0; i < 16; i += 2) { - output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i - 1]); - output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i]); - } - - return output; -} - -/// Read a byte from the gdb client. -static u8 ReadByte() { - u8 c; - std::size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); - if (received_size != 1) { - LOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size); - Shutdown(); - } - - return c; -} - -/// Calculate the checksum of the current command buffer. -static u8 CalculateChecksum(const u8* buffer, std::size_t length) { - return static_cast<u8>(std::accumulate(buffer, buffer + length, u8{0}, - [](u8 lhs, u8 rhs) { return u8(lhs + rhs); })); -} - -/** - * Get the map of breakpoints for a given breakpoint type. - * - * @param type Type of breakpoint map. - */ -static BreakpointMap& GetBreakpointMap(BreakpointType type) { - switch (type) { - case BreakpointType::Execute: - return breakpoints_execute; - case BreakpointType::Read: - return breakpoints_read; - case BreakpointType::Write: - return breakpoints_write; - default: - return breakpoints_read; - } -} - -/** - * Remove the breakpoint from the given address of the specified type. - * - * @param type Type of breakpoint. - * @param addr Address of breakpoint. - */ -static void RemoveBreakpoint(BreakpointType type, VAddr addr) { - BreakpointMap& p = GetBreakpointMap(type); - - const auto bp = p.find(addr); - if (bp == p.end()) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}", - bp->second.len, bp->second.addr, static_cast<int>(type)); - - if (type == BreakpointType::Execute) { - auto& system = Core::System::GetInstance(); - system.Memory().WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size()); - system.InvalidateCpuInstructionCaches(); - } - p.erase(addr); -} - -BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type) { - const BreakpointMap& p = GetBreakpointMap(type); - const auto next_breakpoint = p.lower_bound(addr); - BreakpointAddress breakpoint; - - if (next_breakpoint != p.end()) { - breakpoint.address = next_breakpoint->first; - breakpoint.type = type; - } else { - breakpoint.address = 0; - breakpoint.type = BreakpointType::None; - } - - return breakpoint; -} - -bool CheckBreakpoint(VAddr addr, BreakpointType type) { - if (!IsConnected()) { - return false; - } - - const BreakpointMap& p = GetBreakpointMap(type); - const auto bp = p.find(addr); - - if (bp == p.end()) { - return false; - } - - u64 len = bp->second.len; - - // IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints - // no matter if it's a 4-byte or 2-byte instruction. When you execute a - // Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on - // two instructions instead of the single instruction you placed the breakpoint - // on. So, as a way to make sure that execution breakpoints are only breaking - // on the instruction that was specified, set the length of an execution - // breakpoint to 1. This should be fine since the CPU should never begin executing - // an instruction anywhere except the beginning of the instruction. - if (type == BreakpointType::Execute) { - len = 1; - } - - if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { - LOG_DEBUG(Debug_GDBStub, - "Found breakpoint type {} @ {:016X}, range: {:016X}" - " - {:016X} ({:X} bytes)", - static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len); - return true; - } - - return false; -} - -/** - * Send packet to gdb client. - * - * @param packet Packet to be sent to client. - */ -static void SendPacket(const char packet) { - std::size_t sent_size = send(gdbserver_socket, &packet, 1, 0); - if (sent_size != 1) { - LOG_ERROR(Debug_GDBStub, "send failed"); - } -} - -/** - * Send reply to gdb client. - * - * @param reply Reply to be sent to client. - */ -static void SendReply(const char* reply) { - if (!IsConnected()) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "Reply: {}", reply); - - memset(command_buffer, 0, sizeof(command_buffer)); - - command_length = static_cast<u32>(strlen(reply)); - if (command_length + 4 > sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); - return; - } - - memcpy(command_buffer + 1, reply, command_length); - - u8 checksum = CalculateChecksum(command_buffer, command_length + 1); - command_buffer[0] = GDB_STUB_START; - command_buffer[command_length + 1] = GDB_STUB_END; - command_buffer[command_length + 2] = NibbleToHex(checksum >> 4); - command_buffer[command_length + 3] = NibbleToHex(checksum); - - u8* ptr = command_buffer; - u32 left = command_length + 4; - while (left > 0) { - int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0); - if (sent_size < 0) { - LOG_ERROR(Debug_GDBStub, "gdb: send failed"); - return Shutdown(); - } - - left -= sent_size; - ptr += sent_size; - } -} - -/// Handle query command from gdb client. -static void HandleQuery() { - LOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1); - - const char* query = reinterpret_cast<const char*>(command_buffer + 1); - - if (strcmp(query, "TStatus") == 0) { - SendReply("T0"); - } else if (strncmp(query, "Supported", strlen("Supported")) == 0) { - // PacketSize needs to be large enough for target xml - std::string buffer = "PacketSize=2000;qXfer:features:read+;qXfer:threads:read+"; - if (!modules.empty()) { - buffer += ";qXfer:libraries:read+"; - } - SendReply(buffer.c_str()); - } else if (strncmp(query, "Xfer:features:read:target.xml:", - strlen("Xfer:features:read:target.xml:")) == 0) { - SendReply(target_xml); - } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { - const VAddr base_address = - Core::System::GetInstance().CurrentProcess()->PageTable().GetCodeRegionStart(); - std::string buffer = fmt::format("TextSeg={:0x}", base_address); - SendReply(buffer.c_str()); - } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { - std::string val = "m"; - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (const auto& thread : threads) { - val += fmt::format("{:x},", thread->GetThreadID()); - } - val.pop_back(); - SendReply(val.c_str()); - } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { - SendReply("l"); - } else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) { - std::string buffer; - buffer += "l<?xml version=\"1.0\"?>"; - buffer += "<threads>"; - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (const auto& thread : threads) { - buffer += - fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*", - thread->GetThreadID(), thread->GetProcessorID(), thread->GetThreadID()); - } - buffer += "</threads>"; - SendReply(buffer.c_str()); - } else if (strncmp(query, "Xfer:libraries:read", strlen("Xfer:libraries:read")) == 0) { - std::string buffer; - buffer += "l<?xml version=\"1.0\"?>"; - buffer += "<library-list>"; - for (const auto& module : modules) { - buffer += - fmt::format(R"*("<library name = "{}"><segment address = "0x{:x}"/></library>)*", - module.name, module.beg); - } - buffer += "</library-list>"; - SendReply(buffer.c_str()); - } else { - SendReply(""); - } -} - -/// Handle set thread command from gdb client. -static void HandleSetThread() { - int thread_id = -1; - if (command_buffer[2] != '-') { - thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2)); - } - if (thread_id >= 1) { - current_thread = FindThreadById(thread_id); - } - if (!current_thread) { - thread_id = 1; - current_thread = FindThreadById(thread_id); - } - if (current_thread) { - SendReply("OK"); - return; - } - SendReply("E01"); -} - -/// Handle thread alive command from gdb client. -static void HandleThreadAlive() { - int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1)); - if (thread_id == 0) { - thread_id = 1; - } - if (FindThreadById(thread_id)) { - SendReply("OK"); - return; - } - SendReply("E01"); -} - -/** - * Send signal packet to client. - * - * @param signal Signal to be sent to client. - */ -static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) { - if (gdbserver_socket == -1) { - return; - } - - latest_signal = signal; - - if (!thread) { - full = false; - } - - std::string buffer; - if (full) { - buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};{:02x}:{:016x}", latest_signal, - PC_REGISTER, Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER, - Common::swap64(RegRead(SP_REGISTER, thread)), LR_REGISTER, - Common::swap64(RegRead(LR_REGISTER, thread))); - } else { - buffer = fmt::format("T{:02x}", latest_signal); - } - - if (thread) { - buffer += fmt::format(";thread:{:x};", thread->GetThreadID()); - } - - SendReply(buffer.c_str()); -} - -/// Read command from gdb client. -static void ReadCommand() { - command_length = 0; - memset(command_buffer, 0, sizeof(command_buffer)); - - u8 c = ReadByte(); - if (c == '+') { - // ignore ack - return; - } else if (c == 0x03) { - LOG_INFO(Debug_GDBStub, "gdb: found break command"); - halt_loop = true; - SendSignal(current_thread, SIGTRAP); - return; - } else if (c != GDB_STUB_START) { - LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c); - return; - } - - while ((c = ReadByte()) != GDB_STUB_END) { - if (command_length >= sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow"); - SendPacket(GDB_STUB_NACK); - return; - } - command_buffer[command_length++] = c; - } - - u8 checksum_received = HexCharToValue(ReadByte()) << 4; - checksum_received |= HexCharToValue(ReadByte()); - - u8 checksum_calculated = CalculateChecksum(command_buffer, command_length); - - if (checksum_received != checksum_calculated) { - LOG_ERROR(Debug_GDBStub, - "gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})", - checksum_calculated, checksum_received, command_buffer, command_length); - - command_length = 0; - - SendPacket(GDB_STUB_NACK); - return; - } - - SendPacket(GDB_STUB_ACK); -} - -/// Check if there is data to be read from the gdb client. -static bool IsDataAvailable() { - if (!IsConnected()) { - return false; - } - - fd_set fd_socket; - - FD_ZERO(&fd_socket); - FD_SET(static_cast<u32>(gdbserver_socket), &fd_socket); - - struct timeval t; - t.tv_sec = 0; - t.tv_usec = 0; - - if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) { - LOG_ERROR(Debug_GDBStub, "select failed"); - return false; - } - - return FD_ISSET(gdbserver_socket, &fd_socket) != 0; -} - -/// Send requested register to gdb client. -static void ReadRegister() { - static u8 reply[64]; - memset(reply, 0, sizeof(reply)); - - u32 id = HexCharToValue(command_buffer[1]); - if (command_buffer[2] != '\0') { - id <<= 4; - id |= HexCharToValue(command_buffer[2]); - } - - if (id <= SP_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == PC_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == PSTATE_REGISTER) { - IntToGdbHex(reply, static_cast<u32>(RegRead(id, current_thread))); - } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - u128 r = FpuRead(id, current_thread); - LongToGdbHex(reply, r[0]); - LongToGdbHex(reply + 16, r[1]); - } else if (id == FPCR_REGISTER) { - u128 r = FpuRead(id, current_thread); - IntToGdbHex(reply, static_cast<u32>(r[0])); - } else if (id == FPCR_REGISTER + 1) { - u128 r = FpuRead(id, current_thread); - IntToGdbHex(reply, static_cast<u32>(r[0] >> 32)); - } - - SendReply(reinterpret_cast<char*>(reply)); -} - -/// Send all registers to the gdb client. -static void ReadRegisters() { - static u8 buffer[GDB_BUFFER_SIZE - 4]; - memset(buffer, 0, sizeof(buffer)); - - u8* bufptr = buffer; - - for (u32 reg = 0; reg <= SP_REGISTER; reg++) { - LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread)); - } - - bufptr += 32 * 16; - - LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread)); - - bufptr += 16; - - IntToGdbHex(bufptr, static_cast<u32>(RegRead(PSTATE_REGISTER, current_thread))); - - bufptr += 8; - - u128 r; - - for (u32 reg = UC_ARM64_REG_Q0; reg < FPCR_REGISTER; reg++) { - r = FpuRead(reg, current_thread); - LongToGdbHex(bufptr + reg * 32, r[0]); - LongToGdbHex(bufptr + reg * 32 + 16, r[1]); - } - - bufptr += 32 * 32; - - r = FpuRead(FPCR_REGISTER, current_thread); - IntToGdbHex(bufptr, static_cast<u32>(r[0])); - - bufptr += 8; - - SendReply(reinterpret_cast<char*>(buffer)); -} - -/// Modify data of register specified by gdb client. -static void WriteRegister() { - const u8* buffer_ptr = command_buffer + 3; - - u32 id = HexCharToValue(command_buffer[1]); - if (command_buffer[2] != '=') { - ++buffer_ptr; - id <<= 4; - id |= HexCharToValue(command_buffer[2]); - } - - if (id <= SP_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == PC_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == PSTATE_REGISTER) { - RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); - } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - FpuWrite(id, GdbHexToU128(buffer_ptr), current_thread); - } else if (id == FPCR_REGISTER) { - } else if (id == FPCR_REGISTER + 1) { - } - - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - - SendReply("OK"); -} - -/// Modify all registers with data received from the client. -static void WriteRegisters() { - const u8* buffer_ptr = command_buffer + 1; - - if (command_buffer[0] != 'G') - return SendReply("E01"); - - for (u32 i = 0, reg = 0; reg <= FPCR_REGISTER; i++, reg++) { - if (reg <= SP_REGISTER) { - RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == PC_REGISTER) { - RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == PSTATE_REGISTER) { - RegWrite(PSTATE_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread); - } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) { - RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == FPCR_REGISTER) { - RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == FPCR_REGISTER + 1) { - RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } - } - - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - - SendReply("OK"); -} - -/// Read location in memory specified by gdb client. -static void ReadMemory() { - static u8 reply[GDB_BUFFER_SIZE - 4]; - - auto start_offset = command_buffer + 1; - const auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - const u64 len = - HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset)); - - LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len); - - if (len * 2 > sizeof(reply)) { - SendReply("E01"); - } - - auto& memory = Core::System::GetInstance().Memory(); - if (!memory.IsValidVirtualAddress(addr)) { - return SendReply("E00"); - } - - std::vector<u8> data(len); - memory.ReadBlock(addr, data.data(), len); - - MemToGdbHex(reply, data.data(), len); - reply[len * 2] = '\0'; - SendReply(reinterpret_cast<char*>(reply)); -} - -/// Modify location in memory with data received from the gdb client. -static void WriteMemory() { - auto start_offset = command_buffer + 1; - const auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - const auto len_pos = std::find(start_offset, command_buffer + command_length, ':'); - const u64 len = HexToLong(start_offset, static_cast<u64>(len_pos - start_offset)); - - auto& system = Core::System::GetInstance(); - auto& memory = system.Memory(); - if (!memory.IsValidVirtualAddress(addr)) { - return SendReply("E00"); - } - - std::vector<u8> data(len); - GdbHexToMem(data.data(), len_pos + 1, len); - memory.WriteBlock(addr, data.data(), len); - system.InvalidateCpuInstructionCaches(); - SendReply("OK"); -} - -void Break(bool is_memory_break) { - send_trap = true; - - memory_break = is_memory_break; -} - -/// Tell the CPU that it should perform a single step. -static void Step() { - if (command_length > 1) { - RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - } - step_loop = true; - halt_loop = true; - send_trap = true; - Core::System::GetInstance().InvalidateCpuInstructionCaches(); -} - -/// Tell the CPU if we hit a memory breakpoint. -bool IsMemoryBreak() { - if (!IsConnected()) { - return false; - } - - return memory_break; -} - -/// Tell the CPU to continue executing. -static void Continue() { - memory_break = false; - step_loop = false; - halt_loop = false; - Core::System::GetInstance().InvalidateCpuInstructionCaches(); -} - -/** - * Commit breakpoint to list of breakpoints. - * - * @param type Type of breakpoint. - * @param addr Address of breakpoint. - * @param len Length of breakpoint. - */ -static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) { - BreakpointMap& p = GetBreakpointMap(type); - - Breakpoint breakpoint; - breakpoint.active = true; - breakpoint.addr = addr; - breakpoint.len = len; - - auto& system = Core::System::GetInstance(); - auto& memory = system.Memory(); - memory.ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size()); - - static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4}; - if (type == BreakpointType::Execute) { - memory.WriteBlock(addr, btrap.data(), btrap.size()); - system.InvalidateCpuInstructionCaches(); - } - p.insert({addr, breakpoint}); - - LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}", - static_cast<int>(type), breakpoint.len, breakpoint.addr); - - return true; -} - -/// Handle add breakpoint command from gdb client. -static void AddBreakpoint() { - BreakpointType type; - - u8 type_id = HexCharToValue(command_buffer[1]); - switch (type_id) { - case 0: - case 1: - type = BreakpointType::Execute; - break; - case 2: - type = BreakpointType::Write; - break; - case 3: - type = BreakpointType::Read; - break; - case 4: - type = BreakpointType::Access; - break; - default: - return SendReply("E01"); - } - - auto start_offset = command_buffer + 3; - auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - u64 len = - HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset)); - - if (type == BreakpointType::Access) { - // Access is made up of Read and Write types, so add both breakpoints - type = BreakpointType::Read; - - if (!CommitBreakpoint(type, addr, len)) { - return SendReply("E02"); - } - - type = BreakpointType::Write; - } - - if (!CommitBreakpoint(type, addr, len)) { - return SendReply("E02"); - } - - SendReply("OK"); -} - -/// Handle remove breakpoint command from gdb client. -static void RemoveBreakpoint() { - BreakpointType type; - - u8 type_id = HexCharToValue(command_buffer[1]); - switch (type_id) { - case 0: - case 1: - type = BreakpointType::Execute; - break; - case 2: - type = BreakpointType::Write; - break; - case 3: - type = BreakpointType::Read; - break; - case 4: - type = BreakpointType::Access; - break; - default: - return SendReply("E01"); - } - - auto start_offset = command_buffer + 3; - auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); - - if (type == BreakpointType::Access) { - // Access is made up of Read and Write types, so add both breakpoints - type = BreakpointType::Read; - RemoveBreakpoint(type, addr); - - type = BreakpointType::Write; - } - - RemoveBreakpoint(type, addr); - SendReply("OK"); -} - -void HandlePacket() { - if (!IsConnected()) { - if (defer_start) { - ToggleServer(true); - } - return; - } - - if (!IsDataAvailable()) { - return; - } - - ReadCommand(); - if (command_length == 0) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer); - - switch (command_buffer[0]) { - case 'q': - HandleQuery(); - break; - case 'H': - HandleSetThread(); - break; - case '?': - SendSignal(current_thread, latest_signal); - break; - case 'k': - Shutdown(); - LOG_INFO(Debug_GDBStub, "killed by gdb"); - return; - case 'g': - ReadRegisters(); - break; - case 'G': - WriteRegisters(); - break; - case 'p': - ReadRegister(); - break; - case 'P': - WriteRegister(); - break; - case 'm': - ReadMemory(); - break; - case 'M': - WriteMemory(); - break; - case 's': - Step(); - return; - case 'C': - case 'c': - Continue(); - return; - case 'z': - RemoveBreakpoint(); - break; - case 'Z': - AddBreakpoint(); - break; - case 'T': - HandleThreadAlive(); - break; - default: - SendReply(""); - break; - } -} - -void SetServerPort(u16 port) { - gdbstub_port = port; -} - -void ToggleServer(bool status) { - if (status) { - server_enabled = status; - - // Start server - if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) { - Init(); - } - } else { - // Stop server - if (IsConnected()) { - Shutdown(); - } - - server_enabled = status; - } -} - -void DeferStart() { - defer_start = true; -} - -static void Init(u16 port) { - if (!server_enabled) { - // Set the halt loop to false in case the user enabled the gdbstub mid-execution. - // This way the CPU can still execute normally. - halt_loop = false; - step_loop = false; - return; - } - - // Setup initial gdbstub status - halt_loop = true; - step_loop = false; - - breakpoints_execute.clear(); - breakpoints_read.clear(); - breakpoints_write.clear(); - - modules.clear(); - - // Start gdb server - LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port); - - sockaddr_in saddr_server = {}; - saddr_server.sin_family = AF_INET; - saddr_server.sin_port = htons(port); - saddr_server.sin_addr.s_addr = INADDR_ANY; - -#ifdef _WIN32 - WSAStartup(MAKEWORD(2, 2), &InitData); -#endif - - int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0)); - if (tmpsock == -1) { - LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); - } - - // Set socket to SO_REUSEADDR so it can always bind on the same port - int reuse_enabled = 1; - if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, - sizeof(reuse_enabled)) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); - } - - const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server); - socklen_t server_addrlen = sizeof(saddr_server); - if (bind(tmpsock, server_addr, server_addrlen) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); - } - - if (listen(tmpsock, 1) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); - } - - // Wait for gdb to connect - LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect..."); - sockaddr_in saddr_client; - sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); - socklen_t client_addrlen = sizeof(saddr_client); - gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen)); - if (gdbserver_socket < 0) { - // In the case that we couldn't start the server for whatever reason, just start CPU - // execution like normal. - halt_loop = false; - step_loop = false; - - LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); - } else { - LOG_INFO(Debug_GDBStub, "Client connected."); - saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); - } - - // Clean up temporary socket if it's still alive at this point. - if (tmpsock != -1) { - shutdown(tmpsock, SHUT_RDWR); - } -} - -void Init() { - Init(gdbstub_port); -} - -void Shutdown() { - if (!server_enabled) { - return; - } - defer_start = false; - - LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); - if (gdbserver_socket != -1) { - shutdown(gdbserver_socket, SHUT_RDWR); - gdbserver_socket = -1; - } - -#ifdef _WIN32 - WSACleanup(); -#endif - - LOG_INFO(Debug_GDBStub, "GDB stopped."); -} - -bool IsServerEnabled() { - return server_enabled; -} - -bool IsConnected() { - return IsServerEnabled() && gdbserver_socket != -1; -} - -bool GetCpuHaltFlag() { - return halt_loop; -} - -bool GetCpuStepFlag() { - return step_loop; -} - -void SetCpuStepFlag(bool is_step) { - step_loop = is_step; -} - -void SendTrap(Kernel::Thread* thread, int trap) { - if (!send_trap) { - return; - } - - current_thread = thread; - SendSignal(thread, trap); - - halt_loop = true; - send_trap = false; -} -}; // namespace GDBStub diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h deleted file mode 100644 index 8fe3c320b..000000000 --- a/src/core/gdbstub/gdbstub.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic. - -#pragma once - -#include <string> -#include "common/common_types.h" -#include "core/hle/kernel/thread.h" - -namespace GDBStub { - -/// Breakpoint Method -enum class BreakpointType { - None, ///< None - Execute, ///< Execution Breakpoint - Read, ///< Read Breakpoint - Write, ///< Write Breakpoint - Access ///< Access (R/W) Breakpoint -}; - -struct BreakpointAddress { - VAddr address; - BreakpointType type; -}; - -/** - * Set the port the gdbstub should use to listen for connections. - * - * @param port Port to listen for connection - */ -void SetServerPort(u16 port); - -/** - * Starts or stops the server if possible. - * - * @param status Set the server to enabled or disabled. - */ -void ToggleServer(bool status); - -/// Start the gdbstub server. -void Init(); - -/** - * Defer initialization of the gdbstub to the first packet processing functions. - * This avoids a case where the gdbstub thread is frozen after initialization - * and fails to respond in time to packets. - */ -void DeferStart(); - -/// Stop gdbstub server. -void Shutdown(); - -/// Checks if the gdbstub server is enabled. -bool IsServerEnabled(); - -/// Returns true if there is an active socket connection. -bool IsConnected(); - -/// Register module. -void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext = true); - -/** - * Signal to the gdbstub server that it should halt CPU execution. - * - * @param is_memory_break If true, the break resulted from a memory breakpoint. - */ -void Break(bool is_memory_break = false); - -/// Determine if there was a memory breakpoint. -bool IsMemoryBreak(); - -/// Read and handle packet from gdb client. -void HandlePacket(); - -/** - * Get the nearest breakpoint of the specified type at the given address. - * - * @param addr Address to search from. - * @param type Type of breakpoint. - */ -BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, GDBStub::BreakpointType type); - -/** - * Check if a breakpoint of the specified type exists at the given address. - * - * @param addr Address of breakpoint. - * @param type Type of breakpoint. - */ -bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type); - -/// If set to true, the CPU will halt at the beginning of the next CPU loop. -bool GetCpuHaltFlag(); - -/// If set to true and the CPU is halted, the CPU will step one instruction. -bool GetCpuStepFlag(); - -/** - * When set to true, the CPU will step one instruction when the CPU is halted next. - * - * @param is_step - */ -void SetCpuStepFlag(bool is_step); - -/** - * Send trap signal from thread back to the gdbstub server. - * - * @param thread Sending thread. - * @param trap Trap no. - */ -void SendTrap(Kernel::Thread* thread, int trap); -} // namespace GDBStub diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h index 456b41e1b..176a72c67 100644 --- a/src/core/hardware_properties.h +++ b/src/core/hardware_properties.h @@ -4,8 +4,10 @@ #pragma once +#include <array> #include <tuple> +#include "common/bit_util.h" #include "common/common_types.h" namespace Core { @@ -18,34 +20,12 @@ constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch cpu frequency is 1020MHz u constexpr u64 CNTFREQ = 19200000; // Switch's hardware clock speed constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores -} // namespace Hardware - -constexpr u32 INVALID_HOST_THREAD_ID = 0xFFFFFFFF; - -struct EmuThreadHandle { - u32 host_handle; - u32 guest_handle; - - u64 GetRaw() const { - return (static_cast<u64>(host_handle) << 32) | guest_handle; - } - - bool operator==(const EmuThreadHandle& rhs) const { - return std::tie(host_handle, guest_handle) == std::tie(rhs.host_handle, rhs.guest_handle); - } - - bool operator!=(const EmuThreadHandle& rhs) const { - return !operator==(rhs); - } - - static constexpr EmuThreadHandle InvalidHandle() { - constexpr u32 invalid_handle = 0xFFFFFFFF; - return {invalid_handle, invalid_handle}; - } - - bool IsInvalid() const { - return (*this) == InvalidHandle(); - } +// Virtual to Physical core map. +constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{ + 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, }; +} // namespace Hardware + } // namespace Core diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 7ce313190..55b1716e4 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -146,7 +146,7 @@ static_assert(sizeof(BufferDescriptorC) == 8, "BufferDescriptorC size is incorre struct DataPayloadHeader { u32_le magic; - INSERT_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); }; static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadHeader size is incorrect"); @@ -160,7 +160,7 @@ struct DomainMessageHeader { // Used when responding to an IPC request, Server -> Client. struct { u32_le num_objects; - INSERT_UNION_PADDING_WORDS(3); + INSERT_PADDING_WORDS_NOINIT(3); }; // Used when performing an IPC request, Client -> Server. @@ -171,10 +171,10 @@ struct DomainMessageHeader { BitField<16, 16, u32> size; }; u32_le object_id; - INSERT_UNION_PADDING_WORDS(2); + INSERT_PADDING_WORDS_NOINIT(2); }; - std::array<u32, 4> raw{}; + std::array<u32, 4> raw; }; }; static_assert(sizeof(DomainMessageHeader) == 16, "DomainMessageHeader size is incorrect"); diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 1b503331f..56cc911d1 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -12,7 +12,6 @@ #include <utility> #include "common/assert.h" #include "common/common_types.h" -#include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" @@ -38,10 +37,11 @@ public: explicit RequestHelperBase(Kernel::HLERequestContext& context) : context(&context), cmdbuf(context.CommandBuffer()) {} - void Skip(unsigned size_in_words, bool set_to_null) { - if (set_to_null) + void Skip(u32 size_in_words, bool set_to_null) { + if (set_to_null) { memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); - index += size_in_words; + } + index += static_cast<ptrdiff_t>(size_in_words); } /** @@ -49,15 +49,15 @@ public: */ void AlignWithPadding() { if (index & 3) { - Skip(4 - (index & 3), true); + Skip(static_cast<u32>(4 - (index & 3)), true); } } - unsigned GetCurrentOffset() const { - return static_cast<unsigned>(index); + u32 GetCurrentOffset() const { + return static_cast<u32>(index); } - void SetCurrentOffset(unsigned offset) { + void SetCurrentOffset(u32 offset) { index = static_cast<ptrdiff_t>(offset); } }; @@ -72,14 +72,12 @@ public: AlwaysMoveHandles = 1, }; - explicit ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {} - explicit ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size, u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0, Flags flags = Flags::None) - : RequestHelperBase(context), normal_params_size(normal_params_size), - num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) { + num_handles_to_copy(num_handles_to_copy), + num_objects_to_move(num_objects_to_move), kernel{context.kernel} { memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); @@ -89,7 +87,7 @@ public: // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory // padding. - u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; + u64 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; u32 num_handles_to_move{}; u32 num_domain_objects{}; @@ -105,7 +103,7 @@ public: raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects; } - header.data_size.Assign(raw_data_size); + header.data_size.Assign(static_cast<u32>(raw_data_size)); if (num_handles_to_copy || num_handles_to_move) { header.enable_handle_descriptor.Assign(1); } @@ -139,7 +137,6 @@ public: if (context->Session()->IsDomain()) { context->AddDomainObject(std::move(iface)); } else { - auto& kernel = Core::System::GetInstance().Kernel(); auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName()); context->AddMoveObject(std::move(client)); iface->ClientConnected(std::move(server)); @@ -169,8 +166,23 @@ public: ValidateHeader(); } + void PushImpl(s8 value); + void PushImpl(s16 value); + void PushImpl(s32 value); + void PushImpl(s64 value); + void PushImpl(u8 value); + void PushImpl(u16 value); + void PushImpl(u32 value); + void PushImpl(u64 value); + void PushImpl(float value); + void PushImpl(double value); + void PushImpl(bool value); + void PushImpl(ResultCode value); + template <typename T> - void Push(T value); + void Push(T value) { + return PushImpl(value); + } template <typename First, typename... Other> void Push(const First& first_value, const Other&... other_values); @@ -213,17 +225,16 @@ private: u32 num_handles_to_copy{}; u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent std::ptrdiff_t datapayload_index{}; + Kernel::KernelCore& kernel; }; /// Push /// -template <> -inline void ResponseBuilder::Push(s32 value) { +inline void ResponseBuilder::PushImpl(s32 value) { cmdbuf[index++] = static_cast<u32>(value); } -template <> -inline void ResponseBuilder::Push(u32 value) { +inline void ResponseBuilder::PushImpl(u32 value) { cmdbuf[index++] = value; } @@ -235,62 +246,52 @@ void ResponseBuilder::PushRaw(const T& value) { index += (sizeof(T) + 3) / 4; // round up to word length } -template <> -inline void ResponseBuilder::Push(ResultCode value) { +inline void ResponseBuilder::PushImpl(ResultCode value) { // Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded. Push(value.raw); Push<u32>(0); } -template <> -inline void ResponseBuilder::Push(s8 value) { +inline void ResponseBuilder::PushImpl(s8 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(s16 value) { +inline void ResponseBuilder::PushImpl(s16 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(s64 value) { - Push(static_cast<u32>(value)); - Push(static_cast<u32>(value >> 32)); +inline void ResponseBuilder::PushImpl(s64 value) { + PushImpl(static_cast<u32>(value)); + PushImpl(static_cast<u32>(value >> 32)); } -template <> -inline void ResponseBuilder::Push(u8 value) { +inline void ResponseBuilder::PushImpl(u8 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(u16 value) { +inline void ResponseBuilder::PushImpl(u16 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(u64 value) { - Push(static_cast<u32>(value)); - Push(static_cast<u32>(value >> 32)); +inline void ResponseBuilder::PushImpl(u64 value) { + PushImpl(static_cast<u32>(value)); + PushImpl(static_cast<u32>(value >> 32)); } -template <> -inline void ResponseBuilder::Push(float value) { +inline void ResponseBuilder::PushImpl(float value) { u32 integral; std::memcpy(&integral, &value, sizeof(u32)); - Push(integral); + PushImpl(integral); } -template <> -inline void ResponseBuilder::Push(double value) { +inline void ResponseBuilder::PushImpl(double value) { u64 integral; std::memcpy(&integral, &value, sizeof(u64)); - Push(integral); + PushImpl(integral); } -template <> -inline void ResponseBuilder::Push(bool value) { - Push(static_cast<u8>(value)); +inline void ResponseBuilder::PushImpl(bool value) { + PushImpl(static_cast<u8>(value)); } template <typename First, typename... Other> diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp deleted file mode 100644 index b882eaa0f..000000000 --- a/src/core/hle/kernel/address_arbiter.cpp +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <vector> - -#include "common/assert.h" -#include "common/common_types.h" -#include "core/arm/exclusive_monitor.h" -#include "core/core.h" -#include "core/hle/kernel/address_arbiter.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/time_manager.h" -#include "core/hle/result.h" -#include "core/memory.h" - -namespace Kernel { - -// Wake up num_to_wake (or all) threads in a vector. -void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, - s32 num_to_wake) { - // Only process up to 'target' threads, unless 'target' is <= 0, in which case process - // them all. - std::size_t last = waiting_threads.size(); - if (num_to_wake > 0) { - last = std::min(last, static_cast<std::size_t>(num_to_wake)); - } - - // Signal the waiting threads. - for (std::size_t i = 0; i < last; i++) { - waiting_threads[i]->SetSynchronizationResults(nullptr, RESULT_SUCCESS); - RemoveThread(waiting_threads[i]); - waiting_threads[i]->WaitForArbitration(false); - waiting_threads[i]->ResumeFromWait(); - } -} - -AddressArbiter::AddressArbiter(Core::System& system) : system{system} {} -AddressArbiter::~AddressArbiter() = default; - -ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value, - s32 num_to_wake) { - switch (type) { - case SignalType::Signal: - return SignalToAddressOnly(address, num_to_wake); - case SignalType::IncrementAndSignalIfEqual: - return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake); - case SignalType::ModifyByWaitingCountAndSignalIfEqual: - return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake); - default: - return ERR_INVALID_ENUM_VALUE; - } -} - -ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); - const std::vector<std::shared_ptr<Thread>> waiting_threads = - GetThreadsWaitingOnAddress(address); - WakeThreads(waiting_threads, num_to_wake); - return RESULT_SUCCESS; -} - -ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, - s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); - auto& memory = system.Memory(); - - // Ensure that we can write to the address. - if (!memory.IsValidVirtualAddress(address)) { - return ERR_INVALID_ADDRESS_STATE; - } - - const std::size_t current_core = system.CurrentCoreIndex(); - auto& monitor = system.Monitor(); - u32 current_value; - do { - current_value = monitor.ExclusiveRead32(current_core, address); - - if (current_value != static_cast<u32>(value)) { - return ERR_INVALID_STATE; - } - current_value++; - } while (!monitor.ExclusiveWrite32(current_core, address, current_value)); - - return SignalToAddressOnly(address, num_to_wake); -} - -ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, - s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); - auto& memory = system.Memory(); - - // Ensure that we can write to the address. - if (!memory.IsValidVirtualAddress(address)) { - return ERR_INVALID_ADDRESS_STATE; - } - - // Get threads waiting on the address. - const std::vector<std::shared_ptr<Thread>> waiting_threads = - GetThreadsWaitingOnAddress(address); - - const std::size_t current_core = system.CurrentCoreIndex(); - auto& monitor = system.Monitor(); - s32 updated_value; - do { - updated_value = monitor.ExclusiveRead32(current_core, address); - - if (updated_value != value) { - return ERR_INVALID_STATE; - } - // Determine the modified value depending on the waiting count. - if (num_to_wake <= 0) { - if (waiting_threads.empty()) { - updated_value = value + 1; - } else { - updated_value = value - 1; - } - } else { - if (waiting_threads.empty()) { - updated_value = value + 1; - } else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) { - updated_value = value - 1; - } else { - updated_value = value; - } - } - } while (!monitor.ExclusiveWrite32(current_core, address, updated_value)); - - WakeThreads(waiting_threads, num_to_wake); - return RESULT_SUCCESS; -} - -ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value, - s64 timeout_ns) { - switch (type) { - case ArbitrationType::WaitIfLessThan: - return WaitForAddressIfLessThan(address, value, timeout_ns, false); - case ArbitrationType::DecrementAndWaitIfLessThan: - return WaitForAddressIfLessThan(address, value, timeout_ns, true); - case ArbitrationType::WaitIfEqual: - return WaitForAddressIfEqual(address, value, timeout_ns); - default: - return ERR_INVALID_ENUM_VALUE; - } -} - -ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, - bool should_decrement) { - auto& memory = system.Memory(); - auto& kernel = system.Kernel(); - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); - - Handle event_handle = InvalidHandle; - { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); - - if (current_thread->IsPendingTermination()) { - lock.CancelSleep(); - return ERR_THREAD_TERMINATING; - } - - // Ensure that we can read the address. - if (!memory.IsValidVirtualAddress(address)) { - lock.CancelSleep(); - return ERR_INVALID_ADDRESS_STATE; - } - - s32 current_value = static_cast<s32>(memory.Read32(address)); - if (current_value >= value) { - lock.CancelSleep(); - return ERR_INVALID_STATE; - } - - current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - - s32 decrement_value; - - const std::size_t current_core = system.CurrentCoreIndex(); - auto& monitor = system.Monitor(); - do { - current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address)); - if (should_decrement) { - decrement_value = current_value - 1; - } else { - decrement_value = current_value; - } - } while ( - !monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value))); - - // Short-circuit without rescheduling, if timeout is zero. - if (timeout == 0) { - lock.CancelSleep(); - return RESULT_TIMEOUT; - } - - current_thread->SetArbiterWaitAddress(address); - InsertThread(SharedFrom(current_thread)); - current_thread->SetStatus(ThreadStatus::WaitArb); - current_thread->WaitForArbitration(true); - } - - if (event_handle != InvalidHandle) { - auto& time_manager = kernel.TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - - { - SchedulerLock lock(kernel); - if (current_thread->IsWaitingForArbitration()) { - RemoveThread(SharedFrom(current_thread)); - current_thread->WaitForArbitration(false); - } - } - - return current_thread->GetSignalingResult(); -} - -ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) { - auto& memory = system.Memory(); - auto& kernel = system.Kernel(); - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); - - Handle event_handle = InvalidHandle; - { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); - - if (current_thread->IsPendingTermination()) { - lock.CancelSleep(); - return ERR_THREAD_TERMINATING; - } - - // Ensure that we can read the address. - if (!memory.IsValidVirtualAddress(address)) { - lock.CancelSleep(); - return ERR_INVALID_ADDRESS_STATE; - } - - s32 current_value = static_cast<s32>(memory.Read32(address)); - if (current_value != value) { - lock.CancelSleep(); - return ERR_INVALID_STATE; - } - - // Short-circuit without rescheduling, if timeout is zero. - if (timeout == 0) { - lock.CancelSleep(); - return RESULT_TIMEOUT; - } - - current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - current_thread->SetArbiterWaitAddress(address); - InsertThread(SharedFrom(current_thread)); - current_thread->SetStatus(ThreadStatus::WaitArb); - current_thread->WaitForArbitration(true); - } - - if (event_handle != InvalidHandle) { - auto& time_manager = kernel.TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - - { - SchedulerLock lock(kernel); - if (current_thread->IsWaitingForArbitration()) { - RemoveThread(SharedFrom(current_thread)); - current_thread->WaitForArbitration(false); - } - } - - return current_thread->GetSignalingResult(); -} - -void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) { - ASSERT(thread->GetStatus() == ThreadStatus::WaitArb); - RemoveThread(thread); - thread->SetArbiterWaitAddress(0); -} - -void AddressArbiter::InsertThread(std::shared_ptr<Thread> thread) { - const VAddr arb_addr = thread->GetArbiterWaitAddress(); - std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr]; - - const auto iter = - std::find_if(thread_list.cbegin(), thread_list.cend(), [&thread](const auto& entry) { - return entry->GetPriority() >= thread->GetPriority(); - }); - - if (iter == thread_list.cend()) { - thread_list.push_back(std::move(thread)); - } else { - thread_list.insert(iter, std::move(thread)); - } -} - -void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) { - const VAddr arb_addr = thread->GetArbiterWaitAddress(); - std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr]; - - const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(), - [&thread](const auto& entry) { return thread == entry; }); - - if (iter != thread_list.cend()) { - thread_list.erase(iter); - } -} - -std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress( - VAddr address) const { - const auto iter = arb_threads.find(address); - if (iter == arb_threads.cend()) { - return {}; - } - - const std::list<std::shared_ptr<Thread>>& thread_list = iter->second; - return {thread_list.cbegin(), thread_list.cend()}; -} -} // namespace Kernel diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h deleted file mode 100644 index 0b05d533c..000000000 --- a/src/core/hle/kernel/address_arbiter.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <list> -#include <memory> -#include <unordered_map> -#include <vector> - -#include "common/common_types.h" - -union ResultCode; - -namespace Core { -class System; -} - -namespace Kernel { - -class Thread; - -class AddressArbiter { -public: - enum class ArbitrationType { - WaitIfLessThan = 0, - DecrementAndWaitIfLessThan = 1, - WaitIfEqual = 2, - }; - - enum class SignalType { - Signal = 0, - IncrementAndSignalIfEqual = 1, - ModifyByWaitingCountAndSignalIfEqual = 2, - }; - - explicit AddressArbiter(Core::System& system); - ~AddressArbiter(); - - AddressArbiter(const AddressArbiter&) = delete; - AddressArbiter& operator=(const AddressArbiter&) = delete; - - AddressArbiter(AddressArbiter&&) = default; - AddressArbiter& operator=(AddressArbiter&&) = delete; - - /// Signals an address being waited on with a particular signaling type. - ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake); - - /// Waits on an address with a particular arbitration type. - ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns); - - /// Removes a thread from the container and resets its address arbiter adress to 0 - void HandleWakeupThread(std::shared_ptr<Thread> thread); - -private: - /// Signals an address being waited on. - ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake); - - /// Signals an address being waited on and increments its value if equal to the value argument. - ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake); - - /// Signals an address being waited on and modifies its value based on waiting thread count if - /// equal to the value argument. - ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, - s32 num_to_wake); - - /// Waits on an address if the value passed is less than the argument value, - /// optionally decrementing. - ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, - bool should_decrement); - - /// Waits on an address if the value passed is equal to the argument value. - ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout); - - /// Wake up num_to_wake (or all) threads in a vector. - void WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, s32 num_to_wake); - - /// Insert a thread into the address arbiter container - void InsertThread(std::shared_ptr<Thread> thread); - - /// Removes a thread from the address arbiter container - void RemoveThread(std::shared_ptr<Thread> thread); - - // Gets the threads waiting on an address. - std::vector<std::shared_ptr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const; - - /// List of threads waiting for a address arbiter - std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> arb_threads; - - Core::System& system; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index 8aff2227a..0b6957e31 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -4,11 +4,11 @@ #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/session.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel { @@ -21,7 +21,7 @@ std::shared_ptr<ServerPort> ClientPort::GetServerPort() const { ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() { if (active_sessions >= max_sessions) { - return ERR_MAX_CONNECTIONS_REACHED; + return ResultMaxConnectionsReached; } active_sessions++; @@ -33,9 +33,6 @@ ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() { server_port->AppendPendingSession(std::move(server)); } - // Wake the threads waiting on the ServerPort - server_port->Signal(); - return MakeResult(std::move(client)); } diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index 9762bbf0d..77559ebf9 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -51,6 +51,8 @@ public: */ void ConnectionClosed(); + void Finalize() override {} + private: std::shared_ptr<ServerPort> server_port; ///< ServerPort associated with this client port. u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 5ab204b9b..e230f365a 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -3,16 +3,16 @@ // Refer to the license.txt file included. #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/result.h" namespace Kernel { -ClientSession::ClientSession(KernelCore& kernel) : SynchronizationObject{kernel} {} +ClientSession::ClientSession(KernelCore& kernel) : KSynchronizationObject{kernel} {} ClientSession::~ClientSession() { // This destructor will be called automatically when the last ClientSession handle is closed by @@ -22,15 +22,6 @@ ClientSession::~ClientSession() { } } -bool ClientSession::ShouldWait(const Thread* thread) const { - UNIMPLEMENTED(); - return {}; -} - -void ClientSession::Acquire(Thread* thread) { - UNIMPLEMENTED(); -} - bool ClientSession::IsSignaled() const { UNIMPLEMENTED(); return true; @@ -47,15 +38,16 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern return MakeResult(std::move(client_session)); } -ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, - Core::Memory::Memory& memory) { +ResultCode ClientSession::SendSyncRequest(std::shared_ptr<KThread> thread, + Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing) { // Keep ServerSession alive until we're done working with it. if (!parent->Server()) { - return ERR_SESSION_CLOSED_BY_REMOTE; + return ResultSessionClosedByRemote; } // Signal the server session that new data is available - return parent->Server()->HandleSyncRequest(std::move(thread), memory); + return parent->Server()->HandleSyncRequest(std::move(thread), memory, core_timing); } } // namespace Kernel diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index c5f760d7d..85aafeaf4 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -7,7 +7,7 @@ #include <memory> #include <string> -#include "core/hle/kernel/synchronization_object.h" +#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/result.h" union ResultCode; @@ -16,13 +16,17 @@ namespace Core::Memory { class Memory; } +namespace Core::Timing { +class CoreTiming; +} + namespace Kernel { class KernelCore; class Session; -class Thread; +class KThread; -class ClientSession final : public SynchronizationObject { +class ClientSession final : public KSynchronizationObject { public: explicit ClientSession(KernelCore& kernel); ~ClientSession() override; @@ -42,14 +46,13 @@ public: return HANDLE_TYPE; } - ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); - - bool ShouldWait(const Thread* thread) const override; - - void Acquire(Thread* thread) override; + ResultCode SendSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing); bool IsSignaled() const override; + void Finalize() override {} + private: static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel, std::shared_ptr<Session> parent, diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h deleted file mode 100644 index d4e5d88cf..000000000 --- a/src/core/hle/kernel/errors.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/hle/result.h" - -namespace Kernel { - -// Confirmed Switch kernel error codes - -constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7}; -constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14}; -constexpr ResultCode ERR_THREAD_TERMINATING{ErrorModule::Kernel, 59}; -constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101}; -constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102}; -constexpr ResultCode ERR_OUT_OF_RESOURCES{ErrorModule::Kernel, 103}; -constexpr ResultCode ERR_OUT_OF_MEMORY{ErrorModule::Kernel, 104}; -constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105}; -constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106}; -constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS{ErrorModule::Kernel, 108}; -constexpr ResultCode ERR_INVALID_MEMORY_RANGE{ErrorModule::Kernel, 110}; -constexpr ResultCode ERR_INVALID_PROCESSOR_ID{ErrorModule::Kernel, 113}; -constexpr ResultCode ERR_INVALID_THREAD_PRIORITY{ErrorModule::Kernel, 112}; -constexpr ResultCode ERR_INVALID_HANDLE{ErrorModule::Kernel, 114}; -constexpr ResultCode ERR_INVALID_POINTER{ErrorModule::Kernel, 115}; -constexpr ResultCode ERR_INVALID_COMBINATION{ErrorModule::Kernel, 116}; -constexpr ResultCode RESULT_TIMEOUT{ErrorModule::Kernel, 117}; -constexpr ResultCode ERR_SYNCHRONIZATION_CANCELED{ErrorModule::Kernel, 118}; -constexpr ResultCode ERR_OUT_OF_RANGE{ErrorModule::Kernel, 119}; -constexpr ResultCode ERR_INVALID_ENUM_VALUE{ErrorModule::Kernel, 120}; -constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121}; -constexpr ResultCode ERR_BUSY{ErrorModule::Kernel, 122}; -constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123}; -constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125}; -constexpr ResultCode ERR_RESERVED_VALUE{ErrorModule::Kernel, 126}; -constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp new file mode 100644 index 000000000..c6838649f --- /dev/null +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -0,0 +1,52 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> + +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/global_scheduler_context.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel) + : kernel{kernel}, scheduler_lock{kernel} {} + +GlobalSchedulerContext::~GlobalSchedulerContext() = default; + +void GlobalSchedulerContext::AddThread(std::shared_ptr<KThread> thread) { + std::scoped_lock lock{global_list_guard}; + thread_list.push_back(std::move(thread)); +} + +void GlobalSchedulerContext::RemoveThread(std::shared_ptr<KThread> thread) { + std::scoped_lock lock{global_list_guard}; + thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), + thread_list.end()); +} + +void GlobalSchedulerContext::PreemptThreads() { + // The priority levels at which the global scheduler preempts threads every 10 ms. They are + // ordered from Core 0 to Core 3. + static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities{ + 59, + 59, + 59, + 63, + }; + + ASSERT(IsLocked()); + for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + const u32 priority = preemption_priorities[core_id]; + kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); + } +} + +bool GlobalSchedulerContext::IsLocked() const { + return scheduler_lock.IsLockedByCurrentThread(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h new file mode 100644 index 000000000..11592843e --- /dev/null +++ b/src/core/hle/kernel/global_scheduler_context.h @@ -0,0 +1,86 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <vector> + +#include "common/common_types.h" +#include "common/spin_lock.h" +#include "core/hardware_properties.h" +#include "core/hle/kernel/k_priority_queue.h" +#include "core/hle/kernel/k_scheduler_lock.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc_types.h" + +namespace Kernel { + +class KernelCore; +class SchedulerLock; + +using KSchedulerPriorityQueue = + KPriorityQueue<KThread, Core::Hardware::NUM_CPU_CORES, Svc::LowestThreadPriority, + Svc::HighestThreadPriority>; + +static constexpr s32 HighestCoreMigrationAllowedPriority = 2; +static_assert(Svc::LowestThreadPriority >= HighestCoreMigrationAllowedPriority); +static_assert(Svc::HighestThreadPriority <= HighestCoreMigrationAllowedPriority); + +class GlobalSchedulerContext final { + friend class KScheduler; + +public: + using LockType = KAbstractSchedulerLock<KScheduler>; + + explicit GlobalSchedulerContext(KernelCore& kernel); + ~GlobalSchedulerContext(); + + /// Adds a new thread to the scheduler + void AddThread(std::shared_ptr<KThread> thread); + + /// Removes a thread from the scheduler + void RemoveThread(std::shared_ptr<KThread> thread); + + /// Returns a list of all threads managed by the scheduler + [[nodiscard]] const std::vector<std::shared_ptr<KThread>>& GetThreadList() const { + return thread_list; + } + + /** + * Rotates the scheduling queues of threads at a preemption priority and then does + * some core rebalancing. Preemption priorities can be found in the array + * 'preemption_priorities'. + * + * @note This operation happens every 10ms. + */ + void PreemptThreads(); + + /// Returns true if the global scheduler lock is acquired + bool IsLocked() const; + + [[nodiscard]] LockType& SchedulerLock() { + return scheduler_lock; + } + + [[nodiscard]] const LockType& SchedulerLock() const { + return scheduler_lock; + } + +private: + friend class KScopedSchedulerLock; + friend class KScopedSchedulerLockAndSleep; + + KernelCore& kernel; + + std::atomic_bool scheduler_update_needed{}; + KSchedulerPriorityQueue priority_queue; + LockType scheduler_lock; + + /// Lists all thread ids that aren't deleted/etc. + std::vector<std::shared_ptr<KThread>> thread_list; + Common::SpinLock global_list_guard{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index fb30b6f8b..f96d34078 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -6,12 +6,12 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel { namespace { @@ -33,7 +33,7 @@ HandleTable::~HandleTable() = default; ResultCode HandleTable::SetSize(s32 handle_table_size) { if (static_cast<u32>(handle_table_size) > MAX_COUNT) { LOG_ERROR(Kernel, "Handle table size {} is greater than {}", handle_table_size, MAX_COUNT); - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } // Values less than or equal to zero indicate to use the maximum allowable @@ -53,7 +53,7 @@ ResultVal<Handle> HandleTable::Create(std::shared_ptr<Object> obj) { const u16 slot = next_free_slot; if (slot >= table_size) { LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use."); - return ERR_HANDLE_TABLE_FULL; + return ResultHandleTableFull; } next_free_slot = generations[slot]; @@ -76,7 +76,7 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) { std::shared_ptr<Object> object = GetGeneric(handle); if (object == nullptr) { LOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } return Create(std::move(object)); } @@ -84,11 +84,15 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) { ResultCode HandleTable::Close(Handle handle) { if (!IsValid(handle)) { LOG_ERROR(Kernel, "Handle is not valid! handle={:08X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } const u16 slot = GetSlot(handle); + if (objects[slot].use_count() == 1) { + objects[slot]->Finalize(); + } + objects[slot] = nullptr; generations[slot] = next_free_slot; @@ -105,7 +109,7 @@ bool HandleTable::IsValid(Handle handle) const { std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { - return SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); + return SharedFrom(kernel.CurrentScheduler()->GetCurrentThread()); } else if (handle == CurrentProcess) { return SharedFrom(kernel.CurrentProcess()); } @@ -118,7 +122,7 @@ std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const { void HandleTable::Clear() { for (u16 i = 0; i < table_size; ++i) { - generations[i] = i + 1; + generations[i] = static_cast<u16>(i + 1); objects[i] = nullptr; } next_free_slot = 0; diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 81f85643b..161d9f782 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -14,18 +14,19 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/kernel/time_manager.h" -#include "core/hle/kernel/writable_event.h" #include "core/memory.h" namespace Kernel { @@ -45,47 +46,9 @@ void SessionRequestHandler::ClientDisconnected( boost::range::remove_erase(connected_sessions, server_session); } -std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread( - const std::string& reason, u64 timeout, WakeupCallback&& callback, - std::shared_ptr<WritableEvent> writable_event) { - // Put the client thread to sleep until the wait event is signaled or the timeout expires. - - if (!writable_event) { - // Create event if not provided - const auto pair = WritableEvent::CreateEventPair(kernel, "HLE Pause Event: " + reason); - writable_event = pair.writable; - } - - { - Handle event_handle = InvalidHandle; - SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout); - thread->SetHLECallback( - [context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool { - ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT - ? ThreadWakeupReason::Timeout - : ThreadWakeupReason::Signal; - callback(thread, context, reason); - context.WriteToOutgoingCommandBuffer(*thread); - return true; - }); - const auto readable_event{writable_event->GetReadableEvent()}; - writable_event->Clear(); - thread->SetHLESyncObject(readable_event.get()); - thread->SetStatus(ThreadStatus::WaitHLEEvent); - thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - readable_event->AddWaitingThread(thread); - lock.Release(); - thread->SetHLETimeEvent(event_handle); - } - - is_thread_waiting = true; - - return writable_event; -} - HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory, std::shared_ptr<ServerSession> server_session, - std::shared_ptr<Thread> thread) + std::shared_ptr<KThread> thread) : server_session(std::move(server_session)), thread(std::move(thread)), kernel{kernel}, memory{memory} { cmd_buf[0] = 0; @@ -219,7 +182,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const HandleTabl return RESULT_SUCCESS; } -ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) { +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& thread) { auto& owner_process = *thread.GetOwnerProcess(); auto& handle_table = owner_process.GetHandleTable(); @@ -375,6 +338,28 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons return 0; } +bool HLERequestContext::CanReadBuffer(std::size_t buffer_index) const { + const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && + BufferDescriptorA()[buffer_index].Size()}; + + if (is_buffer_a) { + return BufferDescriptorA().size() > buffer_index; + } else { + return BufferDescriptorX().size() > buffer_index; + } +} + +bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const { + const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && + BufferDescriptorB()[buffer_index].Size()}; + + if (is_buffer_b) { + return BufferDescriptorB().size() > buffer_index; + } else { + return BufferDescriptorC().size() > buffer_index; + } +} + std::string HLERequestContext::Description() const { if (!command_header) { return "No command header available"; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index f3277b766..9a769781b 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -24,6 +24,10 @@ namespace Core::Memory { class Memory; } +namespace IPC { +class ResponseBuilder; +} + namespace Service { class ServiceFrameworkBase; } @@ -36,9 +40,9 @@ class HLERequestContext; class KernelCore; class Process; class ServerSession; -class Thread; -class ReadableEvent; -class WritableEvent; +class KThread; +class KReadableEvent; +class KWritableEvent; enum class ThreadWakeupReason; @@ -106,7 +110,7 @@ class HLERequestContext { public: explicit HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory, std::shared_ptr<ServerSession> session, - std::shared_ptr<Thread> thread); + std::shared_ptr<KThread> thread); ~HLERequestContext(); /// Returns a pointer to the IPC command buffer for this request. @@ -122,32 +126,12 @@ public: return server_session; } - using WakeupCallback = std::function<void( - std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>; - - /** - * Puts the specified guest thread to sleep until the returned event is signaled or until the - * specified timeout expires. - * @param reason Reason for pausing the thread, to be used for debugging purposes. - * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback - * invoked with a Timeout reason. - * @param callback Callback to be invoked when the thread is resumed. This callback must write - * the entire command response once again, regardless of the state of it before this function - * was called. - * @param writable_event Event to use to wake up the thread. If unspecified, an event will be - * created. - * @returns Event that when signaled will resume the thread and call the callback function. - */ - std::shared_ptr<WritableEvent> SleepClientThread( - const std::string& reason, u64 timeout, WakeupCallback&& callback, - std::shared_ptr<WritableEvent> writable_event = nullptr); - /// Populates this context with data from the requesting process/thread. ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf); /// Writes data from this context back to the requesting process/thread. - ResultCode WriteToOutgoingCommandBuffer(Thread& thread); + ResultCode WriteToOutgoingCommandBuffer(KThread& thread); u32_le GetCommand() const { return command; @@ -220,6 +204,12 @@ public: /// Helper function to get the size of the output buffer std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const; + /// Helper function to test whether the input buffer at buffer_index can be read + bool CanReadBuffer(std::size_t buffer_index = 0) const; + + /// Helper function to test whether the output buffer at buffer_index can be written + bool CanWriteBuffer(std::size_t buffer_index = 0) const; + template <typename T> std::shared_ptr<T> GetCopyObject(std::size_t index) { return DynamicObjectCast<T>(copy_objects.at(index)); @@ -274,11 +264,11 @@ public: std::string Description() const; - Thread& GetThread() { + KThread& GetThread() { return *thread; } - const Thread& GetThread() const { + const KThread& GetThread() const { return *thread; } @@ -287,11 +277,13 @@ public: } private: + friend class IPC::ResponseBuilder; + void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming); std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; std::shared_ptr<Kernel::ServerSession> server_session; - std::shared_ptr<Thread> thread; + std::shared_ptr<KThread> thread; // TODO(yuriks): Check common usage of this and optimize size accordingly boost::container::small_vector<std::shared_ptr<Object>, 8> move_objects; boost::container::small_vector<std::shared_ptr<Object>, 8> copy_objects; diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp new file mode 100644 index 000000000..7018f56da --- /dev/null +++ b/src/core/hle/kernel/k_address_arbiter.cpp @@ -0,0 +1,341 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/arm/exclusive_monitor.h" +#include "core/core.h" +#include "core/hle/kernel/k_address_arbiter.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/time_manager.h" +#include "core/memory.h" + +namespace Kernel { + +KAddressArbiter::KAddressArbiter(Core::System& system_) + : system{system_}, kernel{system.Kernel()} {} +KAddressArbiter::~KAddressArbiter() = default; + +namespace { + +bool ReadFromUser(Core::System& system, s32* out, VAddr address) { + *out = system.Memory().Read32(address); + return true; +} + +bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) { + auto& monitor = system.Monitor(); + const auto current_core = system.CurrentCoreIndex(); + + // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. + // TODO(bunnei): We should call CanAccessAtomic(..) here. + + // Load the value from the address. + const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address)); + + // Compare it to the desired one. + if (current_value < value) { + // If less than, we want to try to decrement. + const s32 decrement_value = current_value - 1; + + // Decrement and try to store. + if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value))) { + // If we failed to store, try again. + DecrementIfLessThan(system, out, address, value); + } + } else { + // Otherwise, clear our exclusive hold and finish + monitor.ClearExclusive(); + } + + // We're done. + *out = current_value; + return true; +} + +bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) { + auto& monitor = system.Monitor(); + const auto current_core = system.CurrentCoreIndex(); + + // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. + // TODO(bunnei): We should call CanAccessAtomic(..) here. + + // Load the value from the address. + const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address)); + + // Compare it to the desired one. + if (current_value == value) { + // If equal, we want to try to write the new value. + + // Try to store. + if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(new_value))) { + // If we failed to store, try again. + UpdateIfEqual(system, out, address, value, new_value); + } + } else { + // Otherwise, clear our exclusive hold and finish. + monitor.ClearExclusive(); + } + + // We're done. + *out = current_value; + return true; +} + +} // namespace + +ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) { + // Perform signaling. + s32 num_waiters{}; + { + KScopedSchedulerLock sl(kernel); + + auto it = thread_tree.nfind_light({addr, -1}); + while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && + (it->GetAddressArbiterKey() == addr)) { + KThread* target_thread = std::addressof(*it); + target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + + ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->Wakeup(); + + it = thread_tree.erase(it); + target_thread->ClearAddressArbiter(); + ++num_waiters; + } + } + return RESULT_SUCCESS; +} + +ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count) { + // Perform signaling. + s32 num_waiters{}; + { + KScopedSchedulerLock sl(kernel); + + // Check the userspace value. + s32 user_value{}; + if (!UpdateIfEqual(system, &user_value, addr, value, value + 1)) { + LOG_ERROR(Kernel, "Invalid current memory!"); + return ResultInvalidCurrentMemory; + } + if (user_value != value) { + return ResultInvalidState; + } + + auto it = thread_tree.nfind_light({addr, -1}); + while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && + (it->GetAddressArbiterKey() == addr)) { + KThread* target_thread = std::addressof(*it); + target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + + ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->Wakeup(); + + it = thread_tree.erase(it); + target_thread->ClearAddressArbiter(); + ++num_waiters; + } + } + return RESULT_SUCCESS; +} + +ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count) { + // Perform signaling. + s32 num_waiters{}; + { + [[maybe_unused]] const KScopedSchedulerLock sl(kernel); + + auto it = thread_tree.nfind_light({addr, -1}); + // Determine the updated value. + s32 new_value{}; + if (count <= 0) { + if (it != thread_tree.end() && it->GetAddressArbiterKey() == addr) { + new_value = value - 2; + } else { + new_value = value + 1; + } + } else { + if (it != thread_tree.end() && it->GetAddressArbiterKey() == addr) { + auto tmp_it = it; + s32 tmp_num_waiters{}; + while (++tmp_it != thread_tree.end() && tmp_it->GetAddressArbiterKey() == addr) { + if (tmp_num_waiters++ >= count) { + break; + } + } + + if (tmp_num_waiters < count) { + new_value = value - 1; + } else { + new_value = value; + } + } else { + new_value = value + 1; + } + } + + // Check the userspace value. + s32 user_value{}; + bool succeeded{}; + if (value != new_value) { + succeeded = UpdateIfEqual(system, &user_value, addr, value, new_value); + } else { + succeeded = ReadFromUser(system, &user_value, addr); + } + + if (!succeeded) { + LOG_ERROR(Kernel, "Invalid current memory!"); + return ResultInvalidCurrentMemory; + } + if (user_value != value) { + return ResultInvalidState; + } + + while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && + (it->GetAddressArbiterKey() == addr)) { + KThread* target_thread = std::addressof(*it); + target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + + ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->Wakeup(); + + it = thread_tree.erase(it); + target_thread->ClearAddressArbiter(); + ++num_waiters; + } + } + return RESULT_SUCCESS; +} + +ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) { + // Prepare to wait. + KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); + + { + KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout}; + + // Check that the thread isn't terminating. + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + return ResultTerminationRequested; + } + + // Set the synced object. + cur_thread->SetSyncedObject(nullptr, ResultTimedOut); + + // Read the value from userspace. + s32 user_value{}; + bool succeeded{}; + if (decrement) { + succeeded = DecrementIfLessThan(system, &user_value, addr, value); + } else { + succeeded = ReadFromUser(system, &user_value, addr); + } + + if (!succeeded) { + slp.CancelSleep(); + return ResultInvalidCurrentMemory; + } + + // Check that the value is less than the specified one. + if (user_value >= value) { + slp.CancelSleep(); + return ResultInvalidState; + } + + // Check that the timeout is non-zero. + if (timeout == 0) { + slp.CancelSleep(); + return ResultTimedOut; + } + + // Set the arbiter. + cur_thread->SetAddressArbiter(&thread_tree, addr); + thread_tree.insert(*cur_thread); + cur_thread->SetState(ThreadState::Waiting); + cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration); + } + + // Cancel the timer wait. + kernel.TimeManager().UnscheduleTimeEvent(cur_thread); + + // Remove from the address arbiter. + { + KScopedSchedulerLock sl(kernel); + + if (cur_thread->IsWaitingForAddressArbiter()) { + thread_tree.erase(thread_tree.iterator_to(*cur_thread)); + cur_thread->ClearAddressArbiter(); + } + } + + // Get the result. + KSynchronizationObject* dummy{}; + return cur_thread->GetWaitResult(&dummy); +} + +ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) { + // Prepare to wait. + KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); + + { + KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout}; + + // Check that the thread isn't terminating. + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + return ResultTerminationRequested; + } + + // Set the synced object. + cur_thread->SetSyncedObject(nullptr, ResultTimedOut); + + // Read the value from userspace. + s32 user_value{}; + if (!ReadFromUser(system, &user_value, addr)) { + slp.CancelSleep(); + return ResultInvalidCurrentMemory; + } + + // Check that the value is equal. + if (value != user_value) { + slp.CancelSleep(); + return ResultInvalidState; + } + + // Check that the timeout is non-zero. + if (timeout == 0) { + slp.CancelSleep(); + return ResultTimedOut; + } + + // Set the arbiter. + cur_thread->SetAddressArbiter(&thread_tree, addr); + thread_tree.insert(*cur_thread); + cur_thread->SetState(ThreadState::Waiting); + cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration); + } + + // Cancel the timer wait. + kernel.TimeManager().UnscheduleTimeEvent(cur_thread); + + // Remove from the address arbiter. + { + KScopedSchedulerLock sl(kernel); + + if (cur_thread->IsWaitingForAddressArbiter()) { + thread_tree.erase(thread_tree.iterator_to(*cur_thread)); + cur_thread->ClearAddressArbiter(); + } + } + + // Get the result. + KSynchronizationObject* dummy{}; + return cur_thread->GetWaitResult(&dummy); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h new file mode 100644 index 000000000..8d379b524 --- /dev/null +++ b/src/core/hle/kernel/k_address_arbiter.h @@ -0,0 +1,70 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/k_condition_variable.h" +#include "core/hle/kernel/svc_types.h" + +union ResultCode; + +namespace Core { +class System; +} + +namespace Kernel { + +class KernelCore; + +class KAddressArbiter { +public: + using ThreadTree = KConditionVariable::ThreadTree; + + explicit KAddressArbiter(Core::System& system_); + ~KAddressArbiter(); + + [[nodiscard]] ResultCode SignalToAddress(VAddr addr, Svc::SignalType type, s32 value, + s32 count) { + switch (type) { + case Svc::SignalType::Signal: + return Signal(addr, count); + case Svc::SignalType::SignalAndIncrementIfEqual: + return SignalAndIncrementIfEqual(addr, value, count); + case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: + return SignalAndModifyByWaitingCountIfEqual(addr, value, count); + } + UNREACHABLE(); + return RESULT_UNKNOWN; + } + + [[nodiscard]] ResultCode WaitForAddress(VAddr addr, Svc::ArbitrationType type, s32 value, + s64 timeout) { + switch (type) { + case Svc::ArbitrationType::WaitIfLessThan: + return WaitIfLessThan(addr, value, false, timeout); + case Svc::ArbitrationType::DecrementAndWaitIfLessThan: + return WaitIfLessThan(addr, value, true, timeout); + case Svc::ArbitrationType::WaitIfEqual: + return WaitIfEqual(addr, value, timeout); + } + UNREACHABLE(); + return RESULT_UNKNOWN; + } + +private: + [[nodiscard]] ResultCode Signal(VAddr addr, s32 count); + [[nodiscard]] ResultCode SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count); + [[nodiscard]] ResultCode SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count); + [[nodiscard]] ResultCode WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout); + [[nodiscard]] ResultCode WaitIfEqual(VAddr addr, s32 value, s64 timeout); + + ThreadTree thread_tree; + + Core::System& system; + KernelCore& kernel; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_affinity_mask.h b/src/core/hle/kernel/k_affinity_mask.h new file mode 100644 index 000000000..b906895fc --- /dev/null +++ b/src/core/hle/kernel/k_affinity_mask.h @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hardware_properties.h" + +namespace Kernel { + +class KAffinityMask { +public: + constexpr KAffinityMask() = default; + + [[nodiscard]] constexpr u64 GetAffinityMask() const { + return this->mask; + } + + constexpr void SetAffinityMask(u64 new_mask) { + ASSERT((new_mask & ~AllowedAffinityMask) == 0); + this->mask = new_mask; + } + + [[nodiscard]] constexpr bool GetAffinity(s32 core) const { + return (this->mask & GetCoreBit(core)) != 0; + } + + constexpr void SetAffinity(s32 core, bool set) { + ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + + if (set) { + this->mask |= GetCoreBit(core); + } else { + this->mask &= ~GetCoreBit(core); + } + } + + constexpr void SetAll() { + this->mask = AllowedAffinityMask; + } + +private: + [[nodiscard]] static constexpr u64 GetCoreBit(s32 core) { + ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + return (1ULL << core); + } + + static constexpr u64 AllowedAffinityMask = (1ULL << Core::Hardware::NUM_CPU_CORES) - 1; + + u64 mask{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp new file mode 100644 index 000000000..170d8fa0d --- /dev/null +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -0,0 +1,345 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> + +#include "core/arm/exclusive_monitor.h" +#include "core/core.h" +#include "core/hle/kernel/k_condition_variable.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/svc_common.h" +#include "core/hle/kernel/svc_results.h" +#include "core/memory.h" + +namespace Kernel { + +namespace { + +bool ReadFromUser(Core::System& system, u32* out, VAddr address) { + *out = system.Memory().Read32(address); + return true; +} + +bool WriteToUser(Core::System& system, VAddr address, const u32* p) { + system.Memory().Write32(address, *p); + return true; +} + +bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero, + u32 new_orr_mask) { + auto& monitor = system.Monitor(); + const auto current_core = system.CurrentCoreIndex(); + + // Load the value from the address. + const auto expected = monitor.ExclusiveRead32(current_core, address); + + // Orr in the new mask. + u32 value = expected | new_orr_mask; + + // If the value is zero, use the if_zero value, otherwise use the newly orr'd value. + if (!expected) { + value = if_zero; + } + + // Try to store. + if (!monitor.ExclusiveWrite32(current_core, address, value)) { + // If we failed to store, try again. + return UpdateLockAtomic(system, out, address, if_zero, new_orr_mask); + } + + // We're done. + *out = expected; + return true; +} + +} // namespace + +KConditionVariable::KConditionVariable(Core::System& system_) + : system{system_}, kernel{system.Kernel()} {} + +KConditionVariable::~KConditionVariable() = default; + +ResultCode KConditionVariable::SignalToAddress(VAddr addr) { + KThread* owner_thread = kernel.CurrentScheduler()->GetCurrentThread(); + + // Signal the address. + { + KScopedSchedulerLock sl(kernel); + + // Remove waiter thread. + s32 num_waiters{}; + KThread* next_owner_thread = + owner_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr); + + // Determine the next tag. + u32 next_value{}; + if (next_owner_thread) { + next_value = next_owner_thread->GetAddressKeyValue(); + if (num_waiters > 1) { + next_value |= Svc::HandleWaitMask; + } + + next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + next_owner_thread->Wakeup(); + } + + // Write the value to userspace. + if (!WriteToUser(system, addr, std::addressof(next_value))) { + if (next_owner_thread) { + next_owner_thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory); + } + + return ResultInvalidCurrentMemory; + } + } + + return RESULT_SUCCESS; +} + +ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) { + KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); + + // Wait for the address. + { + std::shared_ptr<KThread> owner_thread; + ASSERT(!owner_thread); + { + KScopedSchedulerLock sl(kernel); + cur_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + + // Check if the thread should terminate. + R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested); + + { + // Read the tag from userspace. + u32 test_tag{}; + R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr), + ResultInvalidCurrentMemory); + + // If the tag isn't the handle (with wait mask), we're done. + R_UNLESS(test_tag == (handle | Svc::HandleWaitMask), RESULT_SUCCESS); + + // Get the lock owner thread. + owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<KThread>(handle); + R_UNLESS(owner_thread, ResultInvalidHandle); + + // Update the lock. + cur_thread->SetAddressKey(addr, value); + owner_thread->AddWaiter(cur_thread); + cur_thread->SetState(ThreadState::Waiting); + cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar); + cur_thread->SetMutexWaitAddressForDebugging(addr); + } + } + ASSERT(owner_thread); + } + + // Remove the thread as a waiter from the lock owner. + { + KScopedSchedulerLock sl(kernel); + KThread* owner_thread = cur_thread->GetLockOwner(); + if (owner_thread != nullptr) { + owner_thread->RemoveWaiter(cur_thread); + } + } + + // Get the wait result. + KSynchronizationObject* dummy{}; + return cur_thread->GetWaitResult(std::addressof(dummy)); +} + +KThread* KConditionVariable::SignalImpl(KThread* thread) { + // Check pre-conditions. + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Update the tag. + VAddr address = thread->GetAddressKey(); + u32 own_tag = thread->GetAddressKeyValue(); + + u32 prev_tag{}; + bool can_access{}; + { + // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. + // TODO(bunnei): We should call CanAccessAtomic(..) here. + can_access = true; + if (can_access) { + UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag, + Svc::HandleWaitMask); + } + } + + KThread* thread_to_close = nullptr; + if (can_access) { + if (prev_tag == InvalidHandle) { + // If nobody held the lock previously, we're all good. + thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + thread->Wakeup(); + } else { + // Get the previous owner. + auto owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<KThread>( + prev_tag & ~Svc::HandleWaitMask); + + if (owner_thread) { + // Add the thread as a waiter on the owner. + owner_thread->AddWaiter(thread); + thread_to_close = owner_thread.get(); + } else { + // The lock was tagged with a thread that doesn't exist. + thread->SetSyncedObject(nullptr, ResultInvalidState); + thread->Wakeup(); + } + } + } else { + // If the address wasn't accessible, note so. + thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory); + thread->Wakeup(); + } + + return thread_to_close; +} + +void KConditionVariable::Signal(u64 cv_key, s32 count) { + // Prepare for signaling. + constexpr int MaxThreads = 16; + + // TODO(bunnei): This should just be Thread once we implement KAutoObject instead of using + // std::shared_ptr. + std::vector<std::shared_ptr<KThread>> thread_list; + std::array<KThread*, MaxThreads> thread_array; + s32 num_to_close{}; + + // Perform signaling. + s32 num_waiters{}; + { + KScopedSchedulerLock sl(kernel); + + auto it = thread_tree.nfind_light({cv_key, -1}); + while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && + (it->GetConditionVariableKey() == cv_key)) { + KThread* target_thread = std::addressof(*it); + + if (KThread* thread = SignalImpl(target_thread); thread != nullptr) { + if (num_to_close < MaxThreads) { + thread_array[num_to_close++] = thread; + } else { + thread_list.push_back(SharedFrom(thread)); + } + } + + it = thread_tree.erase(it); + target_thread->ClearConditionVariable(); + ++num_waiters; + } + + // If we have no waiters, clear the has waiter flag. + if (it == thread_tree.end() || it->GetConditionVariableKey() != cv_key) { + const u32 has_waiter_flag{}; + WriteToUser(system, cv_key, std::addressof(has_waiter_flag)); + } + } + + // Close threads in the array. + for (auto i = 0; i < num_to_close; ++i) { + thread_array[i]->Close(); + } + + // Close threads in the list. + for (auto it = thread_list.begin(); it != thread_list.end(); it = thread_list.erase(it)) { + (*it)->Close(); + } +} + +ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) { + // Prepare to wait. + KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); + + { + KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout}; + + // Set the synced object. + cur_thread->SetSyncedObject(nullptr, ResultTimedOut); + + // Check that the thread isn't terminating. + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + return ResultTerminationRequested; + } + + // Update the value and process for the next owner. + { + // Remove waiter thread. + s32 num_waiters{}; + KThread* next_owner_thread = + cur_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr); + + // Update for the next owner thread. + u32 next_value{}; + if (next_owner_thread != nullptr) { + // Get the next tag value. + next_value = next_owner_thread->GetAddressKeyValue(); + if (num_waiters > 1) { + next_value |= Svc::HandleWaitMask; + } + + // Wake up the next owner. + next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); + next_owner_thread->Wakeup(); + } + + // Write to the cv key. + { + const u32 has_waiter_flag = 1; + WriteToUser(system, key, std::addressof(has_waiter_flag)); + // TODO(bunnei): We should call DataMemoryBarrier(..) here. + } + + // Write the value to userspace. + if (!WriteToUser(system, addr, std::addressof(next_value))) { + slp.CancelSleep(); + return ResultInvalidCurrentMemory; + } + } + + // Update condition variable tracking. + { + cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value); + thread_tree.insert(*cur_thread); + } + + // If the timeout is non-zero, set the thread as waiting. + if (timeout != 0) { + cur_thread->SetState(ThreadState::Waiting); + cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar); + cur_thread->SetMutexWaitAddressForDebugging(addr); + } + } + + // Cancel the timer wait. + kernel.TimeManager().UnscheduleTimeEvent(cur_thread); + + // Remove from the condition variable. + { + KScopedSchedulerLock sl(kernel); + + if (KThread* owner = cur_thread->GetLockOwner(); owner != nullptr) { + owner->RemoveWaiter(cur_thread); + } + + if (cur_thread->IsWaitingForConditionVariable()) { + thread_tree.erase(thread_tree.iterator_to(*cur_thread)); + cur_thread->ClearConditionVariable(); + } + } + + // Get the result. + KSynchronizationObject* dummy{}; + return cur_thread->GetWaitResult(std::addressof(dummy)); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h new file mode 100644 index 000000000..861dbd420 --- /dev/null +++ b/src/core/hle/kernel/k_condition_variable.h @@ -0,0 +1,59 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "common/common_types.h" + +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/result.h" + +namespace Core { +class System; +} + +namespace Kernel { + +class KConditionVariable { +public: + using ThreadTree = typename KThread::ConditionVariableThreadTreeType; + + explicit KConditionVariable(Core::System& system_); + ~KConditionVariable(); + + // Arbitration + [[nodiscard]] ResultCode SignalToAddress(VAddr addr); + [[nodiscard]] ResultCode WaitForAddress(Handle handle, VAddr addr, u32 value); + + // Condition variable + void Signal(u64 cv_key, s32 count); + [[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout); + +private: + [[nodiscard]] KThread* SignalImpl(KThread* thread); + + ThreadTree thread_tree; + + Core::System& system; + KernelCore& kernel; +}; + +inline void BeforeUpdatePriority(const KernelCore& kernel, KConditionVariable::ThreadTree* tree, + KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + tree->erase(tree->iterator_to(*thread)); +} + +inline void AfterUpdatePriority(const KernelCore& kernel, KConditionVariable::ThreadTree* tree, + KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + tree->insert(*thread); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp new file mode 100644 index 000000000..bb2fa4ad5 --- /dev/null +++ b/src/core/hle/kernel/k_event.cpp @@ -0,0 +1,32 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" + +namespace Kernel { + +KEvent::KEvent(KernelCore& kernel, std::string&& name) : Object{kernel, std::move(name)} {} + +KEvent::~KEvent() = default; + +std::shared_ptr<KEvent> KEvent::Create(KernelCore& kernel, std::string&& name) { + return std::make_shared<KEvent>(kernel, std::move(name)); +} + +void KEvent::Initialize() { + // Create our sub events. + readable_event = std::make_shared<KReadableEvent>(kernel, GetName() + ":Readable"); + writable_event = std::make_shared<KWritableEvent>(kernel, GetName() + ":Writable"); + + // Initialize our sub sessions. + readable_event->Initialize(this); + writable_event->Initialize(this); + + // Mark initialized. + initialized = true; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_event.h b/src/core/hle/kernel/k_event.h new file mode 100644 index 000000000..2fb887129 --- /dev/null +++ b/src/core/hle/kernel/k_event.h @@ -0,0 +1,57 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/object.h" + +namespace Kernel { + +class KernelCore; +class KReadableEvent; +class KWritableEvent; + +class KEvent final : public Object { +public: + explicit KEvent(KernelCore& kernel, std::string&& name); + ~KEvent() override; + + static std::shared_ptr<KEvent> Create(KernelCore& kernel, std::string&& name); + + void Initialize(); + + void Finalize() override {} + + std::string GetTypeName() const override { + return "KEvent"; + } + + static constexpr HandleType HANDLE_TYPE = HandleType::Event; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + std::shared_ptr<KReadableEvent>& GetReadableEvent() { + return readable_event; + } + + std::shared_ptr<KWritableEvent>& GetWritableEvent() { + return writable_event; + } + + const std::shared_ptr<KReadableEvent>& GetReadableEvent() const { + return readable_event; + } + + const std::shared_ptr<KWritableEvent>& GetWritableEvent() const { + return writable_event; + } + +private: + std::shared_ptr<KReadableEvent> readable_event; + std::shared_ptr<KWritableEvent> writable_event; + bool initialized{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_light_condition_variable.h b/src/core/hle/kernel/k_light_condition_variable.h new file mode 100644 index 000000000..362d0db28 --- /dev/null +++ b/src/core/hle/kernel/k_light_condition_variable.h @@ -0,0 +1,57 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_thread_queue.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { +class KernelCore; + +class KLightConditionVariable { +public: + explicit KLightConditionVariable(KernelCore& kernel) : thread_queue(kernel), kernel(kernel) {} + + void Wait(KLightLock* lock, s64 timeout = -1) { + WaitImpl(lock, timeout); + lock->Lock(); + } + + void Broadcast() { + KScopedSchedulerLock lk{kernel}; + while (thread_queue.WakeupFrontThread() != nullptr) { + // We want to signal all threads, and so should continue waking up until there's nothing + // to wake. + } + } + +private: + void WaitImpl(KLightLock* lock, s64 timeout) { + KThread* owner = GetCurrentThreadPointer(kernel); + + // Sleep the thread. + { + KScopedSchedulerLockAndSleep lk(kernel, owner, timeout); + lock->Unlock(); + + if (!thread_queue.SleepThread(owner)) { + lk.CancelSleep(); + return; + } + } + + // Cancel the task that the sleep setup. + kernel.TimeManager().UnscheduleTimeEvent(owner); + } + KThreadQueue thread_queue; + KernelCore& kernel; +}; +} // namespace Kernel diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp new file mode 100644 index 000000000..f974022e8 --- /dev/null +++ b/src/core/hle/kernel/k_light_lock.cpp @@ -0,0 +1,130 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_light_lock.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +void KLightLock::Lock() { + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)); + const uintptr_t cur_thread_tag = (cur_thread | 1); + + while (true) { + uintptr_t old_tag = tag.load(std::memory_order_relaxed); + + while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : old_tag | 1, + std::memory_order_acquire)) { + if ((old_tag | 1) == cur_thread_tag) { + return; + } + } + + if ((old_tag == 0) || ((old_tag | 1) == cur_thread_tag)) { + break; + } + + LockSlowPath(old_tag | 1, cur_thread); + } +} + +void KLightLock::Unlock() { + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)); + uintptr_t expected = cur_thread; + do { + if (expected != cur_thread) { + return UnlockSlowPath(cur_thread); + } + } while (!tag.compare_exchange_weak(expected, 0, std::memory_order_release)); +} + +void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { + KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread); + + // Pend the current thread waiting on the owner thread. + { + KScopedSchedulerLock sl{kernel}; + + // Ensure we actually have locking to do. + if (tag.load(std::memory_order_relaxed) != _owner) { + return; + } + + // Add the current thread as a waiter on the owner. + KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL); + cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); + owner_thread->AddWaiter(cur_thread); + + // Set thread states. + if (cur_thread->GetState() == ThreadState::Runnable) { + cur_thread->SetState(ThreadState::Waiting); + } else { + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + + if (owner_thread->IsSuspended()) { + owner_thread->ContinueIfHasKernelWaiters(); + } + } + + // We're no longer waiting on the lock owner. + { + KScopedSchedulerLock sl{kernel}; + KThread* owner_thread = cur_thread->GetLockOwner(); + if (owner_thread) { + owner_thread->RemoveWaiter(cur_thread); + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + } +} + +void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { + KThread* owner_thread = reinterpret_cast<KThread*>(_cur_thread); + + // Unlock. + { + KScopedSchedulerLock sl{kernel}; + + // Get the next owner. + s32 num_waiters = 0; + KThread* next_owner = owner_thread->RemoveWaiterByKey( + std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag))); + + // Pass the lock to the next owner. + uintptr_t next_tag = 0; + if (next_owner) { + next_tag = reinterpret_cast<uintptr_t>(next_owner); + if (num_waiters > 1) { + next_tag |= 0x1; + } + + if (next_owner->GetState() == ThreadState::Waiting) { + next_owner->SetState(ThreadState::Runnable); + } else { + KScheduler::SetSchedulerUpdateNeeded(kernel); + } + + if (next_owner->IsSuspended()) { + next_owner->ContinueIfHasKernelWaiters(); + } + } + + // We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if + // so. + if (owner_thread->IsSuspended()) { + owner_thread->TrySuspend(); + } + + // Write the new tag value. + tag.store(next_tag); + } +} + +bool KLightLock::IsLockedByCurrentThread() const { + return (tag | 1ULL) == (reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)) | 1ULL); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_light_lock.h b/src/core/hle/kernel/k_light_lock.h new file mode 100644 index 000000000..f4c45f76a --- /dev/null +++ b/src/core/hle/kernel/k_light_lock.h @@ -0,0 +1,41 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> + +#include "common/common_types.h" +#include "core/hle/kernel/k_scoped_lock.h" + +namespace Kernel { + +class KernelCore; + +class KLightLock { +public: + explicit KLightLock(KernelCore& kernel_) : kernel{kernel_} {} + + void Lock(); + + void Unlock(); + + void LockSlowPath(uintptr_t owner, uintptr_t cur_thread); + + void UnlockSlowPath(uintptr_t cur_thread); + + bool IsLocked() const { + return tag != 0; + } + + bool IsLockedByCurrentThread() const; + +private: + std::atomic<uintptr_t> tag{}; + KernelCore& kernel; +}; + +using KScopedLightLock = KScopedLock<KLightLock>; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h new file mode 100644 index 000000000..4aa669d95 --- /dev/null +++ b/src/core/hle/kernel/k_priority_queue.h @@ -0,0 +1,451 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include <array> +#include <bit> +#include <concepts> + +#include "common/assert.h" +#include "common/bit_set.h" +#include "common/common_types.h" +#include "common/concepts.h" + +namespace Kernel { + +class KThread; + +template <typename T> +concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) { + { t.GetAffinityMask() } + ->Common::ConvertibleTo<u64>; + {t.SetAffinityMask(0)}; + + { t.GetAffinity(0) } + ->std::same_as<bool>; + {t.SetAffinity(0, false)}; + {t.SetAll()}; +}; + +template <typename T> +concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) { + {typename T::QueueEntry()}; + {(typename T::QueueEntry()).Initialize()}; + {(typename T::QueueEntry()).SetPrev(std::addressof(t))}; + {(typename T::QueueEntry()).SetNext(std::addressof(t))}; + { (typename T::QueueEntry()).GetNext() } + ->std::same_as<T*>; + { (typename T::QueueEntry()).GetPrev() } + ->std::same_as<T*>; + { t.GetPriorityQueueEntry(0) } + ->std::same_as<typename T::QueueEntry&>; + + {t.GetAffinityMask()}; + { std::remove_cvref_t<decltype(t.GetAffinityMask())>() } + ->KPriorityQueueAffinityMask; + + { t.GetActiveCore() } + ->Common::ConvertibleTo<s32>; + { t.GetPriority() } + ->Common::ConvertibleTo<s32>; +}; + +template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority> +requires KPriorityQueueMember<Member> class KPriorityQueue { +public: + using AffinityMaskType = std::remove_cv_t< + std::remove_reference_t<decltype(std::declval<Member>().GetAffinityMask())>>; + + static_assert(LowestPriority >= 0); + static_assert(HighestPriority >= 0); + static_assert(LowestPriority >= HighestPriority); + static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1; + static constexpr size_t NumCores = NumCores_; + + static constexpr bool IsValidCore(s32 core) { + return 0 <= core && core < static_cast<s32>(NumCores); + } + + static constexpr bool IsValidPriority(s32 priority) { + return HighestPriority <= priority && priority <= LowestPriority + 1; + } + +private: + using Entry = typename Member::QueueEntry; + +public: + class KPerCoreQueue { + private: + std::array<Entry, NumCores> root{}; + + public: + constexpr KPerCoreQueue() { + for (auto& per_core_root : root) { + per_core_root.Initialize(); + } + } + + constexpr bool PushBack(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entry associated with the end of the queue. + Member* tail = this->root[core].GetPrev(); + Entry& tail_entry = + (tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core]; + + // Link the entries. + member_entry.SetPrev(tail); + member_entry.SetNext(nullptr); + tail_entry.SetNext(member); + this->root[core].SetPrev(member); + + return tail == nullptr; + } + + constexpr bool PushFront(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entry associated with the front of the queue. + Member* head = this->root[core].GetNext(); + Entry& head_entry = + (head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core]; + + // Link the entries. + member_entry.SetPrev(nullptr); + member_entry.SetNext(head); + head_entry.SetPrev(member); + this->root[core].SetNext(member); + + return (head == nullptr); + } + + constexpr bool Remove(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entries associated with next and prev. + Member* prev = member_entry.GetPrev(); + Member* next = member_entry.GetNext(); + Entry& prev_entry = + (prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core]; + Entry& next_entry = + (next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core]; + + // Unlink. + prev_entry.SetNext(next); + next_entry.SetPrev(prev); + + return (this->GetFront(core) == nullptr); + } + + constexpr Member* GetFront(s32 core) const { + return this->root[core].GetNext(); + } + }; + + class KPriorityQueueImpl { + public: + constexpr KPriorityQueueImpl() = default; + + constexpr void PushBack(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].PushBack(core, member)) { + this->available_priorities[core].SetBit(priority); + } + } + + constexpr void PushFront(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].PushFront(core, member)) { + this->available_priorities[core].SetBit(priority); + } + } + + constexpr void Remove(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].Remove(core, member)) { + this->available_priorities[core].ClearBit(priority); + } + } + + constexpr Member* GetFront(s32 core) const { + ASSERT(IsValidCore(core)); + + const s32 priority = + static_cast<s32>(this->available_priorities[core].CountLeadingZero()); + if (priority <= LowestPriority) { + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr Member* GetFront(s32 priority, s32 core) const { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr Member* GetNext(s32 core, const Member* member) const { + ASSERT(IsValidCore(core)); + + Member* next = member->GetPriorityQueueEntry(core).GetNext(); + if (next == nullptr) { + const s32 priority = static_cast<s32>( + this->available_priorities[core].GetNextSet(member->GetPriority())); + if (priority <= LowestPriority) { + next = this->queues[priority].GetFront(core); + } + } + return next; + } + + constexpr void MoveToFront(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + this->queues[priority].Remove(core, member); + this->queues[priority].PushFront(core, member); + } + } + + constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + this->queues[priority].Remove(core, member); + this->queues[priority].PushBack(core, member); + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + private: + std::array<KPerCoreQueue, NumPriority> queues{}; + std::array<Common::BitSet64<NumPriority>, NumCores> available_priorities{}; + }; + +private: + KPriorityQueueImpl scheduled_queue; + KPriorityQueueImpl suggested_queue; + +private: + constexpr void ClearAffinityBit(u64& affinity, s32 core) { + affinity &= ~(u64(1) << core); + } + + constexpr s32 GetNextCore(u64& affinity) { + const s32 core = std::countr_zero(affinity); + ClearAffinityBit(affinity, core); + return core; + } + + constexpr void PushBack(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Push onto the scheduled queue for its core, if we can. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.PushBack(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // And suggest the thread for all other cores. + while (affinity) { + this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr void PushFront(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Push onto the scheduled queue for its core, if we can. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.PushFront(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // And suggest the thread for all other cores. + // Note: Nintendo pushes onto the back of the suggested queue, not the front. + while (affinity) { + this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr void Remove(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Remove from the scheduled queue for its core. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.Remove(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // Remove from the suggested queue for all other cores. + while (affinity) { + this->suggested_queue.Remove(priority, GetNextCore(affinity), member); + } + } + +public: + constexpr KPriorityQueue() = default; + + // Getters. + constexpr Member* GetScheduledFront(s32 core) const { + return this->scheduled_queue.GetFront(core); + } + + constexpr Member* GetScheduledFront(s32 core, s32 priority) const { + return this->scheduled_queue.GetFront(priority, core); + } + + constexpr Member* GetSuggestedFront(s32 core) const { + return this->suggested_queue.GetFront(core); + } + + constexpr Member* GetSuggestedFront(s32 core, s32 priority) const { + return this->suggested_queue.GetFront(priority, core); + } + + constexpr Member* GetScheduledNext(s32 core, const Member* member) const { + return this->scheduled_queue.GetNext(core, member); + } + + constexpr Member* GetSuggestedNext(s32 core, const Member* member) const { + return this->suggested_queue.GetNext(core, member); + } + + constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const { + return member->GetPriorityQueueEntry(core).GetNext(); + } + + // Mutators. + constexpr void PushBack(Member* member) { + this->PushBack(member->GetPriority(), member); + } + + constexpr void Remove(Member* member) { + this->Remove(member->GetPriority(), member); + } + + constexpr void MoveToScheduledFront(Member* member) { + this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member); + } + + constexpr KThread* MoveToScheduledBack(Member* member) { + return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(), + member); + } + + // First class fancy operations. + constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) { + ASSERT(IsValidPriority(prev_priority)); + + // Remove the member from the queues. + const s32 new_priority = member->GetPriority(); + this->Remove(prev_priority, member); + + // And enqueue. If the member is running, we want to keep it running. + if (is_running) { + this->PushFront(new_priority, member); + } else { + this->PushBack(new_priority, member); + } + } + + constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity, + Member* member) { + // Get the new information. + const s32 priority = member->GetPriority(); + const AffinityMaskType& new_affinity = member->GetAffinityMask(); + const s32 new_core = member->GetActiveCore(); + + // Remove the member from all queues it was in before. + for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { + if (prev_affinity.GetAffinity(core)) { + if (core == prev_core) { + this->scheduled_queue.Remove(priority, core, member); + } else { + this->suggested_queue.Remove(priority, core, member); + } + } + } + + // And add the member to all queues it should be in now. + for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { + if (new_affinity.GetAffinity(core)) { + if (core == new_core) { + this->scheduled_queue.PushBack(priority, core, member); + } else { + this->suggested_queue.PushBack(priority, core, member); + } + } + } + } + + constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) { + // Get the new information. + const s32 new_core = member->GetActiveCore(); + const s32 priority = member->GetPriority(); + + // We don't need to do anything if the core is the same. + if (prev_core != new_core) { + // Remove from the scheduled queue for the previous core. + if (prev_core >= 0) { + this->scheduled_queue.Remove(priority, prev_core, member); + } + + // Remove from the suggested queue and add to the scheduled queue for the new core. + if (new_core >= 0) { + this->suggested_queue.Remove(priority, new_core, member); + if (to_front) { + this->scheduled_queue.PushFront(priority, new_core, member); + } else { + this->scheduled_queue.PushBack(priority, new_core, member); + } + } + + // Add to the suggested queue for the previous core. + if (prev_core >= 0) { + this->suggested_queue.PushBack(priority, prev_core, member); + } + } + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_readable_event.cpp b/src/core/hle/kernel/k_readable_event.cpp new file mode 100644 index 000000000..4b4d34857 --- /dev/null +++ b/src/core/hle/kernel/k_readable_event.cpp @@ -0,0 +1,56 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/logging/log.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { + +KReadableEvent::KReadableEvent(KernelCore& kernel, std::string&& name) + : KSynchronizationObject{kernel, std::move(name)} {} +KReadableEvent::~KReadableEvent() = default; + +bool KReadableEvent::IsSignaled() const { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + return is_signaled; +} + +ResultCode KReadableEvent::Signal() { + KScopedSchedulerLock lk{kernel}; + + if (!is_signaled) { + is_signaled = true; + NotifyAvailable(); + } + + return RESULT_SUCCESS; +} + +ResultCode KReadableEvent::Clear() { + Reset(); + + return RESULT_SUCCESS; +} + +ResultCode KReadableEvent::Reset() { + KScopedSchedulerLock lk{kernel}; + + if (!is_signaled) { + return ResultInvalidState; + } + + is_signaled = false; + return RESULT_SUCCESS; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_readable_event.h b/src/core/hle/kernel/k_readable_event.h new file mode 100644 index 000000000..e6f0fd900 --- /dev/null +++ b/src/core/hle/kernel/k_readable_event.h @@ -0,0 +1,51 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/object.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KernelCore; +class KEvent; + +class KReadableEvent final : public KSynchronizationObject { +public: + explicit KReadableEvent(KernelCore& kernel, std::string&& name); + ~KReadableEvent() override; + + std::string GetTypeName() const override { + return "KReadableEvent"; + } + + static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + KEvent* GetParent() const { + return parent; + } + + void Initialize(KEvent* parent_) { + is_signaled = false; + parent = parent_; + } + + bool IsSignaled() const override; + void Finalize() override {} + + ResultCode Signal(); + ResultCode Clear(); + ResultCode Reset(); + +private: + bool is_signaled{}; + KEvent* parent{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp new file mode 100644 index 000000000..d7a4a38e6 --- /dev/null +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -0,0 +1,152 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#include "common/assert.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { +constexpr s64 DefaultTimeout = 10000000000; // 10 seconds + +KResourceLimit::KResourceLimit(KernelCore& kernel, Core::System& system) + : Object{kernel}, lock{kernel}, cond_var{kernel}, kernel{kernel}, system(system) {} +KResourceLimit::~KResourceLimit() = default; + +s64 KResourceLimit::GetLimitValue(LimitableResource which) const { + const auto index = static_cast<std::size_t>(which); + s64 value{}; + { + KScopedLightLock lk{lock}; + value = limit_values[index]; + ASSERT(value >= 0); + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + } + return value; +} + +s64 KResourceLimit::GetCurrentValue(LimitableResource which) const { + const auto index = static_cast<std::size_t>(which); + s64 value{}; + { + KScopedLightLock lk{lock}; + value = current_values[index]; + ASSERT(value >= 0); + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + } + return value; +} + +s64 KResourceLimit::GetPeakValue(LimitableResource which) const { + const auto index = static_cast<std::size_t>(which); + s64 value{}; + { + KScopedLightLock lk{lock}; + value = peak_values[index]; + ASSERT(value >= 0); + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + } + return value; +} + +s64 KResourceLimit::GetFreeValue(LimitableResource which) const { + const auto index = static_cast<std::size_t>(which); + s64 value{}; + { + KScopedLightLock lk(lock); + ASSERT(current_values[index] >= 0); + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + value = limit_values[index] - current_values[index]; + } + + return value; +} + +ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { + const auto index = static_cast<std::size_t>(which); + KScopedLightLock lk(lock); + R_UNLESS(current_values[index] <= value, ResultInvalidState); + + limit_values[index] = value; + + return RESULT_SUCCESS; +} + +bool KResourceLimit::Reserve(LimitableResource which, s64 value) { + return Reserve(which, value, system.CoreTiming().GetGlobalTimeNs().count() + DefaultTimeout); +} + +bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { + ASSERT(value >= 0); + const auto index = static_cast<std::size_t>(which); + KScopedLightLock lk(lock); + + ASSERT(current_hints[index] <= current_values[index]); + if (current_hints[index] >= limit_values[index]) { + return false; + } + + // Loop until we reserve or run out of time. + while (true) { + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + + // If we would overflow, don't allow to succeed. + if (current_values[index] + value <= current_values[index]) { + break; + } + + if (current_values[index] + value <= limit_values[index]) { + current_values[index] += value; + current_hints[index] += value; + peak_values[index] = std::max(peak_values[index], current_values[index]); + return true; + } + + if (current_hints[index] + value <= limit_values[index] && + (timeout < 0 || system.CoreTiming().GetGlobalTimeNs().count() < timeout)) { + waiter_count++; + cond_var.Wait(&lock, timeout); + waiter_count--; + } else { + break; + } + } + + return false; +} + +void KResourceLimit::Release(LimitableResource which, s64 value) { + Release(which, value, value); +} + +void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { + ASSERT(value >= 0); + ASSERT(hint >= 0); + + const auto index = static_cast<std::size_t>(which); + KScopedLightLock lk(lock); + ASSERT(current_values[index] <= limit_values[index]); + ASSERT(current_hints[index] <= current_values[index]); + ASSERT(value <= current_values[index]); + ASSERT(hint <= current_hints[index]); + + current_values[index] -= value; + current_hints[index] -= hint; + + if (waiter_count != 0) { + cond_var.Broadcast(); + } +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h new file mode 100644 index 000000000..58ae456f1 --- /dev/null +++ b/src/core/hle/kernel/k_resource_limit.h @@ -0,0 +1,81 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include <array> +#include "common/common_types.h" +#include "core/hle/kernel/k_light_condition_variable.h" +#include "core/hle/kernel/k_light_lock.h" +#include "core/hle/kernel/object.h" + +union ResultCode; + +namespace Core { +class System; +} + +namespace Kernel { +class KernelCore; +enum class LimitableResource : u32 { + PhysicalMemory = 0, + Threads = 1, + Events = 2, + TransferMemory = 3, + Sessions = 4, + + Count, +}; + +constexpr bool IsValidResourceType(LimitableResource type) { + return type < LimitableResource::Count; +} + +class KResourceLimit final : public Object { +public: + explicit KResourceLimit(KernelCore& kernel, Core::System& system); + ~KResourceLimit(); + + s64 GetLimitValue(LimitableResource which) const; + s64 GetCurrentValue(LimitableResource which) const; + s64 GetPeakValue(LimitableResource which) const; + s64 GetFreeValue(LimitableResource which) const; + + ResultCode SetLimitValue(LimitableResource which, s64 value); + + bool Reserve(LimitableResource which, s64 value); + bool Reserve(LimitableResource which, s64 value, s64 timeout); + void Release(LimitableResource which, s64 value); + void Release(LimitableResource which, s64 value, s64 hint); + + std::string GetTypeName() const override { + return "KResourceLimit"; + } + std::string GetName() const override { + return GetTypeName(); + } + + static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + virtual void Finalize() override {} + +private: + using ResourceArray = std::array<s64, static_cast<std::size_t>(LimitableResource::Count)>; + ResourceArray limit_values{}; + ResourceArray current_values{}; + ResourceArray current_hints{}; + ResourceArray peak_values{}; + mutable KLightLock lock; + s32 waiter_count{}; + KLightConditionVariable cond_var; + KernelCore& kernel; + Core::System& system; +}; +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp new file mode 100644 index 000000000..bb5f43b53 --- /dev/null +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -0,0 +1,814 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#include <bit> + +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/fiber.h" +#include "common/logging/log.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/cpu_manager.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { + +static void IncrementScheduledCount(Kernel::KThread* thread) { + if (auto process = thread->GetOwnerProcess(); process) { + process->IncrementScheduledCount(); + } +} + +void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) { + auto scheduler = kernel.CurrentScheduler(); + + u32 current_core{0xF}; + bool must_context_switch{}; + if (scheduler) { + current_core = scheduler->core_id; + // TODO(bunnei): Should be set to true when we deprecate single core + must_context_switch = !kernel.IsPhantomModeForSingleCore(); + } + + while (cores_pending_reschedule != 0) { + const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule)); + ASSERT(core < Core::Hardware::NUM_CPU_CORES); + if (!must_context_switch || core != current_core) { + auto& phys_core = kernel.PhysicalCore(core); + phys_core.Interrupt(); + } else { + must_context_switch = true; + } + cores_pending_reschedule &= ~(1ULL << core); + } + if (must_context_switch) { + auto core_scheduler = kernel.CurrentScheduler(); + kernel.ExitSVCProfile(); + core_scheduler->RescheduleCurrentCore(); + kernel.EnterSVCProfile(); + } +} + +u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { + std::scoped_lock lock{guard}; + if (KThread* prev_highest_thread = state.highest_priority_thread; + prev_highest_thread != highest_thread) { + if (prev_highest_thread != nullptr) { + IncrementScheduledCount(prev_highest_thread); + prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks()); + } + if (state.should_count_idle) { + if (highest_thread != nullptr) { + if (Process* process = highest_thread->GetOwnerProcess(); process != nullptr) { + process->SetRunningThread(core_id, highest_thread, state.idle_count); + } + } else { + state.idle_count++; + } + } + + state.highest_priority_thread = highest_thread; + state.needs_scheduling.store(true); + return (1ULL << core_id); + } else { + return 0; + } +} + +u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Clear that we need to update. + ClearSchedulerUpdateNeeded(kernel); + + u64 cores_needing_scheduling = 0, idle_cores = 0; + KThread* top_threads[Core::Hardware::NUM_CPU_CORES]; + auto& priority_queue = GetPriorityQueue(kernel); + + /// We want to go over all cores, finding the highest priority thread and determining if + /// scheduling is needed for that core. + for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id)); + if (top_thread != nullptr) { + // If the thread has no waiters, we need to check if the process has a thread pinned. + if (top_thread->GetNumKernelWaiters() == 0) { + if (Process* parent = top_thread->GetOwnerProcess(); parent != nullptr) { + if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); + pinned != nullptr && pinned != top_thread) { + // We prefer our parent's pinned thread if possible. However, we also don't + // want to schedule un-runnable threads. + if (pinned->GetRawState() == ThreadState::Runnable) { + top_thread = pinned; + } else { + top_thread = nullptr; + } + } + } + } + } else { + idle_cores |= (1ULL << core_id); + } + + top_threads[core_id] = top_thread; + cores_needing_scheduling |= + kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + } + + // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. + while (idle_cores != 0) { + const auto core_id = static_cast<u32>(std::countr_zero(idle_cores)); + if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { + s32 migration_candidates[Core::Hardware::NUM_CPU_CORES]; + size_t num_candidates = 0; + + // While we have a suggested thread, try to migrate it! + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread* top_thread = + (suggested_core >= 0) ? top_threads[suggested_core] : nullptr; + top_thread != suggested) { + // Make sure we're not dealing with threads too high priority for migration. + if (top_thread != nullptr && + top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) { + break; + } + + // The suggested thread isn't bound to its core, so we can migrate it! + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested); + + top_threads[core_id] = suggested; + cores_needing_scheduling |= + kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + break; + } + + // Note this core as a candidate for migration. + ASSERT(num_candidates < Core::Hardware::NUM_CPU_CORES); + migration_candidates[num_candidates++] = suggested_core; + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If suggested is nullptr, we failed to migrate a specific thread. So let's try all our + // candidate cores' top threads. + if (suggested == nullptr) { + for (size_t i = 0; i < num_candidates; i++) { + // Check if there's some other thread that can run on the candidate core. + const s32 candidate_core = migration_candidates[i]; + suggested = top_threads[candidate_core]; + if (KThread* next_on_candidate_core = + priority_queue.GetScheduledNext(candidate_core, suggested); + next_on_candidate_core != nullptr) { + // The candidate core can run some other thread! We'll migrate its current + // top thread to us. + top_threads[candidate_core] = next_on_candidate_core; + cores_needing_scheduling |= + kernel.Scheduler(candidate_core) + .UpdateHighestPriorityThread(top_threads[candidate_core]); + + // Perform the migration. + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(candidate_core, suggested); + + top_threads[core_id] = suggested; + cores_needing_scheduling |= + kernel.Scheduler(core_id).UpdateHighestPriorityThread( + top_threads[core_id]); + break; + } + } + } + } + + idle_cores &= ~(1ULL << core_id); + } + + return cores_needing_scheduling; +} + +void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) { + // Get an atomic reference to the core scheduler's previous thread. + std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread); + static_assert(std::atomic_ref<KThread*>::is_always_lock_free); + + // Atomically clear the previous thread if it's our target. + KThread* compare = thread; + prev_thread.compare_exchange_strong(compare, nullptr); + } +} + +void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Check if the state has changed, because if it hasn't there's nothing to do. + const auto cur_state = thread->GetRawState(); + if (cur_state == old_state) { + return; + } + + // Update the priority queues. + if (old_state == ThreadState::Runnable) { + // If we were previously runnable, then we're not runnable now, and we should remove. + GetPriorityQueue(kernel).Remove(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } else if (cur_state == ThreadState::Runnable) { + // If we're now runnable, then we weren't previously, and we should add. + GetPriorityQueue(kernel).PushBack(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // If the thread is runnable, we want to change its priority in the queue. + if (thread->GetRawState() == ThreadState::Runnable) { + GetPriorityQueue(kernel).ChangePriority( + old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, + const KAffinityMask& old_affinity, s32 old_core) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // If the thread is runnable, we want to change its affinity in the queue. + if (thread->GetRawState() == ThreadState::Runnable) { + GetPriorityQueue(kernel).ChangeAffinityMask(old_core, old_affinity, thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) { + ASSERT(system.GlobalSchedulerContext().IsLocked()); + + // Get a reference to the priority queue. + auto& kernel = system.Kernel(); + auto& priority_queue = GetPriorityQueue(kernel); + + // Rotate the front of the queue to the end. + KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority); + KThread* next_thread = nullptr; + if (top_thread != nullptr) { + next_thread = priority_queue.MoveToScheduledBack(top_thread); + if (next_thread != top_thread) { + IncrementScheduledCount(top_thread); + IncrementScheduledCount(next_thread); + } + } + + // While we have a suggested thread, try to migrate it! + { + KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority); + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If the next thread is a new thread that has been waiting longer than our + // suggestion, we prefer it to our suggestion. + if (top_thread != next_thread && next_thread != nullptr && + next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) { + suggested = nullptr; + break; + } + + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion + // to the front of the queue. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSamePriorityNext(core_id, suggested); + } + } + + // Now that we might have migrated a thread with the same priority, check if we can do better. + + { + KThread* best_thread = priority_queue.GetScheduledFront(core_id); + if (best_thread == GetCurrentThread()) { + best_thread = priority_queue.GetScheduledNext(core_id, best_thread); + } + + // If the best thread we can choose has a priority the same or worse than ours, try to + // migrate a higher priority thread. + if (best_thread != nullptr && best_thread->GetPriority() >= priority) { + KThread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // If the suggestion's priority is the same as ours, don't bother. + if (suggested->GetPriority() >= best_thread->GetPriority()) { + break; + } + + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the + // suggestion to the front of the queue. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + } + } + + // After a rotation, we need a scheduler update. + SetSchedulerUpdateNeeded(kernel); +} + +bool KScheduler::CanSchedule(KernelCore& kernel) { + return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1; +} + +bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire); +} + +void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release); +} + +void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release); +} + +void KScheduler::DisableScheduling(KernelCore& kernel) { + if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { + ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0); + scheduler->GetCurrentThread()->DisableDispatch(); + } +} + +void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { + if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { + ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1); + if (scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1) { + scheduler->GetCurrentThread()->EnableDispatch(); + } + } + RescheduleCores(kernel, cores_needing_scheduling); +} + +u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { + if (IsSchedulerUpdateNeeded(kernel)) { + return UpdateHighestPriorityThreadsImpl(kernel); + } else { + return 0; + } +} + +KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().priority_queue; +} + +void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + KThread& cur_thread = Kernel::GetCurrentThread(kernel); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == ThreadState::Runnable) { + // Put the current thread at the back of the queue. + KThread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // If the next thread is different, we have an update to perform. + if (next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else { + // Otherwise, set the thread's yield count so that we won't waste work until the + // process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } +} + +void KScheduler::YieldWithCoreMigration(KernelCore& kernel) { + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + KThread& cur_thread = Kernel::GetCurrentThread(kernel); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == ThreadState::Runnable) { + // Get the current active core. + const s32 core_id = cur_thread.GetActiveCore(); + + // Put the current thread at the back of the queue. + KThread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // While we have a suggested thread, try to migrate it! + bool recheck = false; + KThread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // Check if the suggested thread is the thread running on its core. + const s32 suggested_core = suggested->GetActiveCore(); + + if (KThread* running_on_suggested_core = + (suggested_core >= 0) + ? kernel.Scheduler(suggested_core).state.highest_priority_thread + : nullptr; + running_on_suggested_core != suggested) { + // If the current thread's priority is higher than our suggestion's we prefer + // the next thread to the suggestion. We also prefer the next thread when the + // current thread's priority is equal to the suggestions, but the next thread + // has been waiting longer. + if ((suggested->GetPriority() > cur_thread.GetPriority()) || + (suggested->GetPriority() == cur_thread.GetPriority() && + next_thread != std::addressof(cur_thread) && + next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) { + suggested = nullptr; + break; + } + + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the + // suggestion to the front of the queue. + if (running_on_suggested_core == nullptr || + running_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } else { + // We couldn't perform a migration, but we should check again on a future + // yield. + recheck = true; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If we still have a suggestion or the next thread is different, we have an update to + // perform. + if (suggested != nullptr || next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else if (!recheck) { + // Otherwise if we don't need to re-check, set the thread's yield count so that we + // won't waste work until the process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } +} + +void KScheduler::YieldToAnyThread(KernelCore& kernel) { + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + KThread& cur_thread = Kernel::GetCurrentThread(kernel); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == ThreadState::Runnable) { + // Get the current active core. + const s32 core_id = cur_thread.GetActiveCore(); + + // Migrate the current thread to core -1. + cur_thread.SetActiveCore(-1); + priority_queue.ChangeCore(core_id, std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // If there's nothing scheduled, we can try to perform a migration. + if (priority_queue.GetScheduledFront(core_id) == nullptr) { + // While we have a suggested thread, try to migrate it! + KThread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If we're allowed to do a migration, do one. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested); + IncrementScheduledCount(suggested); + } + + // Regardless of whether we migrated, we had a candidate, so we're done. + break; + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If the suggestion is different from the current thread, we need to perform an + // update. + if (suggested != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else { + // Otherwise, set the thread's yield count so that we won't waste work until the + // process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } else { + // Otherwise, we have an update to perform. + SetSchedulerUpdateNeeded(kernel); + } + } + } +} + +KScheduler::KScheduler(Core::System& system, s32 core_id) : system(system), core_id(core_id) { + switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this); + state.needs_scheduling.store(true); + state.interrupt_task_thread_runnable = false; + state.should_count_idle = false; + state.idle_count = 0; + state.idle_thread_stack = nullptr; + state.highest_priority_thread = nullptr; +} + +KScheduler::~KScheduler() = default; + +KThread* KScheduler::GetCurrentThread() const { + if (auto result = current_thread.load(); result) { + return result; + } + return idle_thread; +} + +u64 KScheduler::GetLastContextSwitchTicks() const { + return last_context_switch_time; +} + +void KScheduler::RescheduleCurrentCore() { + ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1); + + auto& phys_core = system.Kernel().PhysicalCore(core_id); + if (phys_core.IsInterrupted()) { + phys_core.ClearInterrupt(); + } + guard.lock(); + if (state.needs_scheduling.load()) { + Schedule(); + } else { + guard.unlock(); + } +} + +void KScheduler::OnThreadStart() { + SwitchContextStep2(); +} + +void KScheduler::Unload(KThread* thread) { + LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr"); + + if (thread) { + if (thread->IsCallingSvc()) { + system.ArmInterface(core_id).ExceptionalExit(); + thread->ClearIsCallingSvc(); + } + if (!thread->IsTerminationRequested()) { + prev_thread = thread; + + Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); + cpu_core.SaveContext(thread->GetContext32()); + cpu_core.SaveContext(thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + } else { + prev_thread = nullptr; + } + thread->context_guard.unlock(); + } +} + +void KScheduler::Reload(KThread* thread) { + LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread ? thread->GetName() : "nullptr"); + + if (thread) { + ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable."); + + auto* const thread_owner_process = thread->GetOwnerProcess(); + if (thread_owner_process != nullptr) { + system.Kernel().MakeCurrentProcess(thread_owner_process); + } + + Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); + cpu_core.LoadContext(thread->GetContext32()); + cpu_core.LoadContext(thread->GetContext64()); + cpu_core.SetTlsAddress(thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + } +} + +void KScheduler::SwitchContextStep2() { + // Load context of new thread + Reload(current_thread.load()); + + RescheduleCurrentCore(); +} + +void KScheduler::ScheduleImpl() { + KThread* previous_thread = current_thread.load(); + KThread* next_thread = state.highest_priority_thread; + + state.needs_scheduling = false; + + // We never want to schedule a null thread, so use the idle thread if we don't have a next. + if (next_thread == nullptr) { + next_thread = idle_thread; + } + + // If we're not actually switching thread, there's nothing to do. + if (next_thread == current_thread.load()) { + guard.unlock(); + return; + } + + current_thread.store(next_thread); + + Process* const previous_process = system.Kernel().CurrentProcess(); + + UpdateLastContextSwitchTime(previous_thread, previous_process); + + // Save context for previous thread + Unload(previous_thread); + + std::shared_ptr<Common::Fiber>* old_context; + if (previous_thread != nullptr) { + old_context = &previous_thread->GetHostContext(); + } else { + old_context = &idle_thread->GetHostContext(); + } + guard.unlock(); + + Common::Fiber::YieldTo(*old_context, switch_fiber); + /// When a thread wakes up, the scheduler may have changed to other in another core. + auto& next_scheduler = *system.Kernel().CurrentScheduler(); + next_scheduler.SwitchContextStep2(); +} + +void KScheduler::OnSwitch(void* this_scheduler) { + KScheduler* sched = static_cast<KScheduler*>(this_scheduler); + sched->SwitchToCurrent(); +} + +void KScheduler::SwitchToCurrent() { + while (true) { + { + std::scoped_lock lock{guard}; + current_thread.store(state.highest_priority_thread); + state.needs_scheduling.store(false); + } + const auto is_switch_pending = [this] { + std::scoped_lock lock{guard}; + return state.needs_scheduling.load(); + }; + do { + auto next_thread = current_thread.load(); + if (next_thread != nullptr) { + next_thread->context_guard.lock(); + if (next_thread->GetRawState() != ThreadState::Runnable) { + next_thread->context_guard.unlock(); + break; + } + if (next_thread->GetActiveCore() != core_id) { + next_thread->context_guard.unlock(); + break; + } + } + std::shared_ptr<Common::Fiber>* next_context; + if (next_thread != nullptr) { + next_context = &next_thread->GetHostContext(); + } else { + next_context = &idle_thread->GetHostContext(); + } + Common::Fiber::YieldTo(switch_fiber, *next_context); + } while (!is_switch_pending()); + } +} + +void KScheduler::UpdateLastContextSwitchTime(KThread* thread, Process* process) { + const u64 prev_switch_ticks = last_context_switch_time; + const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); + const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; + + if (thread != nullptr) { + thread->AddCpuTime(core_id, update_ticks); + } + + if (process != nullptr) { + process->UpdateCPUTimeTicks(update_ticks); + } + + last_context_switch_time = most_recent_switch_ticks; +} + +void KScheduler::Initialize() { + std::string name = "Idle Thread Id:" + std::to_string(core_id); + std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + auto thread_res = KThread::Create(system, ThreadType::Main, name, 0, + KThread::IdleThreadPriority, 0, static_cast<u32>(core_id), 0, + nullptr, std::move(init_func), init_func_parameter); + idle_thread = thread_res.Unwrap().get(); +} + +KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel) + : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {} + +KScopedSchedulerLock::~KScopedSchedulerLock() = default; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h new file mode 100644 index 000000000..f595b9a5c --- /dev/null +++ b/src/core/hle/kernel/k_scheduler.h @@ -0,0 +1,207 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include <atomic> + +#include "common/common_types.h" +#include "common/spin_lock.h" +#include "core/hle/kernel/global_scheduler_context.h" +#include "core/hle/kernel/k_priority_queue.h" +#include "core/hle/kernel/k_scheduler_lock.h" +#include "core/hle/kernel/k_scoped_lock.h" + +namespace Common { +class Fiber; +} + +namespace Core { +class System; +} + +namespace Kernel { + +class KernelCore; +class Process; +class SchedulerLock; +class KThread; + +class KScheduler final { +public: + explicit KScheduler(Core::System& system, s32 core_id); + ~KScheduler(); + + /// Reschedules to the next available thread (call after current thread is suspended) + void RescheduleCurrentCore(); + + /// Reschedules cores pending reschedule, to be called on EnableScheduling. + static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule); + + /// The next two are for SingleCore Only. + /// Unload current thread before preempting core. + void Unload(KThread* thread); + + /// Reload current thread after core preemption. + void Reload(KThread* thread); + + /// Gets the current running thread + [[nodiscard]] KThread* GetCurrentThread() const; + + /// Returns true if the scheduler is idle + [[nodiscard]] bool IsIdle() const { + return GetCurrentThread() == idle_thread; + } + + /// Gets the timestamp for the last context switch in ticks. + [[nodiscard]] u64 GetLastContextSwitchTicks() const; + + [[nodiscard]] bool ContextSwitchPending() const { + return state.needs_scheduling.load(std::memory_order_relaxed); + } + + void Initialize(); + + void OnThreadStart(); + + [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() { + return switch_fiber; + } + + [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const { + return switch_fiber; + } + + [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread); + + /** + * Takes a thread and moves it to the back of the it's priority list. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + static void YieldWithoutCoreMigration(KernelCore& kernel); + + /** + * Takes a thread and moves it to the back of the it's priority list. + * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or + * a better priority than the next thread in the core. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + static void YieldWithCoreMigration(KernelCore& kernel); + + /** + * Takes a thread and moves it out of the scheduling queue. + * and into the suggested queue. If no thread can be scheduled afterwards in that core, + * a suggested thread is obtained instead. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + static void YieldToAnyThread(KernelCore& kernel); + + static void ClearPreviousThread(KernelCore& kernel, KThread* thread); + + /// Notify the scheduler a thread's status has changed. + static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state); + + /// Notify the scheduler a thread's priority has changed. + static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority); + + /// Notify the scheduler a thread's core and/or affinity mask has changed. + static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, + const KAffinityMask& old_affinity, s32 old_core); + + static bool CanSchedule(KernelCore& kernel); + static bool IsSchedulerUpdateNeeded(const KernelCore& kernel); + static void SetSchedulerUpdateNeeded(KernelCore& kernel); + static void ClearSchedulerUpdateNeeded(KernelCore& kernel); + static void DisableScheduling(KernelCore& kernel); + static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); + [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel); + +private: + friend class GlobalSchedulerContext; + + /** + * Takes care of selecting the new scheduled threads in three steps: + * + * 1. First a thread is selected from the top of the priority queue. If no thread + * is obtained then we move to step two, else we are done. + * + * 2. Second we try to get a suggested thread that's not assigned to any core or + * that is not the top thread in that core. + * + * 3. Third is no suggested thread is found, we do a second pass and pick a running + * thread in another core and swap it with its current thread. + * + * returns the cores needing scheduling. + */ + [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); + + [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel); + + void RotateScheduledQueue(s32 core_id, s32 priority); + + void Schedule() { + ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1); + this->ScheduleImpl(); + } + + /// Switches the CPU's active thread context to that of the specified thread + void ScheduleImpl(); + + /// When a thread wakes up, it must run this through it's new scheduler + void SwitchContextStep2(); + + /** + * Called on every context switch to update the internal timestamp + * This also updates the running time ticks for the given thread and + * process using the following difference: + * + * ticks += most_recent_ticks - last_context_switch_ticks + * + * The internal tick timestamp for the scheduler is simply the + * most recent tick count retrieved. No special arithmetic is + * applied to it. + */ + void UpdateLastContextSwitchTime(KThread* thread, Process* process); + + static void OnSwitch(void* this_scheduler); + void SwitchToCurrent(); + + KThread* prev_thread{}; + std::atomic<KThread*> current_thread{}; + + KThread* idle_thread; + + std::shared_ptr<Common::Fiber> switch_fiber{}; + + struct SchedulingState { + std::atomic<bool> needs_scheduling; + bool interrupt_task_thread_runnable{}; + bool should_count_idle{}; + u64 idle_count{}; + KThread* highest_priority_thread{}; + void* idle_thread_stack{}; + }; + + SchedulingState state; + + Core::System& system; + u64 last_context_switch_time{}; + const s32 core_id; + + Common::SpinLock guard{}; +}; + +class KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> { +public: + explicit KScopedSchedulerLock(KernelCore& kernel); + ~KScopedSchedulerLock(); +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h new file mode 100644 index 000000000..169455d18 --- /dev/null +++ b/src/core/hle/kernel/k_scheduler_lock.h @@ -0,0 +1,75 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/assert.h" +#include "common/spin_lock.h" +#include "core/hardware_properties.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class KernelCore; + +template <typename SchedulerType> +class KAbstractSchedulerLock { +public: + explicit KAbstractSchedulerLock(KernelCore& kernel_) : kernel{kernel_} {} + + bool IsLockedByCurrentThread() const { + return owner_thread == GetCurrentThreadPointer(kernel); + } + + void Lock() { + if (IsLockedByCurrentThread()) { + // If we already own the lock, we can just increment the count. + ASSERT(lock_count > 0); + lock_count++; + } else { + // Otherwise, we want to disable scheduling and acquire the spinlock. + SchedulerType::DisableScheduling(kernel); + spin_lock.lock(); + + // For debug, ensure that our state is valid. + ASSERT(lock_count == 0); + ASSERT(owner_thread == nullptr); + + // Increment count, take ownership. + lock_count = 1; + owner_thread = GetCurrentThreadPointer(kernel); + } + } + + void Unlock() { + ASSERT(IsLockedByCurrentThread()); + ASSERT(lock_count > 0); + + // Release an instance of the lock. + if ((--lock_count) == 0) { + // We're no longer going to hold the lock. Take note of what cores need scheduling. + const u64 cores_needing_scheduling = + SchedulerType::UpdateHighestPriorityThreads(kernel); + + // Note that we no longer hold the lock, and unlock the spinlock. + owner_thread = nullptr; + spin_lock.unlock(); + + // Enable scheduling, and perform a rescheduling operation. + SchedulerType::EnableScheduling(kernel, cores_needing_scheduling); + } + } + +private: + KernelCore& kernel; + Common::SpinLock spin_lock{}; + s32 lock_count{}; + KThread* owner_thread{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h new file mode 100644 index 000000000..d7cc557b2 --- /dev/null +++ b/src/core/hle/kernel/k_scoped_lock.h @@ -0,0 +1,41 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" + +namespace Kernel { + +template <typename T> +concept KLockable = !std::is_reference_v<T> && requires(T & t) { + { t.Lock() } + ->std::same_as<void>; + { t.Unlock() } + ->std::same_as<void>; +}; + +template <typename T> +requires KLockable<T> class KScopedLock { +public: + explicit KScopedLock(T* l) : lock_ptr(l) { + this->lock_ptr->Lock(); + } + explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) { /* ... */ + } + ~KScopedLock() { + this->lock_ptr->Unlock(); + } + + KScopedLock(const KScopedLock&) = delete; + KScopedLock(KScopedLock&&) = delete; + +private: + T* lock_ptr; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scoped_resource_reservation.h b/src/core/hle/kernel/k_scoped_resource_reservation.h new file mode 100644 index 000000000..c5deca00b --- /dev/null +++ b/src/core/hle/kernel/k_scoped_resource_reservation.h @@ -0,0 +1,67 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/process.h" + +namespace Kernel { + +class KScopedResourceReservation { +public: + explicit KScopedResourceReservation(std::shared_ptr<KResourceLimit> l, LimitableResource r, + s64 v, s64 timeout) + : resource_limit(std::move(l)), value(v), resource(r) { + if (resource_limit && value) { + success = resource_limit->Reserve(resource, value, timeout); + } else { + success = true; + } + } + + explicit KScopedResourceReservation(std::shared_ptr<KResourceLimit> l, LimitableResource r, + s64 v = 1) + : resource_limit(std::move(l)), value(v), resource(r) { + if (resource_limit && value) { + success = resource_limit->Reserve(resource, value); + } else { + success = true; + } + } + + explicit KScopedResourceReservation(const Process* p, LimitableResource r, s64 v, s64 t) + : KScopedResourceReservation(p->GetResourceLimit(), r, v, t) {} + + explicit KScopedResourceReservation(const Process* p, LimitableResource r, s64 v = 1) + : KScopedResourceReservation(p->GetResourceLimit(), r, v) {} + + ~KScopedResourceReservation() noexcept { + if (resource_limit && value && success) { + // resource was not committed, release the reservation. + resource_limit->Release(resource, value); + } + } + + /// Commit the resource reservation, destruction of this object does not release the resource + void Commit() { + resource_limit = nullptr; + } + + [[nodiscard]] bool Succeeded() const { + return success; + } + +private: + std::shared_ptr<KResourceLimit> resource_limit; + s64 value; + LimitableResource resource; + bool success; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h new file mode 100644 index 000000000..f8189e107 --- /dev/null +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h @@ -0,0 +1,46 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { + +class KScopedSchedulerLockAndSleep { +public: + explicit KScopedSchedulerLockAndSleep(KernelCore& kernel, KThread* t, s64 timeout) + : kernel(kernel), thread(t), timeout_tick(timeout) { + // Lock the scheduler. + kernel.GlobalSchedulerContext().scheduler_lock.Lock(); + } + + ~KScopedSchedulerLockAndSleep() { + // Register the sleep. + if (timeout_tick > 0) { + kernel.TimeManager().ScheduleTimeEvent(thread, timeout_tick); + } + + // Unlock the scheduler. + kernel.GlobalSchedulerContext().scheduler_lock.Unlock(); + } + + void CancelSleep() { + timeout_tick = 0; + } + +private: + KernelCore& kernel; + KThread* thread{}; + s64 timeout_tick{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_synchronization_object.cpp b/src/core/hle/kernel/k_synchronization_object.cpp new file mode 100644 index 000000000..82f72a0fe --- /dev/null +++ b/src/core/hle/kernel/k_synchronization_object.cpp @@ -0,0 +1,171 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { + +ResultCode KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index, + KSynchronizationObject** objects, const s32 num_objects, + s64 timeout) { + // Allocate space on stack for thread nodes. + std::vector<ThreadListNode> thread_nodes(num_objects); + + // Prepare for wait. + KThread* thread = kernel.CurrentScheduler()->GetCurrentThread(); + + { + // Setup the scheduling lock and sleep. + KScopedSchedulerLockAndSleep slp{kernel, thread, timeout}; + + // Check if any of the objects are already signaled. + for (auto i = 0; i < num_objects; ++i) { + ASSERT(objects[i] != nullptr); + + if (objects[i]->IsSignaled()) { + *out_index = i; + slp.CancelSleep(); + return RESULT_SUCCESS; + } + } + + // Check if the timeout is zero. + if (timeout == 0) { + slp.CancelSleep(); + return ResultTimedOut; + } + + // Check if the thread should terminate. + if (thread->IsTerminationRequested()) { + slp.CancelSleep(); + return ResultTerminationRequested; + } + + // Check if waiting was canceled. + if (thread->IsWaitCancelled()) { + slp.CancelSleep(); + thread->ClearWaitCancelled(); + return ResultCancelled; + } + + // Add the waiters. + for (auto i = 0; i < num_objects; ++i) { + thread_nodes[i].thread = thread; + thread_nodes[i].next = nullptr; + + if (objects[i]->thread_list_tail == nullptr) { + objects[i]->thread_list_head = std::addressof(thread_nodes[i]); + } else { + objects[i]->thread_list_tail->next = std::addressof(thread_nodes[i]); + } + + objects[i]->thread_list_tail = std::addressof(thread_nodes[i]); + } + + // For debugging only + thread->SetWaitObjectsForDebugging({objects, static_cast<std::size_t>(num_objects)}); + + // Mark the thread as waiting. + thread->SetCancellable(); + thread->SetSyncedObject(nullptr, ResultTimedOut); + thread->SetState(ThreadState::Waiting); + thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization); + } + + // The lock/sleep is done, so we should be able to get our result. + + // Thread is no longer cancellable. + thread->ClearCancellable(); + + // For debugging only + thread->SetWaitObjectsForDebugging({}); + + // Cancel the timer as needed. + kernel.TimeManager().UnscheduleTimeEvent(thread); + + // Get the wait result. + ResultCode wait_result{RESULT_SUCCESS}; + s32 sync_index = -1; + { + KScopedSchedulerLock lock(kernel); + KSynchronizationObject* synced_obj; + wait_result = thread->GetWaitResult(std::addressof(synced_obj)); + + for (auto i = 0; i < num_objects; ++i) { + // Unlink the object from the list. + ThreadListNode* prev_ptr = + reinterpret_cast<ThreadListNode*>(std::addressof(objects[i]->thread_list_head)); + ThreadListNode* prev_val = nullptr; + ThreadListNode *prev, *tail_prev; + + do { + prev = prev_ptr; + prev_ptr = prev_ptr->next; + tail_prev = prev_val; + prev_val = prev_ptr; + } while (prev_ptr != std::addressof(thread_nodes[i])); + + if (objects[i]->thread_list_tail == std::addressof(thread_nodes[i])) { + objects[i]->thread_list_tail = tail_prev; + } + + prev->next = thread_nodes[i].next; + + if (objects[i] == synced_obj) { + sync_index = i; + } + } + } + + // Set output. + *out_index = sync_index; + return wait_result; +} + +KSynchronizationObject::KSynchronizationObject(KernelCore& kernel) : Object{kernel} {} + +KSynchronizationObject::KSynchronizationObject(KernelCore& kernel, std::string&& name) + : Object{kernel, std::move(name)} {} + +KSynchronizationObject::~KSynchronizationObject() = default; + +void KSynchronizationObject::NotifyAvailable(ResultCode result) { + KScopedSchedulerLock lock(kernel); + + // If we're not signaled, we've nothing to notify. + if (!this->IsSignaled()) { + return; + } + + // Iterate over each thread. + for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { + KThread* thread = cur_node->thread; + if (thread->GetState() == ThreadState::Waiting) { + thread->SetSyncedObject(this, result); + thread->SetState(ThreadState::Runnable); + } + } +} + +std::vector<KThread*> KSynchronizationObject::GetWaitingThreadsForDebugging() const { + std::vector<KThread*> threads; + + // If debugging, dump the list of waiters. + { + KScopedSchedulerLock lock(kernel); + for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { + threads.emplace_back(cur_node->thread); + } + } + + return threads; +} +} // namespace Kernel diff --git a/src/core/hle/kernel/k_synchronization_object.h b/src/core/hle/kernel/k_synchronization_object.h new file mode 100644 index 000000000..5803718fd --- /dev/null +++ b/src/core/hle/kernel/k_synchronization_object.h @@ -0,0 +1,59 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "core/hle/kernel/object.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KernelCore; +class Synchronization; +class KThread; + +/// Class that represents a Kernel object that a thread can be waiting on +class KSynchronizationObject : public Object { +public: + struct ThreadListNode { + ThreadListNode* next{}; + KThread* thread{}; + }; + + [[nodiscard]] static ResultCode Wait(KernelCore& kernel, s32* out_index, + KSynchronizationObject** objects, const s32 num_objects, + s64 timeout); + + [[nodiscard]] virtual bool IsSignaled() const = 0; + + [[nodiscard]] std::vector<KThread*> GetWaitingThreadsForDebugging() const; + +protected: + explicit KSynchronizationObject(KernelCore& kernel); + explicit KSynchronizationObject(KernelCore& kernel, std::string&& name); + virtual ~KSynchronizationObject(); + + void NotifyAvailable(ResultCode result); + void NotifyAvailable() { + return this->NotifyAvailable(RESULT_SUCCESS); + } + +private: + ThreadListNode* thread_list_head{}; + ThreadListNode* thread_list_tail{}; +}; + +// Specialization of DynamicObjectCast for KSynchronizationObjects +template <> +inline std::shared_ptr<KSynchronizationObject> DynamicObjectCast<KSynchronizationObject>( + std::shared_ptr<Object> object) { + if (object != nullptr && object->IsWaitable()) { + return std::static_pointer_cast<KSynchronizationObject>(object); + } + return nullptr; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp new file mode 100644 index 000000000..e5620da5a --- /dev/null +++ b/src/core/hle/kernel/k_thread.cpp @@ -0,0 +1,1048 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cinttypes> +#include <optional> +#include <vector> + +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/fiber.h" +#include "common/logging/log.h" +#include "common/scope_exit.h" +#include "common/thread_queue_list.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/hardware_properties.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_condition_variable.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_thread_queue.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/memory/memory_layout.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/time_manager.h" +#include "core/hle/result.h" +#include "core/memory.h" + +#ifdef ARCHITECTURE_x86_64 +#include "core/arm/dynarmic/arm_dynarmic_32.h" +#include "core/arm/dynarmic/arm_dynarmic_64.h" +#endif + +namespace { +static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top, + u32 entry_point, u32 arg) { + context = {}; + context.cpu_registers[0] = arg; + context.cpu_registers[15] = entry_point; + context.cpu_registers[13] = stack_top; +} + +static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context, VAddr stack_top, + VAddr entry_point, u64 arg) { + context = {}; + context.cpu_registers[0] = arg; + context.pc = entry_point; + context.sp = stack_top; + // TODO(merry): Perform a hardware test to determine the below value. + context.fpcr = 0; +} +} // namespace + +namespace Kernel { + +KThread::KThread(KernelCore& kernel) + : KSynchronizationObject{kernel}, activity_pause_lock{kernel} {} +KThread::~KThread() = default; + +ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top, s32 prio, + s32 virt_core, Process* owner, ThreadType type) { + // Assert parameters are valid. + ASSERT((type == ThreadType::Main) || + (Svc::HighestThreadPriority <= prio && prio <= Svc::LowestThreadPriority)); + ASSERT((owner != nullptr) || (type != ThreadType::User)); + ASSERT(0 <= virt_core && virt_core < static_cast<s32>(Common::BitSize<u64>())); + + // Convert the virtual core to a physical core. + const s32 phys_core = Core::Hardware::VirtualToPhysicalCoreMap[virt_core]; + ASSERT(0 <= phys_core && phys_core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + + // First, clear the TLS address. + tls_address = {}; + + // Next, assert things based on the type. + switch (type) { + case ThreadType::Main: + ASSERT(arg == 0); + [[fallthrough]]; + case ThreadType::HighPriority: + [[fallthrough]]; + case ThreadType::User: + ASSERT(((owner == nullptr) || + (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); + ASSERT(((owner == nullptr) || + (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); + break; + case ThreadType::Kernel: + UNIMPLEMENTED(); + break; + default: + UNREACHABLE_MSG("KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type)); + break; + } + thread_type_for_debugging = type; + + // Set the ideal core ID and affinity mask. + virtual_ideal_core_id = virt_core; + physical_ideal_core_id = phys_core; + virtual_affinity_mask = 1ULL << virt_core; + physical_affinity_mask.SetAffinity(phys_core, true); + + // Set the thread state. + thread_state = (type == ThreadType::Main) ? ThreadState::Runnable : ThreadState::Initialized; + + // Set TLS address. + tls_address = 0; + + // Set parent and condvar tree. + parent = nullptr; + condvar_tree = nullptr; + + // Set sync booleans. + signaled = false; + termination_requested = false; + wait_cancelled = false; + cancellable = false; + + // Set core ID and wait result. + core_id = phys_core; + wait_result = ResultNoSynchronizationObject; + + // Set priorities. + priority = prio; + base_priority = prio; + + // Set sync object and waiting lock to null. + synced_object = nullptr; + + // Initialize sleeping queue. + sleeping_queue = nullptr; + + // Set suspend flags. + suspend_request_flags = 0; + suspend_allowed_flags = static_cast<u32>(ThreadState::SuspendFlagMask); + + // We're neither debug attached, nor are we nesting our priority inheritance. + debug_attached = false; + priority_inheritance_count = 0; + + // We haven't been scheduled, and we have done no light IPC. + schedule_count = -1; + last_scheduled_tick = 0; + light_ipc_data = nullptr; + + // We're not waiting for a lock, and we haven't disabled migration. + lock_owner = nullptr; + num_core_migration_disables = 0; + + // We have no waiters, but we do have an entrypoint. + num_kernel_waiters = 0; + + // Set our current core id. + current_core_id = phys_core; + + // We haven't released our resource limit hint, and we've spent no time on the cpu. + resource_limit_release_hint = false; + cpu_time = 0; + + // Clear our stack parameters. + std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0, + sizeof(StackParameters)); + + // Setup the TLS, if needed. + if (type == ThreadType::User) { + tls_address = owner->CreateTLSRegion(); + } + + // Set parent, if relevant. + if (owner != nullptr) { + parent = owner; + parent->IncrementThreadCount(); + } + + // Initialize thread context. + ResetThreadContext64(thread_context_64, user_stack_top, func, arg); + ResetThreadContext32(thread_context_32, static_cast<u32>(user_stack_top), + static_cast<u32>(func), static_cast<u32>(arg)); + + // Setup the stack parameters. + StackParameters& sp = GetStackParameters(); + sp.cur_thread = this; + sp.disable_count = 1; + SetInExceptionHandler(); + + // Set thread ID. + thread_id = kernel.CreateNewThreadID(); + + // We initialized! + initialized = true; + + // Register ourselves with our parent process. + if (parent != nullptr) { + parent->RegisterThread(this); + if (parent->IsSuspended()) { + RequestSuspend(SuspendType::Process); + } + } + + return RESULT_SUCCESS; +} + +ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, + VAddr user_stack_top, s32 prio, s32 core, Process* owner, + ThreadType type) { + // Initialize the thread. + R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type)); + + return RESULT_SUCCESS; +} + +void KThread::Finalize() { + // If the thread has an owner process, unregister it. + if (parent != nullptr) { + parent->UnregisterThread(this); + } + + // If the thread has a local region, delete it. + if (tls_address != 0) { + parent->FreeTLSRegion(tls_address); + } + + // Release any waiters. + { + ASSERT(lock_owner == nullptr); + KScopedSchedulerLock sl{kernel}; + + auto it = waiter_list.begin(); + while (it != waiter_list.end()) { + // The thread shouldn't be a kernel waiter. + it->SetLockOwner(nullptr); + it->SetSyncedObject(nullptr, ResultInvalidState); + it->Wakeup(); + it = waiter_list.erase(it); + } + } + + // Decrement the parent process's thread count. + if (parent != nullptr) { + parent->DecrementThreadCount(); + parent->GetResourceLimit()->Release(LimitableResource::Threads, 1); + } +} + +bool KThread::IsSignaled() const { + return signaled; +} + +void KThread::Wakeup() { + KScopedSchedulerLock sl{kernel}; + + if (GetState() == ThreadState::Waiting) { + if (sleeping_queue != nullptr) { + sleeping_queue->WakeupThread(this); + } else { + SetState(ThreadState::Runnable); + } + } +} + +void KThread::StartTermination() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Release user exception and unpin, if relevant. + if (parent != nullptr) { + parent->ReleaseUserException(this); + if (parent->GetPinnedThread(GetCurrentCoreId(kernel)) == this) { + parent->UnpinCurrentThread(); + } + } + + // Set state to terminated. + SetState(ThreadState::Terminated); + + // Clear the thread's status as running in parent. + if (parent != nullptr) { + parent->ClearRunningThread(this); + } + + // Signal. + signaled = true; + NotifyAvailable(); + + // Clear previous thread in KScheduler. + KScheduler::ClearPreviousThread(kernel, this); + + // Register terminated dpc flag. + RegisterDpc(DpcFlag::Terminated); +} + +void KThread::Pin() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Set ourselves as pinned. + GetStackParameters().is_pinned = true; + + // Disable core migration. + ASSERT(num_core_migration_disables == 0); + { + ++num_core_migration_disables; + + // Save our ideal state to restore when we're unpinned. + original_physical_ideal_core_id = physical_ideal_core_id; + original_physical_affinity_mask = physical_affinity_mask; + + // Bind ourselves to this core. + const s32 active_core = GetActiveCore(); + const s32 current_core = GetCurrentCoreId(kernel); + + SetActiveCore(current_core); + physical_ideal_core_id = current_core; + physical_affinity_mask.SetAffinityMask(1ULL << current_core); + + if (active_core != current_core || physical_affinity_mask.GetAffinityMask() != + original_physical_affinity_mask.GetAffinityMask()) { + KScheduler::OnThreadAffinityMaskChanged(kernel, this, original_physical_affinity_mask, + active_core); + } + } + + // Disallow performing thread suspension. + { + // Update our allow flags. + suspend_allowed_flags &= ~(1 << (static_cast<u32>(SuspendType::Thread) + + static_cast<u32>(ThreadState::SuspendShift))); + + // Update our state. + const ThreadState old_state = thread_state; + thread_state = static_cast<ThreadState>(GetSuspendFlags() | + static_cast<u32>(old_state & ThreadState::Mask)); + if (thread_state != old_state) { + KScheduler::OnThreadStateChanged(kernel, this, old_state); + } + } + + // TODO(bunnei): Update our SVC access permissions. + ASSERT(parent != nullptr); +} + +void KThread::Unpin() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Set ourselves as unpinned. + GetStackParameters().is_pinned = false; + + // Enable core migration. + ASSERT(num_core_migration_disables == 1); + { + num_core_migration_disables--; + + // Restore our original state. + const KAffinityMask old_mask = physical_affinity_mask; + + physical_ideal_core_id = original_physical_ideal_core_id; + physical_affinity_mask = original_physical_affinity_mask; + + if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = GetActiveCore(); + + if (!physical_affinity_mask.GetAffinity(active_core)) { + if (physical_ideal_core_id >= 0) { + SetActiveCore(physical_ideal_core_id); + } else { + SetActiveCore(static_cast<s32>( + Common::BitSize<u64>() - 1 - + std::countl_zero(physical_affinity_mask.GetAffinityMask()))); + } + } + KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core); + } + } + + // Allow performing thread suspension (if termination hasn't been requested). + { + // Update our allow flags. + if (!IsTerminationRequested()) { + suspend_allowed_flags |= (1 << (static_cast<u32>(SuspendType::Thread) + + static_cast<u32>(ThreadState::SuspendShift))); + } + + // Update our state. + const ThreadState old_state = thread_state; + thread_state = static_cast<ThreadState>(GetSuspendFlags() | + static_cast<u32>(old_state & ThreadState::Mask)); + if (thread_state != old_state) { + KScheduler::OnThreadStateChanged(kernel, this, old_state); + } + } + + // TODO(bunnei): Update our SVC access permissions. + ASSERT(parent != nullptr); + + // Resume any threads that began waiting on us while we were pinned. + for (auto it = pinned_waiter_list.begin(); it != pinned_waiter_list.end(); ++it) { + if (it->GetState() == ThreadState::Waiting) { + it->SetState(ThreadState::Runnable); + } + } +} + +ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{kernel}; + + // Get the virtual mask. + *out_ideal_core = virtual_ideal_core_id; + *out_affinity_mask = virtual_affinity_mask; + + return RESULT_SUCCESS; +} + +ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{kernel}; + ASSERT(num_core_migration_disables >= 0); + + // Select between core mask and original core mask. + if (num_core_migration_disables == 0) { + *out_ideal_core = physical_ideal_core_id; + *out_affinity_mask = physical_affinity_mask.GetAffinityMask(); + } else { + *out_ideal_core = original_physical_ideal_core_id; + *out_affinity_mask = original_physical_affinity_mask.GetAffinityMask(); + } + + return RESULT_SUCCESS; +} + +ResultCode KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) { + ASSERT(parent != nullptr); + ASSERT(v_affinity_mask != 0); + KScopedLightLock lk{activity_pause_lock}; + + // Set the core mask. + u64 p_affinity_mask = 0; + { + KScopedSchedulerLock sl{kernel}; + ASSERT(num_core_migration_disables >= 0); + + // If the core id is no-update magic, preserve the ideal core id. + if (core_id == Svc::IdealCoreNoUpdate) { + core_id = virtual_ideal_core_id; + R_UNLESS(((1ULL << core_id) & v_affinity_mask) != 0, ResultInvalidCombination); + } + + // Set the virtual core/affinity mask. + virtual_ideal_core_id = core_id; + virtual_affinity_mask = v_affinity_mask; + + // Translate the virtual core to a physical core. + if (core_id >= 0) { + core_id = Core::Hardware::VirtualToPhysicalCoreMap[core_id]; + } + + // Translate the virtual affinity mask to a physical one. + while (v_affinity_mask != 0) { + const u64 next = std::countr_zero(v_affinity_mask); + v_affinity_mask &= ~(1ULL << next); + p_affinity_mask |= (1ULL << Core::Hardware::VirtualToPhysicalCoreMap[next]); + } + + // If we haven't disabled migration, perform an affinity change. + if (num_core_migration_disables == 0) { + const KAffinityMask old_mask = physical_affinity_mask; + + // Set our new ideals. + physical_ideal_core_id = core_id; + physical_affinity_mask.SetAffinityMask(p_affinity_mask); + + if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = GetActiveCore(); + + if (active_core >= 0 && !physical_affinity_mask.GetAffinity(active_core)) { + const s32 new_core = static_cast<s32>( + physical_ideal_core_id >= 0 + ? physical_ideal_core_id + : Common::BitSize<u64>() - 1 - + std::countl_zero(physical_affinity_mask.GetAffinityMask())); + SetActiveCore(new_core); + } + KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core); + } + } else { + // Otherwise, we edit the original affinity for restoration later. + original_physical_ideal_core_id = core_id; + original_physical_affinity_mask.SetAffinityMask(p_affinity_mask); + } + } + + // Update the pinned waiter list. + { + bool retry_update{}; + bool thread_is_pinned{}; + do { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Don't do any further management if our termination has been requested. + R_SUCCEED_IF(IsTerminationRequested()); + + // By default, we won't need to retry. + retry_update = false; + + // Check if the thread is currently running. + bool thread_is_current{}; + s32 thread_core; + for (thread_core = 0; thread_core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); + ++thread_core) { + if (kernel.Scheduler(thread_core).GetCurrentThread() == this) { + thread_is_current = true; + break; + } + } + + // If the thread is currently running, check whether it's no longer allowed under the + // new mask. + if (thread_is_current && ((1ULL << thread_core) & p_affinity_mask) == 0) { + // If the thread is pinned, we want to wait until it's not pinned. + if (GetStackParameters().is_pinned) { + // Verify that the current thread isn't terminating. + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), + ResultTerminationRequested); + + // Note that the thread was pinned. + thread_is_pinned = true; + + // Wait until the thread isn't pinned any more. + pinned_waiter_list.push_back(GetCurrentThread(kernel)); + GetCurrentThread(kernel).SetState(ThreadState::Waiting); + } else { + // If the thread isn't pinned, release the scheduler lock and retry until it's + // not current. + retry_update = true; + } + } + } while (retry_update); + + // If the thread was pinned, it no longer is, and we should remove the current thread from + // our waiter list. + if (thread_is_pinned) { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Remove from the list. + pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel))); + } + } + + return RESULT_SUCCESS; +} + +void KThread::SetBasePriority(s32 value) { + ASSERT(Svc::HighestThreadPriority <= value && value <= Svc::LowestThreadPriority); + + KScopedSchedulerLock sl{kernel}; + + // Change our base priority. + base_priority = value; + + // Perform a priority restoration. + RestorePriority(kernel, this); +} + +void KThread::RequestSuspend(SuspendType type) { + KScopedSchedulerLock sl{kernel}; + + // Note the request in our flags. + suspend_request_flags |= + (1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type))); + + // Try to perform the suspend. + TrySuspend(); +} + +void KThread::Resume(SuspendType type) { + KScopedSchedulerLock sl{kernel}; + + // Clear the request in our flags. + suspend_request_flags &= + ~(1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type))); + + // Update our state. + const ThreadState old_state = thread_state; + thread_state = static_cast<ThreadState>(GetSuspendFlags() | + static_cast<u32>(old_state & ThreadState::Mask)); + if (thread_state != old_state) { + KScheduler::OnThreadStateChanged(kernel, this, old_state); + } +} + +void KThread::WaitCancel() { + KScopedSchedulerLock sl{kernel}; + + // Check if we're waiting and cancellable. + if (GetState() == ThreadState::Waiting && cancellable) { + if (sleeping_queue != nullptr) { + sleeping_queue->WakeupThread(this); + wait_cancelled = true; + } else { + SetSyncedObject(nullptr, ResultCancelled); + SetState(ThreadState::Runnable); + wait_cancelled = false; + } + } else { + // Otherwise, note that we cancelled a wait. + wait_cancelled = true; + } +} + +void KThread::TrySuspend() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSuspendRequested()); + + // Ensure that we have no waiters. + if (GetNumKernelWaiters() > 0) { + return; + } + ASSERT(GetNumKernelWaiters() == 0); + + // Perform the suspend. + Suspend(); +} + +void KThread::Suspend() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(IsSuspendRequested()); + + // Set our suspend flags in state. + const auto old_state = thread_state; + thread_state = static_cast<ThreadState>(GetSuspendFlags()) | (old_state & ThreadState::Mask); + + // Note the state change in scheduler. + KScheduler::OnThreadStateChanged(kernel, this, old_state); +} + +void KThread::Continue() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Clear our suspend flags in state. + const auto old_state = thread_state; + thread_state = old_state & ThreadState::Mask; + + // Note the state change in scheduler. + KScheduler::OnThreadStateChanged(kernel, this, old_state); +} + +ResultCode KThread::SetActivity(Svc::ThreadActivity activity) { + // Lock ourselves. + KScopedLightLock lk(activity_pause_lock); + + // Set the activity. + { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Verify our state. + const auto cur_state = GetState(); + R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable), + ResultInvalidState); + + // Either pause or resume. + if (activity == Svc::ThreadActivity::Paused) { + // Verify that we're not suspended. + R_UNLESS(!IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + + // Suspend. + RequestSuspend(SuspendType::Thread); + } else { + ASSERT(activity == Svc::ThreadActivity::Runnable); + + // Verify that we're suspended. + R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + + // Resume. + Resume(SuspendType::Thread); + } + } + + // If the thread is now paused, update the pinned waiter list. + if (activity == Svc::ThreadActivity::Paused) { + bool thread_is_pinned{}; + bool thread_is_current{}; + do { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Don't do any further management if our termination has been requested. + R_SUCCEED_IF(IsTerminationRequested()); + + // Check whether the thread is pinned. + if (GetStackParameters().is_pinned) { + // Verify that the current thread isn't terminating. + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), + ResultTerminationRequested); + + // Note that the thread was pinned and not current. + thread_is_pinned = true; + thread_is_current = false; + + // Wait until the thread isn't pinned any more. + pinned_waiter_list.push_back(GetCurrentThread(kernel)); + GetCurrentThread(kernel).SetState(ThreadState::Waiting); + } else { + // Check if the thread is currently running. + // If it is, we'll need to retry. + thread_is_current = false; + + for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { + if (kernel.Scheduler(i).GetCurrentThread() == this) { + thread_is_current = true; + break; + } + } + } + } while (thread_is_current); + + // If the thread was pinned, it no longer is, and we should remove the current thread from + // our waiter list. + if (thread_is_pinned) { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Remove from the list. + pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel))); + } + } + + return RESULT_SUCCESS; +} + +ResultCode KThread::GetThreadContext3(std::vector<u8>& out) { + // Lock ourselves. + KScopedLightLock lk{activity_pause_lock}; + + // Get the context. + { + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // Verify that we're suspended. + R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState); + + // If we're not terminating, get the thread's user context. + if (!IsTerminationRequested()) { + if (parent->Is64BitProcess()) { + // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. + auto context = GetContext64(); + context.pstate &= 0xFF0FFE20; + + out.resize(sizeof(context)); + std::memcpy(out.data(), &context, sizeof(context)); + } else { + // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. + auto context = GetContext32(); + context.cpsr &= 0xFF0FFE20; + + out.resize(sizeof(context)); + std::memcpy(out.data(), &context, sizeof(context)); + } + } + } + + return RESULT_SUCCESS; +} + +void KThread::AddWaiterImpl(KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Find the right spot to insert the waiter. + auto it = waiter_list.begin(); + while (it != waiter_list.end()) { + if (it->GetPriority() > thread->GetPriority()) { + break; + } + it++; + } + + // Keep track of how many kernel waiters we have. + if (Memory::IsKernelAddressKey(thread->GetAddressKey())) { + ASSERT((num_kernel_waiters++) >= 0); + } + + // Insert the waiter. + waiter_list.insert(it, *thread); + thread->SetLockOwner(this); +} + +void KThread::RemoveWaiterImpl(KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Keep track of how many kernel waiters we have. + if (Memory::IsKernelAddressKey(thread->GetAddressKey())) { + ASSERT((num_kernel_waiters--) > 0); + } + + // Remove the waiter. + waiter_list.erase(waiter_list.iterator_to(*thread)); + thread->SetLockOwner(nullptr); +} + +void KThread::RestorePriority(KernelCore& kernel, KThread* thread) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + while (true) { + // We want to inherit priority where possible. + s32 new_priority = thread->GetBasePriority(); + if (thread->HasWaiters()) { + new_priority = std::min(new_priority, thread->waiter_list.front().GetPriority()); + } + + // If the priority we would inherit is not different from ours, don't do anything. + if (new_priority == thread->GetPriority()) { + return; + } + + // Ensure we don't violate condition variable red black tree invariants. + if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + BeforeUpdatePriority(kernel, cv_tree, thread); + } + + // Change the priority. + const s32 old_priority = thread->GetPriority(); + thread->SetPriority(new_priority); + + // Restore the condition variable, if relevant. + if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + AfterUpdatePriority(kernel, cv_tree, thread); + } + + // Update the scheduler. + KScheduler::OnThreadPriorityChanged(kernel, thread, old_priority); + + // Keep the lock owner up to date. + KThread* lock_owner = thread->GetLockOwner(); + if (lock_owner == nullptr) { + return; + } + + // Update the thread in the lock owner's sorted list, and continue inheriting. + lock_owner->RemoveWaiterImpl(thread); + lock_owner->AddWaiterImpl(thread); + thread = lock_owner; + } +} + +void KThread::AddWaiter(KThread* thread) { + AddWaiterImpl(thread); + RestorePriority(kernel, this); +} + +void KThread::RemoveWaiter(KThread* thread) { + RemoveWaiterImpl(thread); + RestorePriority(kernel, this); +} + +KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + s32 num_waiters{}; + KThread* next_lock_owner{}; + auto it = waiter_list.begin(); + while (it != waiter_list.end()) { + if (it->GetAddressKey() == key) { + KThread* thread = std::addressof(*it); + + // Keep track of how many kernel waiters we have. + if (Memory::IsKernelAddressKey(thread->GetAddressKey())) { + ASSERT((num_kernel_waiters--) > 0); + } + it = waiter_list.erase(it); + + // Update the next lock owner. + if (next_lock_owner == nullptr) { + next_lock_owner = thread; + next_lock_owner->SetLockOwner(nullptr); + } else { + next_lock_owner->AddWaiterImpl(thread); + } + num_waiters++; + } else { + it++; + } + } + + // Do priority updates, if we have a next owner. + if (next_lock_owner) { + RestorePriority(kernel, this); + RestorePriority(kernel, next_lock_owner); + } + + // Return output. + *out_num_waiters = num_waiters; + return next_lock_owner; +} + +ResultCode KThread::Run() { + while (true) { + KScopedSchedulerLock lk{kernel}; + + // If either this thread or the current thread are requesting termination, note it. + R_UNLESS(!IsTerminationRequested(), ResultTerminationRequested); + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); + + // Ensure our thread state is correct. + R_UNLESS(GetState() == ThreadState::Initialized, ResultInvalidState); + + // If the current thread has been asked to suspend, suspend it and retry. + if (GetCurrentThread(kernel).IsSuspended()) { + GetCurrentThread(kernel).Suspend(); + continue; + } + + // If we're not a kernel thread and we've been asked to suspend, suspend ourselves. + if (IsUserThread() && IsSuspended()) { + Suspend(); + } + + // Set our state and finish. + SetState(ThreadState::Runnable); + return RESULT_SUCCESS; + } +} + +void KThread::Exit() { + ASSERT(this == GetCurrentThreadPointer(kernel)); + + // Release the thread resource hint from parent. + if (parent != nullptr) { + // TODO(bunnei): Hint that the resource is about to be released. + resource_limit_release_hint = true; + } + + // Perform termination. + { + KScopedSchedulerLock sl{kernel}; + + // Disallow all suspension. + suspend_allowed_flags = 0; + + // Start termination. + StartTermination(); + } +} + +ResultCode KThread::Sleep(s64 timeout) { + ASSERT(!kernel.GlobalSchedulerContext().IsLocked()); + ASSERT(this == GetCurrentThreadPointer(kernel)); + ASSERT(timeout > 0); + + { + // Setup the scheduling lock and sleep. + KScopedSchedulerLockAndSleep slp{kernel, this, timeout}; + + // Check if the thread should terminate. + if (IsTerminationRequested()) { + slp.CancelSleep(); + return ResultTerminationRequested; + } + + // Mark the thread as waiting. + SetState(ThreadState::Waiting); + SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep); + } + + // The lock/sleep is done. + + // Cancel the timer. + kernel.TimeManager().UnscheduleTimeEvent(this); + + return RESULT_SUCCESS; +} + +void KThread::SetState(ThreadState state) { + KScopedSchedulerLock sl{kernel}; + + // Clear debugging state + SetMutexWaitAddressForDebugging({}); + SetWaitReasonForDebugging({}); + + const ThreadState old_state = thread_state; + thread_state = + static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)); + if (thread_state != old_state) { + KScheduler::OnThreadStateChanged(kernel, this, old_state); + } +} + +std::shared_ptr<Common::Fiber>& KThread::GetHostContext() { + return host_context; +} + +ResultVal<std::shared_ptr<KThread>> KThread::Create(Core::System& system, ThreadType type_flags, + std::string name, VAddr entry_point, + u32 priority, u64 arg, s32 processor_id, + VAddr stack_top, Process* owner_process) { + std::function<void(void*)> init_func = Core::CpuManager::GetGuestThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top, + owner_process, std::move(init_func), init_func_parameter); +} + +ResultVal<std::shared_ptr<KThread>> KThread::Create(Core::System& system, ThreadType type_flags, + std::string name, VAddr entry_point, + u32 priority, u64 arg, s32 processor_id, + VAddr stack_top, Process* owner_process, + std::function<void(void*)>&& thread_start_func, + void* thread_start_parameter) { + auto& kernel = system.Kernel(); + + std::shared_ptr<KThread> thread = std::make_shared<KThread>(kernel); + + if (const auto result = + thread->InitializeThread(thread.get(), entry_point, arg, stack_top, priority, + processor_id, owner_process, type_flags); + result.IsError()) { + return result; + } + + thread->name = name; + + auto& scheduler = kernel.GlobalSchedulerContext(); + scheduler.AddThread(thread); + + thread->host_context = + std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter); + + return MakeResult<std::shared_ptr<KThread>>(std::move(thread)); +} + +KThread* GetCurrentThreadPointer(KernelCore& kernel) { + return kernel.GetCurrentEmuThread(); +} + +KThread& GetCurrentThread(KernelCore& kernel) { + return *GetCurrentThreadPointer(kernel); +} + +s32 GetCurrentCoreId(KernelCore& kernel) { + return GetCurrentThread(kernel).GetCurrentCore(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h new file mode 100644 index 000000000..c8ac656a4 --- /dev/null +++ b/src/core/hle/kernel/k_thread.h @@ -0,0 +1,768 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <span> +#include <string> +#include <utility> +#include <vector> + +#include <boost/intrusive/list.hpp> + +#include "common/common_types.h" +#include "common/intrusive_red_black_tree.h" +#include "common/spin_lock.h" +#include "core/arm/arm_interface.h" +#include "core/hle/kernel/k_affinity_mask.h" +#include "core/hle/kernel/k_light_lock.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/svc_common.h" +#include "core/hle/kernel/svc_types.h" +#include "core/hle/result.h" + +namespace Common { +class Fiber; +} + +namespace Core { +class ARM_Interface; +class System; +} // namespace Core + +namespace Kernel { + +class GlobalSchedulerContext; +class KernelCore; +class Process; +class KScheduler; +class KThreadQueue; + +using KThreadFunction = VAddr; + +enum class ThreadType : u32 { + Main = 0, + Kernel = 1, + HighPriority = 2, + User = 3, +}; +DECLARE_ENUM_FLAG_OPERATORS(ThreadType); + +enum class SuspendType : u32 { + Process = 0, + Thread = 1, + Debug = 2, + Backtrace = 3, + Init = 4, + + Count, +}; + +enum class ThreadState : u16 { + Initialized = 0, + Waiting = 1, + Runnable = 2, + Terminated = 3, + + SuspendShift = 4, + Mask = (1 << SuspendShift) - 1, + + ProcessSuspended = (1 << (0 + SuspendShift)), + ThreadSuspended = (1 << (1 + SuspendShift)), + DebugSuspended = (1 << (2 + SuspendShift)), + BacktraceSuspended = (1 << (3 + SuspendShift)), + InitSuspended = (1 << (4 + SuspendShift)), + + SuspendFlagMask = ((1 << 5) - 1) << SuspendShift, +}; +DECLARE_ENUM_FLAG_OPERATORS(ThreadState); + +enum class DpcFlag : u32 { + Terminating = (1 << 0), + Terminated = (1 << 1), +}; + +enum class ThreadWaitReasonForDebugging : u32 { + None, ///< Thread is not waiting + Sleep, ///< Thread is waiting due to a SleepThread SVC + IPC, ///< Thread is waiting for the reply from an IPC request + Synchronization, ///< Thread is waiting due to a WaitSynchronization SVC + ConditionVar, ///< Thread is waiting due to a WaitProcessWideKey SVC + Arbitration, ///< Thread is waiting due to a SignalToAddress/WaitForAddress SVC + Suspended, ///< Thread is waiting due to process suspension +}; + +[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel); +[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel); +[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel); + +class KThread final : public KSynchronizationObject, public boost::intrusive::list_base_hook<> { + friend class KScheduler; + friend class Process; + +public: + static constexpr s32 DefaultThreadPriority = 44; + static constexpr s32 IdleThreadPriority = Svc::LowestThreadPriority + 1; + + explicit KThread(KernelCore& kernel); + ~KThread() override; + +public: + using ThreadContext32 = Core::ARM_Interface::ThreadContext32; + using ThreadContext64 = Core::ARM_Interface::ThreadContext64; + using WaiterList = boost::intrusive::list<KThread>; + + /** + * Creates and returns a new thread. The new thread is immediately scheduled + * @param system The instance of the whole system + * @param name The friendly name desired for the thread + * @param entry_point The address at which the thread should start execution + * @param priority The thread's priority + * @param arg User data to pass to the thread + * @param processor_id The ID(s) of the processors on which the thread is desired to be run + * @param stack_top The address of the thread's stack top + * @param owner_process The parent process for the thread, if null, it's a kernel thread + * @return A shared pointer to the newly created thread + */ + [[nodiscard]] static ResultVal<std::shared_ptr<KThread>> Create( + Core::System& system, ThreadType type_flags, std::string name, VAddr entry_point, + u32 priority, u64 arg, s32 processor_id, VAddr stack_top, Process* owner_process); + + /** + * Creates and returns a new thread. The new thread is immediately scheduled + * @param system The instance of the whole system + * @param name The friendly name desired for the thread + * @param entry_point The address at which the thread should start execution + * @param priority The thread's priority + * @param arg User data to pass to the thread + * @param processor_id The ID(s) of the processors on which the thread is desired to be run + * @param stack_top The address of the thread's stack top + * @param owner_process The parent process for the thread, if null, it's a kernel thread + * @param thread_start_func The function where the host context will start. + * @param thread_start_parameter The parameter which will passed to host context on init + * @return A shared pointer to the newly created thread + */ + [[nodiscard]] static ResultVal<std::shared_ptr<KThread>> Create( + Core::System& system, ThreadType type_flags, std::string name, VAddr entry_point, + u32 priority, u64 arg, s32 processor_id, VAddr stack_top, Process* owner_process, + std::function<void(void*)>&& thread_start_func, void* thread_start_parameter); + + [[nodiscard]] std::string GetName() const override { + return name; + } + + void SetName(std::string new_name) { + name = std::move(new_name); + } + + [[nodiscard]] std::string GetTypeName() const override { + return "Thread"; + } + + static constexpr HandleType HANDLE_TYPE = HandleType::Thread; + [[nodiscard]] HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + /** + * Gets the thread's current priority + * @return The current thread's priority + */ + [[nodiscard]] s32 GetPriority() const { + return priority; + } + + /** + * Sets the thread's current priority. + * @param priority The new priority. + */ + void SetPriority(s32 value) { + priority = value; + } + + /** + * Gets the thread's nominal priority. + * @return The current thread's nominal priority. + */ + [[nodiscard]] s32 GetBasePriority() const { + return base_priority; + } + + /** + * Gets the thread's thread ID + * @return The thread's ID + */ + [[nodiscard]] u64 GetThreadID() const { + return thread_id; + } + + void ContinueIfHasKernelWaiters() { + if (GetNumKernelWaiters() > 0) { + Continue(); + } + } + + void Wakeup(); + + void SetBasePriority(s32 value); + + [[nodiscard]] ResultCode Run(); + + void Exit(); + + [[nodiscard]] u32 GetSuspendFlags() const { + return suspend_allowed_flags & suspend_request_flags; + } + + [[nodiscard]] bool IsSuspended() const { + return GetSuspendFlags() != 0; + } + + [[nodiscard]] bool IsSuspendRequested(SuspendType type) const { + return (suspend_request_flags & + (1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type)))) != + 0; + } + + [[nodiscard]] bool IsSuspendRequested() const { + return suspend_request_flags != 0; + } + + void RequestSuspend(SuspendType type); + + void Resume(SuspendType type); + + void TrySuspend(); + + void Continue(); + + void Suspend(); + + void Finalize() override; + + bool IsSignaled() const override; + + void SetSyncedObject(KSynchronizationObject* obj, ResultCode wait_res) { + synced_object = obj; + wait_result = wait_res; + } + + [[nodiscard]] ResultCode GetWaitResult(KSynchronizationObject** out) const { + *out = synced_object; + return wait_result; + } + + /* + * Returns the Thread Local Storage address of the current thread + * @returns VAddr of the thread's TLS + */ + [[nodiscard]] VAddr GetTLSAddress() const { + return tls_address; + } + + /* + * Returns the value of the TPIDR_EL0 Read/Write system register for this thread. + * @returns The value of the TPIDR_EL0 register. + */ + [[nodiscard]] u64 GetTPIDR_EL0() const { + return thread_context_64.tpidr; + } + + /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread. + void SetTPIDR_EL0(u64 value) { + thread_context_64.tpidr = value; + thread_context_32.tpidr = static_cast<u32>(value); + } + + [[nodiscard]] ThreadContext32& GetContext32() { + return thread_context_32; + } + + [[nodiscard]] const ThreadContext32& GetContext32() const { + return thread_context_32; + } + + [[nodiscard]] ThreadContext64& GetContext64() { + return thread_context_64; + } + + [[nodiscard]] const ThreadContext64& GetContext64() const { + return thread_context_64; + } + + [[nodiscard]] std::shared_ptr<Common::Fiber>& GetHostContext(); + + [[nodiscard]] ThreadState GetState() const { + return thread_state & ThreadState::Mask; + } + + [[nodiscard]] ThreadState GetRawState() const { + return thread_state; + } + + void SetState(ThreadState state); + + [[nodiscard]] s64 GetLastScheduledTick() const { + return last_scheduled_tick; + } + + void SetLastScheduledTick(s64 tick) { + last_scheduled_tick = tick; + } + + void AddCpuTime([[maybe_unused]] s32 core_id_, s64 amount) { + cpu_time += amount; + // TODO(bunnei): Debug kernels track per-core tick counts. Should we? + } + + [[nodiscard]] s64 GetCpuTime() const { + return cpu_time; + } + + [[nodiscard]] s32 GetActiveCore() const { + return core_id; + } + + void SetActiveCore(s32 core) { + core_id = core; + } + + [[nodiscard]] s32 GetCurrentCore() const { + return current_core_id; + } + + void SetCurrentCore(s32 core) { + current_core_id = core; + } + + [[nodiscard]] Process* GetOwnerProcess() { + return parent; + } + + [[nodiscard]] const Process* GetOwnerProcess() const { + return parent; + } + + [[nodiscard]] bool IsUserThread() const { + return parent != nullptr; + } + + [[nodiscard]] KThread* GetLockOwner() const { + return lock_owner; + } + + void SetLockOwner(KThread* owner) { + lock_owner = owner; + } + + [[nodiscard]] const KAffinityMask& GetAffinityMask() const { + return physical_affinity_mask; + } + + [[nodiscard]] ResultCode GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask); + + [[nodiscard]] ResultCode GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask); + + [[nodiscard]] ResultCode SetCoreMask(s32 core_id, u64 v_affinity_mask); + + [[nodiscard]] ResultCode SetActivity(Svc::ThreadActivity activity); + + [[nodiscard]] ResultCode Sleep(s64 timeout); + + [[nodiscard]] s64 GetYieldScheduleCount() const { + return schedule_count; + } + + void SetYieldScheduleCount(s64 count) { + schedule_count = count; + } + + void WaitCancel(); + + [[nodiscard]] bool IsWaitCancelled() const { + return wait_cancelled; + } + + [[nodiscard]] void ClearWaitCancelled() { + wait_cancelled = false; + } + + [[nodiscard]] bool IsCancellable() const { + return cancellable; + } + + void SetCancellable() { + cancellable = true; + } + + void ClearCancellable() { + cancellable = false; + } + + [[nodiscard]] bool IsTerminationRequested() const { + return termination_requested || GetRawState() == ThreadState::Terminated; + } + + struct StackParameters { + u8 svc_permission[0x10]; + std::atomic<u8> dpc_flags; + u8 current_svc_id; + bool is_calling_svc; + bool is_in_exception_handler; + bool is_pinned; + s32 disable_count; + KThread* cur_thread; + }; + + [[nodiscard]] StackParameters& GetStackParameters() { + return stack_parameters; + } + + [[nodiscard]] const StackParameters& GetStackParameters() const { + return stack_parameters; + } + + class QueueEntry { + public: + constexpr QueueEntry() = default; + + constexpr void Initialize() { + prev = nullptr; + next = nullptr; + } + + constexpr KThread* GetPrev() const { + return prev; + } + constexpr KThread* GetNext() const { + return next; + } + constexpr void SetPrev(KThread* thread) { + prev = thread; + } + constexpr void SetNext(KThread* thread) { + next = thread; + } + + private: + KThread* prev{}; + KThread* next{}; + }; + + [[nodiscard]] QueueEntry& GetPriorityQueueEntry(s32 core) { + return per_core_priority_queue_entry[core]; + } + + [[nodiscard]] const QueueEntry& GetPriorityQueueEntry(s32 core) const { + return per_core_priority_queue_entry[core]; + } + + void SetSleepingQueue(KThreadQueue* q) { + sleeping_queue = q; + } + + [[nodiscard]] s32 GetDisableDispatchCount() const { + return this->GetStackParameters().disable_count; + } + + void DisableDispatch() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); + this->GetStackParameters().disable_count++; + } + + void EnableDispatch() { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); + this->GetStackParameters().disable_count--; + } + + void Pin(); + + void Unpin(); + + void SetInExceptionHandler() { + this->GetStackParameters().is_in_exception_handler = true; + } + + void ClearInExceptionHandler() { + this->GetStackParameters().is_in_exception_handler = false; + } + + [[nodiscard]] bool IsInExceptionHandler() const { + return this->GetStackParameters().is_in_exception_handler; + } + + void SetIsCallingSvc() { + this->GetStackParameters().is_calling_svc = true; + } + + void ClearIsCallingSvc() { + this->GetStackParameters().is_calling_svc = false; + } + + [[nodiscard]] bool IsCallingSvc() const { + return this->GetStackParameters().is_calling_svc; + } + + [[nodiscard]] u8 GetSvcId() const { + return this->GetStackParameters().current_svc_id; + } + + void RegisterDpc(DpcFlag flag) { + this->GetStackParameters().dpc_flags |= static_cast<u8>(flag); + } + + void ClearDpc(DpcFlag flag) { + this->GetStackParameters().dpc_flags &= ~static_cast<u8>(flag); + } + + [[nodiscard]] u8 GetDpc() const { + return this->GetStackParameters().dpc_flags; + } + + [[nodiscard]] bool HasDpc() const { + return this->GetDpc() != 0; + } + + void SetWaitReasonForDebugging(ThreadWaitReasonForDebugging reason) { + wait_reason_for_debugging = reason; + } + + [[nodiscard]] ThreadWaitReasonForDebugging GetWaitReasonForDebugging() const { + return wait_reason_for_debugging; + } + + [[nodiscard]] ThreadType GetThreadTypeForDebugging() const { + return thread_type_for_debugging; + } + + void SetWaitObjectsForDebugging(const std::span<KSynchronizationObject*>& objects) { + wait_objects_for_debugging.clear(); + wait_objects_for_debugging.reserve(objects.size()); + for (const auto& object : objects) { + wait_objects_for_debugging.emplace_back(object); + } + } + + [[nodiscard]] const std::vector<KSynchronizationObject*>& GetWaitObjectsForDebugging() const { + return wait_objects_for_debugging; + } + + void SetMutexWaitAddressForDebugging(VAddr address) { + mutex_wait_address_for_debugging = address; + } + + [[nodiscard]] VAddr GetMutexWaitAddressForDebugging() const { + return mutex_wait_address_for_debugging; + } + + [[nodiscard]] s32 GetIdealCoreForDebugging() const { + return virtual_ideal_core_id; + } + + void AddWaiter(KThread* thread); + + void RemoveWaiter(KThread* thread); + + [[nodiscard]] ResultCode GetThreadContext3(std::vector<u8>& out); + + [[nodiscard]] KThread* RemoveWaiterByKey(s32* out_num_waiters, VAddr key); + + [[nodiscard]] VAddr GetAddressKey() const { + return address_key; + } + + [[nodiscard]] u32 GetAddressKeyValue() const { + return address_key_value; + } + + void SetAddressKey(VAddr key) { + address_key = key; + } + + void SetAddressKey(VAddr key, u32 val) { + address_key = key; + address_key_value = val; + } + + [[nodiscard]] bool HasWaiters() const { + return !waiter_list.empty(); + } + + [[nodiscard]] s32 GetNumKernelWaiters() const { + return num_kernel_waiters; + } + + [[nodiscard]] u64 GetConditionVariableKey() const { + return condvar_key; + } + + [[nodiscard]] u64 GetAddressArbiterKey() const { + return condvar_key; + } + +private: + static constexpr size_t PriorityInheritanceCountMax = 10; + union SyncObjectBuffer { + std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> sync_objects{}; + std::array<Handle, + Svc::ArgumentHandleCountMax*(sizeof(KSynchronizationObject*) / sizeof(Handle))> + handles; + constexpr SyncObjectBuffer() {} + }; + static_assert(sizeof(SyncObjectBuffer::sync_objects) == sizeof(SyncObjectBuffer::handles)); + + struct ConditionVariableComparator { + struct LightCompareType { + u64 cv_key{}; + s32 priority{}; + + [[nodiscard]] constexpr u64 GetConditionVariableKey() const { + return cv_key; + } + + [[nodiscard]] constexpr s32 GetPriority() const { + return priority; + } + }; + + template <typename T> + requires( + std::same_as<T, KThread> || + std::same_as<T, LightCompareType>) static constexpr int Compare(const T& lhs, + const KThread& rhs) { + const u64 l_key = lhs.GetConditionVariableKey(); + const u64 r_key = rhs.GetConditionVariableKey(); + + if (l_key < r_key) { + // Sort first by key + return -1; + } else if (l_key == r_key && lhs.GetPriority() < rhs.GetPriority()) { + // And then by priority. + return -1; + } else { + return 1; + } + } + }; + + void AddWaiterImpl(KThread* thread); + + void RemoveWaiterImpl(KThread* thread); + + void StartTermination(); + + [[nodiscard]] ResultCode Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top, + s32 prio, s32 virt_core, Process* owner, ThreadType type); + + [[nodiscard]] static ResultCode InitializeThread(KThread* thread, KThreadFunction func, + uintptr_t arg, VAddr user_stack_top, s32 prio, + s32 core, Process* owner, ThreadType type); + + static void RestorePriority(KernelCore& kernel, KThread* thread); + + // For core KThread implementation + ThreadContext32 thread_context_32{}; + ThreadContext64 thread_context_64{}; + Common::IntrusiveRedBlackTreeNode condvar_arbiter_tree_node{}; + s32 priority{}; + using ConditionVariableThreadTreeTraits = + Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert< + &KThread::condvar_arbiter_tree_node>; + using ConditionVariableThreadTree = + ConditionVariableThreadTreeTraits::TreeType<ConditionVariableComparator>; + ConditionVariableThreadTree* condvar_tree{}; + u64 condvar_key{}; + u64 virtual_affinity_mask{}; + KAffinityMask physical_affinity_mask{}; + u64 thread_id{}; + std::atomic<s64> cpu_time{}; + KSynchronizationObject* synced_object{}; + VAddr address_key{}; + Process* parent{}; + VAddr kernel_stack_top{}; + u32* light_ipc_data{}; + VAddr tls_address{}; + KLightLock activity_pause_lock; + s64 schedule_count{}; + s64 last_scheduled_tick{}; + std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{}; + KThreadQueue* sleeping_queue{}; + WaiterList waiter_list{}; + WaiterList pinned_waiter_list{}; + KThread* lock_owner{}; + u32 address_key_value{}; + u32 suspend_request_flags{}; + u32 suspend_allowed_flags{}; + ResultCode wait_result{RESULT_SUCCESS}; + s32 base_priority{}; + s32 physical_ideal_core_id{}; + s32 virtual_ideal_core_id{}; + s32 num_kernel_waiters{}; + s32 current_core_id{}; + s32 core_id{}; + KAffinityMask original_physical_affinity_mask{}; + s32 original_physical_ideal_core_id{}; + s32 num_core_migration_disables{}; + ThreadState thread_state{}; + std::atomic<bool> termination_requested{}; + bool wait_cancelled{}; + bool cancellable{}; + bool signaled{}; + bool initialized{}; + bool debug_attached{}; + s8 priority_inheritance_count{}; + bool resource_limit_release_hint{}; + StackParameters stack_parameters{}; + Common::SpinLock context_guard{}; + + // For emulation + std::shared_ptr<Common::Fiber> host_context{}; + + // For debugging + std::vector<KSynchronizationObject*> wait_objects_for_debugging; + VAddr mutex_wait_address_for_debugging{}; + ThreadWaitReasonForDebugging wait_reason_for_debugging{}; + ThreadType thread_type_for_debugging{}; + std::string name; + +public: + using ConditionVariableThreadTreeType = ConditionVariableThreadTree; + + void SetConditionVariable(ConditionVariableThreadTree* tree, VAddr address, u64 cv_key, + u32 value) { + condvar_tree = tree; + condvar_key = cv_key; + address_key = address; + address_key_value = value; + } + + void ClearConditionVariable() { + condvar_tree = nullptr; + } + + [[nodiscard]] bool IsWaitingForConditionVariable() const { + return condvar_tree != nullptr; + } + + void SetAddressArbiter(ConditionVariableThreadTree* tree, u64 address) { + condvar_tree = tree; + condvar_key = address; + } + + void ClearAddressArbiter() { + condvar_tree = nullptr; + } + + [[nodiscard]] bool IsWaitingForAddressArbiter() const { + return condvar_tree != nullptr; + } + + [[nodiscard]] ConditionVariableThreadTree* GetConditionVariableTree() const { + return condvar_tree; + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_thread_queue.h b/src/core/hle/kernel/k_thread_queue.h new file mode 100644 index 000000000..c52eba249 --- /dev/null +++ b/src/core/hle/kernel/k_thread_queue.h @@ -0,0 +1,81 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/k_thread.h" + +namespace Kernel { + +class KThreadQueue { +public: + explicit KThreadQueue(KernelCore& kernel) : kernel{kernel} {} + + bool IsEmpty() const { + return wait_list.empty(); + } + + KThread::WaiterList::iterator begin() { + return wait_list.begin(); + } + KThread::WaiterList::iterator end() { + return wait_list.end(); + } + + bool SleepThread(KThread* t) { + KScopedSchedulerLock sl{kernel}; + + // If the thread needs terminating, don't enqueue it. + if (t->IsTerminationRequested()) { + return false; + } + + // Set the thread's queue and mark it as waiting. + t->SetSleepingQueue(this); + t->SetState(ThreadState::Waiting); + + // Add the thread to the queue. + wait_list.push_back(*t); + + return true; + } + + void WakeupThread(KThread* t) { + KScopedSchedulerLock sl{kernel}; + + // Remove the thread from the queue. + wait_list.erase(wait_list.iterator_to(*t)); + + // Mark the thread as no longer sleeping. + t->SetState(ThreadState::Runnable); + t->SetSleepingQueue(nullptr); + } + + KThread* WakeupFrontThread() { + KScopedSchedulerLock sl{kernel}; + + if (wait_list.empty()) { + return nullptr; + } else { + // Remove the thread from the queue. + auto it = wait_list.begin(); + KThread* thread = std::addressof(*it); + wait_list.erase(it); + + ASSERT(thread->GetState() == ThreadState::Waiting); + + // Mark the thread as no longer sleeping. + thread->SetState(ThreadState::Runnable); + thread->SetSleepingQueue(nullptr); + + return thread; + } + } + +private: + KernelCore& kernel; + KThread::WaiterList wait_list{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_writable_event.cpp b/src/core/hle/kernel/k_writable_event.cpp new file mode 100644 index 000000000..25c52edb2 --- /dev/null +++ b/src/core/hle/kernel/k_writable_event.cpp @@ -0,0 +1,27 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" + +namespace Kernel { + +KWritableEvent::KWritableEvent(KernelCore& kernel, std::string&& name) + : Object{kernel, std::move(name)} {} +KWritableEvent::~KWritableEvent() = default; + +void KWritableEvent::Initialize(KEvent* parent_) { + parent = parent_; +} + +ResultCode KWritableEvent::Signal() { + return parent->GetReadableEvent()->Signal(); +} + +ResultCode KWritableEvent::Clear() { + return parent->GetReadableEvent()->Clear(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_writable_event.h b/src/core/hle/kernel/k_writable_event.h new file mode 100644 index 000000000..518f5448d --- /dev/null +++ b/src/core/hle/kernel/k_writable_event.h @@ -0,0 +1,44 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/object.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KernelCore; +class KEvent; + +class KWritableEvent final : public Object { +public: + explicit KWritableEvent(KernelCore& kernel, std::string&& name); + ~KWritableEvent() override; + + std::string GetTypeName() const override { + return "KWritableEvent"; + } + + static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + void Initialize(KEvent* parent_); + + void Finalize() override {} + + ResultCode Signal(); + ResultCode Clear(); + + KEvent* GetParent() const { + return parent; + } + +private: + KEvent* parent{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index cabe8d418..b6e6f115e 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -7,15 +7,15 @@ #include <bitset> #include <functional> #include <memory> -#include <mutex> #include <thread> -#include <unordered_map> +#include <unordered_set> #include <utility> #include "common/assert.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "common/thread.h" +#include "common/thread_worker.h" #include "core/arm/arm_interface.h" #include "core/arm/cpu_interrupt_handler.h" #include "core/arm/exclusive_monitor.h" @@ -26,19 +26,19 @@ #include "core/device_memory.h" #include "core/hardware_properties.h" #include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_layout.h" #include "core/hle/kernel/memory/memory_manager.h" #include "core/hle/kernel/memory/slab_heap.h" #include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/service_thread.h" #include "core/hle/kernel/shared_memory.h" -#include "core/hle/kernel/synchronization.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/kernel/time_manager.h" #include "core/hle/lock.h" #include "core/hle/result.h" @@ -50,26 +50,42 @@ namespace Kernel { struct KernelCore::Impl { explicit Impl(Core::System& system, KernelCore& kernel) - : global_scheduler{kernel}, synchronization{system}, time_manager{system}, - global_handle_table{kernel}, system{system} {} + : time_manager{system}, global_handle_table{kernel}, system{system} {} void SetMulticore(bool is_multicore) { this->is_multicore = is_multicore; } void Initialize(KernelCore& kernel) { - Shutdown(); + global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel); + RegisterHostThread(); + service_thread_manager = + std::make_unique<Common::ThreadWorker>(1, "yuzu:ServiceThreadManager"); + is_phantom_mode_for_singlecore = false; + InitializePhysicalCores(); - InitializeSystemResourceLimit(kernel); + InitializeSystemResourceLimit(kernel, system); InitializeMemoryLayout(); InitializePreemption(kernel); InitializeSchedulers(); InitializeSuspendThreads(); } + void InitializeCores() { + for (auto& core : cores) { + core.Initialize(current_process->Is64BitProcess()); + } + } + void Shutdown() { + process_list.clear(); + + // Ensures all service threads gracefully shutdown + service_thread_manager.reset(); + service_threads.clear(); + next_object_id = 0; next_kernel_process_id = Process::InitialKIPIDMin; next_user_process_id = Process::ProcessIDMin; @@ -81,74 +97,69 @@ struct KernelCore::Impl { } } - for (std::size_t i = 0; i < cores.size(); i++) { - cores[i].Shutdown(); - schedulers[i].reset(); - } cores.clear(); - registered_core_threads.reset(); - - process_list.clear(); current_process = nullptr; system_resource_limit = nullptr; global_handle_table.Clear(); - preemption_event = nullptr; - global_scheduler.Shutdown(); + preemption_event = nullptr; named_ports.clear(); - for (auto& core : cores) { - core.Shutdown(); - } - cores.clear(); - exclusive_monitor.reset(); - host_thread_ids.clear(); + + // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others + next_host_thread_id = Core::Hardware::NUM_CPU_CORES; } void InitializePhysicalCores() { exclusive_monitor = Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); - for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i); - cores.emplace_back(system, i, *schedulers[i], interrupts[i]); + for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i); + cores.emplace_back(i, system, *schedulers[i], interrupts); } } void InitializeSchedulers() { - for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { cores[i].Scheduler().Initialize(); } } // Creates the default system resource limit - void InitializeSystemResourceLimit(KernelCore& kernel) { - system_resource_limit = ResourceLimit::Create(kernel); + void InitializeSystemResourceLimit(KernelCore& kernel, Core::System& system) { + system_resource_limit = std::make_shared<KResourceLimit>(kernel, system); // If setting the default system values fails, then something seriously wrong has occurred. - ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x100000000) + ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, 0x100000000) .IsSuccess()); - ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess()); - ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess()); - ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess()); - ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Events, 700).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200) + .IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Sessions, 933).IsSuccess()); - if (!system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0) || - !system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0x60000)) { + // Derived from recent software updates. The kernel reserves 27MB + constexpr u64 kernel_size{0x1b00000}; + if (!system_resource_limit->Reserve(LimitableResource::PhysicalMemory, kernel_size)) { UNREACHABLE(); } + // Reserve secure applet memory, introduced in firmware 5.0.0 + constexpr u64 secure_applet_memory_size{0x400000}; + ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory, + secure_applet_memory_size)); } void InitializePreemption(KernelCore& kernel) { preemption_event = Core::Timing::CreateEvent( "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) { { - SchedulerLock lock(kernel); - global_scheduler.PreemptThreads(); + KScopedSchedulerLock lock(kernel); + global_scheduler_context->PreemptThreads(); } const auto time_interval = std::chrono::nanoseconds{ Core::Timing::msToCycles(std::chrono::milliseconds(10))}; @@ -165,11 +176,9 @@ struct KernelCore::Impl { std::string name = "Suspend Thread Id:" + std::to_string(i); std::function<void(void*)> init_func = Core::CpuManager::GetSuspendThreadStartFunc(); void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); - const auto type = - static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND); - auto thread_res = - Thread::Create(system, type, std::move(name), 0, 0, 0, static_cast<u32>(i), 0, - nullptr, std::move(init_func), init_func_parameter); + auto thread_res = KThread::Create(system, ThreadType::HighPriority, std::move(name), 0, + 0, 0, static_cast<u32>(i), 0, nullptr, + std::move(init_func), init_func_parameter); suspend_threads[i] = std::move(thread_res).Unwrap(); } @@ -177,69 +186,82 @@ struct KernelCore::Impl { void MakeCurrentProcess(Process* process) { current_process = process; - if (process == nullptr) { return; } - u32 core_id = GetCurrentHostThreadID(); + const u32 core_id = GetCurrentHostThreadID(); if (core_id < Core::Hardware::NUM_CPU_CORES) { system.Memory().SetCurrentPageTable(*process, core_id); } } + /// Creates a new host thread ID, should only be called by GetHostThreadId + u32 AllocateHostThreadId(std::optional<std::size_t> core_id) { + if (core_id) { + // The first for slots are reserved for CPU core threads + ASSERT(*core_id < Core::Hardware::NUM_CPU_CORES); + return static_cast<u32>(*core_id); + } else { + return next_host_thread_id++; + } + } + + /// Gets the host thread ID for the caller, allocating a new one if this is the first time + u32 GetHostThreadId(std::optional<std::size_t> core_id = std::nullopt) { + const thread_local auto host_thread_id{AllocateHostThreadId(core_id)}; + return host_thread_id; + } + + // Gets the dummy KThread for the caller, allocating a new one if this is the first time + KThread* GetHostDummyThread() { + const thread_local auto thread = + KThread::Create( + system, ThreadType::Main, fmt::format("DummyThread:{}", GetHostThreadId()), 0, + KThread::DefaultThreadPriority, 0, static_cast<u32>(3), 0, nullptr, + []([[maybe_unused]] void* arg) { UNREACHABLE(); }, nullptr) + .Unwrap(); + return thread.get(); + } + + /// Registers a CPU core thread by allocating a host thread ID for it void RegisterCoreThread(std::size_t core_id) { - std::unique_lock lock{register_thread_mutex}; + ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); + const auto this_id = GetHostThreadId(core_id); if (!is_multicore) { - single_core_thread_id = std::this_thread::get_id(); + single_core_thread_id = this_id; } - const std::thread::id this_id = std::this_thread::get_id(); - const auto it = host_thread_ids.find(this_id); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - ASSERT(it == host_thread_ids.end()); - ASSERT(!registered_core_threads[core_id]); - host_thread_ids[this_id] = static_cast<u32>(core_id); - registered_core_threads.set(core_id); } + /// Registers a new host thread by allocating a host thread ID for it void RegisterHostThread() { - std::unique_lock lock{register_thread_mutex}; - const std::thread::id this_id = std::this_thread::get_id(); - const auto it = host_thread_ids.find(this_id); - if (it != host_thread_ids.end()) { - return; - } - host_thread_ids[this_id] = registered_thread_ids++; + [[maybe_unused]] const auto this_id = GetHostThreadId(); + [[maybe_unused]] const auto dummy_thread = GetHostDummyThread(); } - u32 GetCurrentHostThreadID() const { - const std::thread::id this_id = std::this_thread::get_id(); - if (!is_multicore) { - if (single_core_thread_id == this_id) { - return static_cast<u32>(system.GetCpuManager().CurrentCore()); - } - } - const auto it = host_thread_ids.find(this_id); - if (it == host_thread_ids.end()) { - return Core::INVALID_HOST_THREAD_ID; + [[nodiscard]] u32 GetCurrentHostThreadID() { + const auto this_id = GetHostThreadId(); + if (!is_multicore && single_core_thread_id == this_id) { + return static_cast<u32>(system.GetCpuManager().CurrentCore()); } - return it->second; + return this_id; } - Core::EmuThreadHandle GetCurrentEmuThreadID() const { - Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle(); - result.host_handle = GetCurrentHostThreadID(); - if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) { - return result; - } - const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler(); - const Kernel::Thread* current = sched.GetCurrentThread(); - if (current != nullptr && !current->IsPhantomMode()) { - result.guest_handle = current->GetGlobalHandle(); - } else { - result.guest_handle = InvalidHandle; + bool IsPhantomModeForSingleCore() const { + return is_phantom_mode_for_singlecore; + } + + void SetIsPhantomModeForSingleCore(bool value) { + ASSERT(!is_multicore); + is_phantom_mode_for_singlecore = value; + } + + KThread* GetCurrentEmuThread() { + const auto thread_id = GetCurrentHostThreadID(); + if (thread_id >= Core::Hardware::NUM_CPU_CORES) { + return GetHostDummyThread(); } - return result; + return schedulers[thread_id]->GetCurrentThread(); } void InitializeMemoryLayout() { @@ -286,8 +308,11 @@ struct KernelCore::Impl { // Allocate slab heaps user_slab_heap_pages = std::make_unique<Memory::SlabHeap<Memory::Page>>(); + constexpr u64 user_slab_heap_size{0x1ef000}; + // Reserve slab heaps + ASSERT( + system_resource_limit->Reserve(LimitableResource::PhysicalMemory, user_slab_heap_size)); // Initialize slab heaps - constexpr u64 user_slab_heap_size{0x3de000}; user_slab_heap_pages->Initialize( system.DeviceMemory().GetPointer(Core::DramMemoryMap::SlabHeapBase), user_slab_heap_size); @@ -301,11 +326,10 @@ struct KernelCore::Impl { // Lists all processes that exist in the current session. std::vector<std::shared_ptr<Process>> process_list; Process* current_process = nullptr; - Kernel::GlobalScheduler global_scheduler; - Kernel::Synchronization synchronization; + std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context; Kernel::TimeManager time_manager; - std::shared_ptr<ResourceLimit> system_resource_limit; + std::shared_ptr<KResourceLimit> system_resource_limit; std::shared_ptr<Core::Timing::EventType> preemption_event; @@ -320,11 +344,8 @@ struct KernelCore::Impl { std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor; std::vector<Kernel::PhysicalCore> cores; - // 0-3 IDs represent core threads, >3 represent others - std::unordered_map<std::thread::id, u32> host_thread_ids; - u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES}; - std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads; - std::mutex register_thread_mutex; + // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others + std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES}; // Kernel memory management std::unique_ptr<Memory::MemoryManager> memory_manager; @@ -336,12 +357,20 @@ struct KernelCore::Impl { std::shared_ptr<Kernel::SharedMemory> irs_shared_mem; std::shared_ptr<Kernel::SharedMemory> time_shared_mem; - std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{}; + // Threads used for services + std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads; + + // Service threads are managed by a worker thread, so that a calling service thread can queue up + // the release of itself + std::unique_ptr<Common::ThreadWorker> service_thread_manager; + + std::array<std::shared_ptr<KThread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{}; std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{}; - std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; + std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; bool is_multicore{}; - std::thread::id single_core_thread_id{}; + bool is_phantom_mode_for_singlecore{}; + u32 single_core_thread_id{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{}; @@ -362,16 +391,20 @@ void KernelCore::Initialize() { impl->Initialize(*this); } +void KernelCore::InitializeCores() { + impl->InitializeCores(); +} + void KernelCore::Shutdown() { impl->Shutdown(); } -std::shared_ptr<ResourceLimit> KernelCore::GetSystemResourceLimit() const { +std::shared_ptr<KResourceLimit> KernelCore::GetSystemResourceLimit() const { return impl->system_resource_limit; } -std::shared_ptr<Thread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const { - return impl->global_handle_table.Get<Thread>(handle); +std::shared_ptr<KThread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const { + return impl->global_handle_table.Get<KThread>(handle); } void KernelCore::AppendNewProcess(std::shared_ptr<Process> process) { @@ -394,19 +427,19 @@ const std::vector<std::shared_ptr<Process>>& KernelCore::GetProcessList() const return impl->process_list; } -Kernel::GlobalScheduler& KernelCore::GlobalScheduler() { - return impl->global_scheduler; +Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() { + return *impl->global_scheduler_context; } -const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const { - return impl->global_scheduler; +const Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() const { + return *impl->global_scheduler_context; } -Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) { +Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) { return *impl->schedulers[id]; } -const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const { +const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const { return *impl->schedulers[id]; } @@ -430,16 +463,13 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { return impl->cores[core_id]; } -Kernel::Scheduler& KernelCore::CurrentScheduler() { +Kernel::KScheduler* KernelCore::CurrentScheduler() { u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return *impl->schedulers[core_id]; -} - -const Kernel::Scheduler& KernelCore::CurrentScheduler() const { - u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return *impl->schedulers[core_id]; + if (core_id >= Core::Hardware::NUM_CPU_CORES) { + // This is expected when called from not a guest thread + return {}; + } + return impl->schedulers[core_id].get(); } std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() { @@ -451,14 +481,6 @@ const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Kern return impl->interrupts; } -Kernel::Synchronization& KernelCore::Synchronization() { - return impl->synchronization; -} - -const Kernel::Synchronization& KernelCore::Synchronization() const { - return impl->synchronization; -} - Kernel::TimeManager& KernelCore::TimeManager() { return impl->time_manager; } @@ -476,12 +498,17 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const { } void KernelCore::InvalidateAllInstructionCaches() { - auto& threads = GlobalScheduler().GetThreadList(); - for (auto& thread : threads) { - if (!thread->IsHLEThread()) { - auto& arm_interface = thread->ArmInterface(); - arm_interface.ClearInstructionCache(); + for (auto& physical_core : impl->cores) { + physical_core.ArmInterface().ClearInstructionCache(); + } +} + +void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { + for (auto& physical_core : impl->cores) { + if (!physical_core.IsInitialized()) { + continue; } + physical_core.ArmInterface().InvalidateCacheRange(addr, size); } } @@ -542,8 +569,8 @@ u32 KernelCore::GetCurrentHostThreadID() const { return impl->GetCurrentHostThreadID(); } -Core::EmuThreadHandle KernelCore::GetCurrentEmuThreadID() const { - return impl->GetCurrentEmuThreadID(); +KThread* KernelCore::GetCurrentEmuThread() const { + return impl->GetCurrentEmuThread(); } Memory::MemoryManager& KernelCore::MemoryManager() { @@ -597,10 +624,12 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const { void KernelCore::Suspend(bool in_suspention) { const bool should_suspend = exception_exited || in_suspention; { - SchedulerLock lock(*this); - ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep; + KScopedSchedulerLock lock(*this); + const auto state = should_suspend ? ThreadState::Runnable : ThreadState::Waiting; for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - impl->suspend_threads[i]->SetStatus(status); + impl->suspend_threads[i]->SetState(state); + impl->suspend_threads[i]->SetWaitReasonForDebugging( + ThreadWaitReasonForDebugging::Suspended); } } } @@ -624,4 +653,27 @@ void KernelCore::ExitSVCProfile() { MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]); } +std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) { + auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name); + impl->service_thread_manager->QueueWork( + [this, service_thread] { impl->service_threads.emplace(service_thread); }); + return service_thread; +} + +void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) { + impl->service_thread_manager->QueueWork([this, service_thread] { + if (auto strong_ptr = service_thread.lock()) { + impl->service_threads.erase(strong_ptr); + } + }); +} + +bool KernelCore::IsPhantomModeForSingleCore() const { + return impl->IsPhantomModeForSingleCore(); +} + +void KernelCore::SetIsPhantomModeForSingleCore(bool value) { + impl->SetIsPhantomModeForSingleCore(value); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 16285c3f0..806a0d986 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -33,19 +33,23 @@ template <typename T> class SlabHeap; } // namespace Memory -class AddressArbiter; class ClientPort; -class GlobalScheduler; +class GlobalSchedulerContext; class HandleTable; class PhysicalCore; class Process; -class ResourceLimit; -class Scheduler; +class KResourceLimit; +class KScheduler; class SharedMemory; +class ServiceThread; class Synchronization; -class Thread; +class KThread; class TimeManager; +using EmuThreadHandle = uintptr_t; +constexpr EmuThreadHandle EmuThreadHandleInvalid{}; +constexpr EmuThreadHandle EmuThreadHandleReserved{1ULL << 63}; + /// Represents a single instance of the kernel. class KernelCore { private: @@ -74,14 +78,17 @@ public: /// Resets the kernel to a clean slate for use. void Initialize(); + /// Initializes the CPU cores. + void InitializeCores(); + /// Clears all resources in use by the kernel instance. void Shutdown(); /// Retrieves a shared pointer to the system resource limit instance. - std::shared_ptr<ResourceLimit> GetSystemResourceLimit() const; + std::shared_ptr<KResourceLimit> GetSystemResourceLimit() const; /// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table. - std::shared_ptr<Thread> RetrieveThreadFromGlobalHandleTable(Handle handle) const; + std::shared_ptr<KThread> RetrieveThreadFromGlobalHandleTable(Handle handle) const; /// Adds the given shared pointer to an internal list of active processes. void AppendNewProcess(std::shared_ptr<Process> process); @@ -99,16 +106,16 @@ public: const std::vector<std::shared_ptr<Process>>& GetProcessList() const; /// Gets the sole instance of the global scheduler - Kernel::GlobalScheduler& GlobalScheduler(); + Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); /// Gets the sole instance of the global scheduler - const Kernel::GlobalScheduler& GlobalScheduler() const; + const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const; /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' - Kernel::Scheduler& Scheduler(std::size_t id); + Kernel::KScheduler& Scheduler(std::size_t id); /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' - const Kernel::Scheduler& Scheduler(std::size_t id) const; + const Kernel::KScheduler& Scheduler(std::size_t id) const; /// Gets the an instance of the respective physical CPU core. Kernel::PhysicalCore& PhysicalCore(std::size_t id); @@ -117,10 +124,7 @@ public: const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const; /// Gets the sole instance of the Scheduler at the current running core. - Kernel::Scheduler& CurrentScheduler(); - - /// Gets the sole instance of the Scheduler at the current running core. - const Kernel::Scheduler& CurrentScheduler() const; + Kernel::KScheduler* CurrentScheduler(); /// Gets the an instance of the current physical CPU core. Kernel::PhysicalCore& CurrentPhysicalCore(); @@ -128,12 +132,6 @@ public: /// Gets the an instance of the current physical CPU core. const Kernel::PhysicalCore& CurrentPhysicalCore() const; - /// Gets the an instance of the Synchronization Interface. - Kernel::Synchronization& Synchronization(); - - /// Gets the an instance of the Synchronization Interface. - const Kernel::Synchronization& Synchronization() const; - /// Gets the an instance of the TimeManager Interface. Kernel::TimeManager& TimeManager(); @@ -153,6 +151,8 @@ public: void InvalidateAllInstructionCaches(); + void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); + /// Adds a port to the named port table void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port); @@ -165,8 +165,8 @@ public: /// Determines whether or not the given port is a valid named port. bool IsValidNamedPort(NamedPortTable::const_iterator port) const; - /// Gets the current host_thread/guest_thread handle. - Core::EmuThreadHandle GetCurrentEmuThreadID() const; + /// Gets the current host_thread/guest_thread pointer. + KThread* GetCurrentEmuThread() const; /// Gets the current host_thread handle. u32 GetCurrentHostThreadID() const; @@ -225,10 +225,30 @@ public: void ExitSVCProfile(); + /** + * Creates an HLE service thread, which are used to execute service routines asynchronously. + * While these are allocated per ServerSession, these need to be owned and managed outside of + * ServerSession to avoid a circular dependency. + * @param name String name for the ServerSession creating this thread, used for debug purposes. + * @returns The a weak pointer newly created service thread. + */ + std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(const std::string& name); + + /** + * Releases a HLE service thread, instructing KernelCore to free it. This should be called when + * the ServerSession associated with the thread is destroyed. + * @param service_thread Service thread to release. + */ + void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread); + + /// Workaround for single-core mode when preempting threads while idle. + bool IsPhantomModeForSingleCore() const; + void SetIsPhantomModeForSingleCore(bool value); + private: friend class Object; friend class Process; - friend class Thread; + friend class KThread; /// Creates a new object ID, incrementing the internal object ID counter. u32 CreateNewObjectID(); diff --git a/src/core/hle/kernel/memory/address_space_info.cpp b/src/core/hle/kernel/memory/address_space_info.cpp index e4288cab4..6cf43ba24 100644 --- a/src/core/hle/kernel/memory/address_space_info.cpp +++ b/src/core/hle/kernel/memory/address_space_info.cpp @@ -96,6 +96,7 @@ u64 AddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) { return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address; } UNREACHABLE(); + return 0; } std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) { @@ -112,6 +113,7 @@ std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size; } UNREACHABLE(); + return 0; } } // namespace Kernel::Memory diff --git a/src/core/hle/kernel/memory/memory_block.h b/src/core/hle/kernel/memory/memory_block.h index 9d7839d08..83acece1e 100644 --- a/src/core/hle/kernel/memory/memory_block.h +++ b/src/core/hle/kernel/memory/memory_block.h @@ -73,12 +73,12 @@ enum class MemoryState : u32 { ThreadLocal = static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted, - Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc | - FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | - FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, + Transferred = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc | + FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | + FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, - SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransfered) | FlagsMisc | - FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, + SharedTransferred = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc | + FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped | FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, @@ -111,8 +111,8 @@ static_assert(static_cast<u32>(MemoryState::AliasCodeData) == 0x03FFBD09); static_assert(static_cast<u32>(MemoryState::Ipc) == 0x005C3C0A); static_assert(static_cast<u32>(MemoryState::Stack) == 0x005C3C0B); static_assert(static_cast<u32>(MemoryState::ThreadLocal) == 0x0040200C); -static_assert(static_cast<u32>(MemoryState::Transfered) == 0x015C3C0D); -static_assert(static_cast<u32>(MemoryState::SharedTransfered) == 0x005C380E); +static_assert(static_cast<u32>(MemoryState::Transferred) == 0x015C3C0D); +static_assert(static_cast<u32>(MemoryState::SharedTransferred) == 0x005C380E); static_assert(static_cast<u32>(MemoryState::SharedCode) == 0x0040380F); static_assert(static_cast<u32>(MemoryState::Inaccessible) == 0x00000010); static_assert(static_cast<u32>(MemoryState::NonSecureIpc) == 0x005C3811); @@ -222,9 +222,9 @@ public: public: constexpr MemoryBlock() = default; - constexpr MemoryBlock(VAddr addr, std::size_t num_pages, MemoryState state, - MemoryPermission perm, MemoryAttribute attribute) - : addr{addr}, num_pages(num_pages), state{state}, perm{perm}, attribute{attribute} {} + constexpr MemoryBlock(VAddr addr_, std::size_t num_pages_, MemoryState state_, + MemoryPermission perm_, MemoryAttribute attribute_) + : addr{addr_}, num_pages(num_pages_), state{state_}, perm{perm_}, attribute{attribute_} {} constexpr VAddr GetAddress() const { return addr; diff --git a/src/core/hle/kernel/memory/memory_block_manager.h b/src/core/hle/kernel/memory/memory_block_manager.h index 6e1d41075..f57d1bbcc 100644 --- a/src/core/hle/kernel/memory/memory_block_manager.h +++ b/src/core/hle/kernel/memory/memory_block_manager.h @@ -57,8 +57,8 @@ public: private: void MergeAdjacent(iterator it, iterator& next_it); - const VAddr start_addr; - const VAddr end_addr; + [[maybe_unused]] const VAddr start_addr; + [[maybe_unused]] const VAddr end_addr; MemoryBlockTree memory_block_tree; }; diff --git a/src/core/hle/kernel/memory/memory_layout.h b/src/core/hle/kernel/memory/memory_layout.h index 9b3d6267a..c7c0b2f49 100644 --- a/src/core/hle/kernel/memory/memory_layout.h +++ b/src/core/hle/kernel/memory/memory_layout.h @@ -5,9 +5,28 @@ #pragma once #include "common/common_types.h" +#include "core/device_memory.h" namespace Kernel::Memory { +constexpr std::size_t KernelAslrAlignment = 2 * 1024 * 1024; +constexpr std::size_t KernelVirtualAddressSpaceWidth = 1ULL << 39; +constexpr std::size_t KernelPhysicalAddressSpaceWidth = 1ULL << 48; +constexpr std::size_t KernelVirtualAddressSpaceBase = 0ULL - KernelVirtualAddressSpaceWidth; +constexpr std::size_t KernelVirtualAddressSpaceEnd = + KernelVirtualAddressSpaceBase + (KernelVirtualAddressSpaceWidth - KernelAslrAlignment); +constexpr std::size_t KernelVirtualAddressSpaceLast = KernelVirtualAddressSpaceEnd - 1; +constexpr std::size_t KernelVirtualAddressSpaceSize = + KernelVirtualAddressSpaceEnd - KernelVirtualAddressSpaceBase; + +constexpr bool IsKernelAddressKey(VAddr key) { + return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast; +} + +constexpr bool IsKernelAddress(VAddr address) { + return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; +} + class MemoryRegion final { friend class MemoryLayout; diff --git a/src/core/hle/kernel/memory/memory_manager.cpp b/src/core/hle/kernel/memory/memory_manager.cpp index acf13585c..77f135cdc 100644 --- a/src/core/hle/kernel/memory/memory_manager.cpp +++ b/src/core/hle/kernel/memory/memory_manager.cpp @@ -8,9 +8,9 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/scope_exit.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory/memory_manager.h" #include "core/hle/kernel/memory/page_linked_list.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel::Memory { @@ -95,7 +95,7 @@ ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pa // Choose a heap based on our page size request const s32 heap_index{PageHeap::GetBlockIndex(num_pages)}; if (heap_index < 0) { - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } // TODO (bunnei): Support multiple managers @@ -140,7 +140,7 @@ ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pa // Only succeed if we allocated as many pages as we wanted if (num_pages) { - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } // We succeeded! diff --git a/src/core/hle/kernel/memory/page_heap.h b/src/core/hle/kernel/memory/page_heap.h index 22b0de860..131093284 100644 --- a/src/core/hle/kernel/memory/page_heap.h +++ b/src/core/hle/kernel/memory/page_heap.h @@ -8,11 +8,11 @@ #pragma once #include <array> +#include <bit> #include <vector> #include "common/alignment.h" #include "common/assert.h" -#include "common/bit_util.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hle/kernel/memory/memory_types.h" @@ -105,7 +105,7 @@ private: ASSERT(depth == 0); return -1; } - offset = offset * 64 + Common::CountTrailingZeroes64(v); + offset = offset * 64 + static_cast<u32>(std::countr_zero(v)); ++depth; } while (depth < static_cast<s32>(used_depths)); diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp index a3fadb533..00ed9b881 100644 --- a/src/core/hle/kernel/memory/page_table.cpp +++ b/src/core/hle/kernel/memory/page_table.cpp @@ -6,7 +6,7 @@ #include "common/assert.h" #include "common/scope_exit.h" #include "core/core.h" -#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/address_space_info.h" #include "core/hle/kernel/memory/memory_block.h" @@ -15,7 +15,7 @@ #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/system_control.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" +#include "core/hle/kernel/svc_results.h" #include "core/memory.h" namespace Kernel::Memory { @@ -141,7 +141,7 @@ ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_t (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)}; if (alloc_size < needed_size) { UNREACHABLE(); - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } const std::size_t remaining_size{alloc_size - needed_size}; @@ -265,7 +265,7 @@ ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_t physical_memory_usage = 0; memory_pool = pool; - page_table_impl.Resize(address_space_width, PageBits, true); + page_table_impl.Resize(address_space_width, PageBits); return InitializeMemoryLayout(start, end); } @@ -277,11 +277,11 @@ ResultCode PageTable::MapProcessCode(VAddr addr, std::size_t num_pages, MemorySt const u64 size{num_pages * PageSize}; if (!CanContain(addr, size, state)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (IsRegionMapped(addr, size)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } PageLinkedList page_linked_list; @@ -307,7 +307,7 @@ ResultCode PageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std:: MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped)); if (IsRegionMapped(dst_addr, size)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } PageLinkedList page_linked_list; @@ -409,27 +409,25 @@ ResultCode PageTable::MapPhysicalMemory(VAddr addr, std::size_t size) { return RESULT_SUCCESS; } - auto process{system.Kernel().CurrentProcess()}; const std::size_t remaining_size{size - mapped_size}; const std::size_t remaining_pages{remaining_size / PageSize}; - if (process->GetResourceLimit() && - !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, remaining_size)) { - return ERR_RESOURCE_LIMIT_EXCEEDED; + // Reserve the memory from the process resource limit. + KScopedResourceReservation memory_reservation( + system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, + remaining_size); + if (!memory_reservation.Succeeded()) { + LOG_ERROR(Kernel, "Could not reserve remaining {:X} bytes", remaining_size); + return ResultResourceLimitedExceeded; } PageLinkedList page_linked_list; - { - auto block_guard = detail::ScopeExit([&] { - system.Kernel().MemoryManager().Free(page_linked_list, remaining_pages, memory_pool); - process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, remaining_size); - }); - CASCADE_CODE(system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages, - memory_pool)); + CASCADE_CODE( + system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages, memory_pool)); - block_guard.Cancel(); - } + // We succeeded, so commit the memory reservation. + memory_reservation.Commit(); MapPhysicalMemory(page_linked_list, addr, end_addr); @@ -454,12 +452,12 @@ ResultCode PageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) { block_manager->IterateForRange(addr, end_addr, [&](const MemoryInfo& info) { if (info.state == MemoryState::Normal) { if (info.attribute != MemoryAttribute::None) { - result = ERR_INVALID_ADDRESS_STATE; + result = ResultInvalidCurrentMemory; return; } mapped_size += GetSizeInRange(info, addr, end_addr); } else if (info.state != MemoryState::Free) { - result = ERR_INVALID_ADDRESS_STATE; + result = ResultInvalidCurrentMemory; } }); @@ -474,7 +472,7 @@ ResultCode PageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) { CASCADE_CODE(UnmapMemory(addr, size)); auto process{system.Kernel().CurrentProcess()}; - process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, mapped_size); + process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size); physical_memory_usage -= mapped_size; return RESULT_SUCCESS; @@ -526,7 +524,7 @@ ResultCode PageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) { MemoryAttribute::Mask, MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped)); if (IsRegionMapped(dst_addr, size)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } PageLinkedList page_linked_list; @@ -577,7 +575,7 @@ ResultCode PageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) { AddRegionToPages(dst_addr, num_pages, dst_pages); if (!dst_pages.IsEqual(src_pages)) { - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } { @@ -626,11 +624,11 @@ ResultCode PageTable::MapPages(VAddr addr, PageLinkedList& page_linked_list, Mem const std::size_t size{num_pages * PageSize}; if (!CanContain(addr, size, state)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (IsRegionMapped(addr, num_pages * PageSize)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } CASCADE_CODE(MapPages(addr, page_linked_list, perm)); @@ -670,6 +668,11 @@ ResultCode PageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size, Memo return RESULT_SUCCESS; } + if ((prev_perm & MemoryPermission::Execute) != (perm & MemoryPermission::Execute)) { + // Memory execution state is changing, invalidate CPU cache range + system.InvalidateCpuInstructionCacheRange(addr, size); + } + const std::size_t num_pages{size / PageSize}; const OperationType operation{(perm & MemoryPermission::Execute) != MemoryPermission::None ? OperationType::ChangePermissionsAndRefresh @@ -763,7 +766,7 @@ ResultCode PageTable::SetHeapCapacity(std::size_t new_heap_capacity) { ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) { if (size > heap_region_end - heap_region_start) { - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } const u64 previous_heap_size{GetHeapSize()}; @@ -776,10 +779,14 @@ ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) { const u64 delta{size - previous_heap_size}; - auto process{system.Kernel().CurrentProcess()}; - if (process->GetResourceLimit() && delta != 0 && - !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, delta)) { - return ERR_RESOURCE_LIMIT_EXCEEDED; + // Reserve memory for the heap extension. + KScopedResourceReservation memory_reservation( + system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, + delta); + + if (!memory_reservation.Succeeded()) { + LOG_ERROR(Kernel, "Could not reserve heap extension of size {:X} bytes", delta); + return ResultResourceLimitedExceeded; } PageLinkedList page_linked_list; @@ -789,12 +796,15 @@ ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) { system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool)); if (IsRegionMapped(current_heap_addr, delta)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } CASCADE_CODE( Operate(current_heap_addr, num_pages, page_linked_list, OperationType::MapGroup)); + // Succeeded in allocation, commit the resource reservation + memory_reservation.Commit(); + block_manager->Update(current_heap_addr, num_pages, MemoryState::Normal, MemoryPermission::ReadAndWrite); @@ -811,17 +821,17 @@ ResultVal<VAddr> PageTable::AllocateAndMapMemory(std::size_t needed_num_pages, s std::lock_guard lock{page_table_lock}; if (!CanContain(region_start, region_num_pages * PageSize, state)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (region_num_pages <= needed_num_pages) { - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } const VAddr addr{ AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)}; if (!addr) { - return ERR_OUT_OF_MEMORY; + return ResultOutOfMemory; } if (is_map_only) { @@ -1002,8 +1012,8 @@ constexpr VAddr PageTable::GetRegionAddress(MemoryState state) const { case MemoryState::Shared: case MemoryState::AliasCode: case MemoryState::AliasCodeData: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: @@ -1037,8 +1047,8 @@ constexpr std::size_t PageTable::GetRegionSize(MemoryState state) const { case MemoryState::Shared: case MemoryState::AliasCode: case MemoryState::AliasCodeData: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: @@ -1075,8 +1085,8 @@ constexpr bool PageTable::CanContain(VAddr addr, std::size_t size, MemoryState s case MemoryState::AliasCodeData: case MemoryState::Stack: case MemoryState::ThreadLocal: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: @@ -1100,13 +1110,13 @@ constexpr ResultCode PageTable::CheckMemoryState(const MemoryInfo& info, MemoryS MemoryAttribute attr) const { // Validate the states match expectation if ((info.state & state_mask) != state) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if ((info.perm & perm_mask) != perm) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if ((info.attribute & attr_mask) != attr) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } return RESULT_SUCCESS; @@ -1133,14 +1143,14 @@ ResultCode PageTable::CheckMemoryState(MemoryState* out_state, MemoryPermission* while (true) { // Validate the current block if (!(info.state == first_state)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!(info.perm == first_perm)) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!((info.attribute | static_cast<MemoryAttribute>(ignore_attr)) == (first_attr | static_cast<MemoryAttribute>(ignore_attr)))) { - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } // Validate against the provided masks diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp deleted file mode 100644 index 8f6c944d1..000000000 --- a/src/core/hle/kernel/mutex.cpp +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <utility> -#include <vector> - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/mutex.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/result.h" -#include "core/memory.h" - -namespace Kernel { - -/// Returns the number of threads that are waiting for a mutex, and the highest priority one among -/// those. -static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThread( - const std::shared_ptr<Thread>& current_thread, VAddr mutex_addr) { - - std::shared_ptr<Thread> highest_priority_thread; - u32 num_waiters = 0; - - for (const auto& thread : current_thread->GetMutexWaitingThreads()) { - if (thread->GetMutexWaitAddress() != mutex_addr) - continue; - - ++num_waiters; - if (highest_priority_thread == nullptr || - thread->GetPriority() < highest_priority_thread->GetPriority()) { - highest_priority_thread = thread; - } - } - - return {highest_priority_thread, num_waiters}; -} - -/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. -static void TransferMutexOwnership(VAddr mutex_addr, std::shared_ptr<Thread> current_thread, - std::shared_ptr<Thread> new_owner) { - current_thread->RemoveMutexWaiter(new_owner); - const auto threads = current_thread->GetMutexWaitingThreads(); - for (const auto& thread : threads) { - if (thread->GetMutexWaitAddress() != mutex_addr) - continue; - - ASSERT(thread->GetLockOwner() == current_thread.get()); - current_thread->RemoveMutexWaiter(thread); - if (new_owner != thread) - new_owner->AddMutexWaiter(thread); - } -} - -Mutex::Mutex(Core::System& system) : system{system} {} -Mutex::~Mutex() = default; - -ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, - Handle requesting_thread_handle) { - // The mutex address must be 4-byte aligned - if ((address % sizeof(u32)) != 0) { - LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address); - return ERR_INVALID_ADDRESS; - } - - auto& kernel = system.Kernel(); - std::shared_ptr<Thread> current_thread = - SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); - { - SchedulerLock lock(kernel); - // The mutex address must be 4-byte aligned - if ((address % sizeof(u32)) != 0) { - return ERR_INVALID_ADDRESS; - } - - const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle); - std::shared_ptr<Thread> requesting_thread = - handle_table.Get<Thread>(requesting_thread_handle); - - // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of - // another thread. - ASSERT(requesting_thread == current_thread); - - current_thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS); - - const u32 addr_value = system.Memory().Read32(address); - - // If the mutex isn't being held, just return success. - if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) { - return RESULT_SUCCESS; - } - - if (holding_thread == nullptr) { - return ERR_INVALID_HANDLE; - } - - // Wait until the mutex is released - current_thread->SetMutexWaitAddress(address); - current_thread->SetWaitHandle(requesting_thread_handle); - - current_thread->SetStatus(ThreadStatus::WaitMutex); - - // Update the lock holder thread's priority to prevent priority inversion. - holding_thread->AddMutexWaiter(current_thread); - } - - { - SchedulerLock lock(kernel); - auto* owner = current_thread->GetLockOwner(); - if (owner != nullptr) { - owner->RemoveMutexWaiter(current_thread); - } - } - return current_thread->GetSignalingResult(); -} - -std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thread> owner, - VAddr address) { - // The mutex address must be 4-byte aligned - if ((address % sizeof(u32)) != 0) { - LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address); - return {ERR_INVALID_ADDRESS, nullptr}; - } - - auto [new_owner, num_waiters] = GetHighestPriorityMutexWaitingThread(owner, address); - if (new_owner == nullptr) { - system.Memory().Write32(address, 0); - return {RESULT_SUCCESS, nullptr}; - } - // Transfer the ownership of the mutex from the previous owner to the new one. - TransferMutexOwnership(address, owner, new_owner); - u32 mutex_value = new_owner->GetWaitHandle(); - if (num_waiters >= 2) { - // Notify the guest that there are still some threads waiting for the mutex - mutex_value |= Mutex::MutexHasWaitersFlag; - } - new_owner->SetSynchronizationResults(nullptr, RESULT_SUCCESS); - new_owner->SetLockOwner(nullptr); - new_owner->ResumeFromWait(); - - system.Memory().Write32(address, mutex_value); - return {RESULT_SUCCESS, new_owner}; -} - -ResultCode Mutex::Release(VAddr address) { - auto& kernel = system.Kernel(); - SchedulerLock lock(kernel); - - std::shared_ptr<Thread> current_thread = - SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); - - auto [result, new_owner] = Unlock(current_thread, address); - - if (result != RESULT_SUCCESS && new_owner != nullptr) { - new_owner->SetSynchronizationResults(nullptr, result); - } - - return result; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h deleted file mode 100644 index 3b81dc3df..000000000 --- a/src/core/hle/kernel/mutex.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -union ResultCode; - -namespace Core { -class System; -} - -namespace Kernel { - -class Mutex final { -public: - explicit Mutex(Core::System& system); - ~Mutex(); - - /// Flag that indicates that a mutex still has threads waiting for it. - static constexpr u32 MutexHasWaitersFlag = 0x40000000; - /// Mask of the bits in a mutex address value that contain the mutex owner. - static constexpr u32 MutexOwnerMask = 0xBFFFFFFF; - - /// Attempts to acquire a mutex at the specified address. - ResultCode TryAcquire(VAddr address, Handle holding_thread_handle, - Handle requesting_thread_handle); - - /// Unlocks a mutex for owner at address - std::pair<ResultCode, std::shared_ptr<Thread>> Unlock(std::shared_ptr<Thread> owner, - VAddr address); - - /// Releases the mutex at the specified address. - ResultCode Release(VAddr address); - -private: - Core::System& system; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index 2c571792b..d7f40c403 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -8,7 +8,10 @@ namespace Kernel { -Object::Object(KernelCore& kernel) : kernel{kernel}, object_id{kernel.CreateNewObjectID()} {} +Object::Object(KernelCore& kernel_) + : kernel{kernel_}, object_id{kernel_.CreateNewObjectID()}, name{"[UNKNOWN KERNEL OBJECT]"} {} +Object::Object(KernelCore& kernel_, std::string&& name_) + : kernel{kernel_}, object_id{kernel_.CreateNewObjectID()}, name{std::move(name_)} {} Object::~Object() = default; bool Object::IsWaitable() const { @@ -21,6 +24,7 @@ bool Object::IsWaitable() const { return true; case HandleType::Unknown: + case HandleType::Event: case HandleType::WritableEvent: case HandleType::SharedMemory: case HandleType::TransferMemory: diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h index e3391e2af..501e58b33 100644 --- a/src/core/hle/kernel/object.h +++ b/src/core/hle/kernel/object.h @@ -18,6 +18,7 @@ using Handle = u32; enum class HandleType : u32 { Unknown, + Event, WritableEvent, ReadableEvent, SharedMemory, @@ -34,7 +35,8 @@ enum class HandleType : u32 { class Object : NonCopyable, public std::enable_shared_from_this<Object> { public: - explicit Object(KernelCore& kernel); + explicit Object(KernelCore& kernel_); + explicit Object(KernelCore& kernel_, std::string&& name_); virtual ~Object(); /// Returns a unique identifier for the object. For debugging purposes only. @@ -46,22 +48,30 @@ public: return "[BAD KERNEL OBJECT TYPE]"; } virtual std::string GetName() const { - return "[UNKNOWN KERNEL OBJECT]"; + return name; } virtual HandleType GetHandleType() const = 0; + void Close() { + // TODO(bunnei): This is a placeholder to decrement the reference count, which we will use + // when we implement KAutoObject instead of using shared_ptr. + } + /** * Check if a thread can wait on the object * @return True if a thread can wait on the object, otherwise false */ bool IsWaitable() const; + virtual void Finalize() = 0; + protected: /// The kernel instance this object was created under. KernelCore& kernel; private: std::atomic<u32> object_id{0}; + std::string name; }; template <typename T> diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index c6bbdb080..7fea45f96 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -2,54 +2,60 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/assert.h" -#include "common/logging/log.h" #include "common/spin_lock.h" -#include "core/arm/arm_interface.h" -#ifdef ARCHITECTURE_x86_64 +#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" -#endif -#include "core/arm/cpu_interrupt_handler.h" -#include "core/arm/exclusive_monitor.h" -#include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" namespace Kernel { -PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler, - Core::CPUInterruptHandler& interrupt_handler) - : interrupt_handler{interrupt_handler}, core_index{id}, scheduler{scheduler} { - - guard = std::make_unique<Common::SpinLock>(); -} +PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, + Kernel::KScheduler& scheduler, Core::CPUInterrupts& interrupts) + : core_index{core_index}, system{system}, scheduler{scheduler}, + interrupts{interrupts}, guard{std::make_unique<Common::SpinLock>()} {} PhysicalCore::~PhysicalCore() = default; -void PhysicalCore::Idle() { - interrupt_handler.AwaitInterrupt(); +void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) { +#ifdef ARCHITECTURE_x86_64 + auto& kernel = system.Kernel(); + if (is_64_bit) { + arm_interface = std::make_unique<Core::ARM_Dynarmic_64>( + system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); + } else { + arm_interface = std::make_unique<Core::ARM_Dynarmic_32>( + system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); + } +#else +#error Platform not supported yet. +#endif } -void PhysicalCore::Shutdown() { - scheduler.Shutdown(); +void PhysicalCore::Run() { + arm_interface->Run(); +} + +void PhysicalCore::Idle() { + interrupts[core_index].AwaitInterrupt(); } bool PhysicalCore::IsInterrupted() const { - return interrupt_handler.IsInterrupted(); + return interrupts[core_index].IsInterrupted(); } void PhysicalCore::Interrupt() { guard->lock(); - interrupt_handler.SetInterrupt(true); + interrupts[core_index].SetInterrupt(true); guard->unlock(); } void PhysicalCore::ClearInterrupt() { guard->lock(); - interrupt_handler.SetInterrupt(false); + interrupts[core_index].SetInterrupt(false); guard->unlock(); } diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index d7a7a951c..f2b0911aa 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -4,19 +4,21 @@ #pragma once +#include <array> #include <cstddef> #include <memory> +#include "core/arm/arm_interface.h" + namespace Common { class SpinLock; } namespace Kernel { -class Scheduler; +class KScheduler; } // namespace Kernel namespace Core { -class ARM_Interface; class CPUInterruptHandler; class ExclusiveMonitor; class System; @@ -26,17 +28,24 @@ namespace Kernel { class PhysicalCore { public: - PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler, - Core::CPUInterruptHandler& interrupt_handler); + PhysicalCore(std::size_t core_index, Core::System& system, Kernel::KScheduler& scheduler, + Core::CPUInterrupts& interrupts); ~PhysicalCore(); PhysicalCore(const PhysicalCore&) = delete; PhysicalCore& operator=(const PhysicalCore&) = delete; PhysicalCore(PhysicalCore&&) = default; - PhysicalCore& operator=(PhysicalCore&&) = default; + PhysicalCore& operator=(PhysicalCore&&) = delete; + + /// Initialize the core for the specified parameters. + void Initialize(bool is_64_bit); + + /// Execute current jit state + void Run(); void Idle(); + /// Interrupt this physical core. void Interrupt(); @@ -46,8 +55,17 @@ public: /// Check if this core is interrupted bool IsInterrupted() const; - // Shutdown this physical core. - void Shutdown(); + bool IsInitialized() const { + return arm_interface != nullptr; + } + + Core::ARM_Interface& ArmInterface() { + return *arm_interface; + } + + const Core::ARM_Interface& ArmInterface() const { + return *arm_interface; + } bool IsMainCore() const { return core_index == 0; @@ -61,19 +79,21 @@ public: return core_index; } - Kernel::Scheduler& Scheduler() { + Kernel::KScheduler& Scheduler() { return scheduler; } - const Kernel::Scheduler& Scheduler() const { + const Kernel::KScheduler& Scheduler() const { return scheduler; } private: - Core::CPUInterruptHandler& interrupt_handler; - std::size_t core_index; - Kernel::Scheduler& scheduler; + const std::size_t core_index; + Core::System& system; + Kernel::KScheduler& scheduler; + Core::CPUInterrupts& interrupts; std::unique_ptr<Common::SpinLock> guard; + std::unique_ptr<Core::ARM_Interface> arm_interface; }; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ff9d9248b..47b3ac57b 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <bitset> +#include <ctime> #include <memory> #include <random> #include "common/alignment.h" @@ -13,15 +14,16 @@ #include "core/device_memory.h" #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/code_set.h" -#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_block_manager.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/slab_heap.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/lock.h" #include "core/memory.h" #include "core/settings.h" @@ -37,11 +39,11 @@ namespace { */ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, VAddr stack_top) { const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart(); - ThreadType type = THREADTYPE_USER; - auto thread_res = Thread::Create(system, type, "main", entry_point, priority, 0, - owner_process.GetIdealCore(), stack_top, &owner_process); + ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::Threads, 1)); + auto thread_res = KThread::Create(system, ThreadType::User, "main", entry_point, priority, 0, + owner_process.GetIdealCoreId(), stack_top, &owner_process); - std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap(); + std::shared_ptr<KThread> thread = std::move(thread_res).Unwrap(); // Register 1 must be a handle to the main thread const Handle thread_handle = owner_process.GetHandleTable().Create(thread).Unwrap(); @@ -53,8 +55,8 @@ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, auto& kernel = system.Kernel(); // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires { - SchedulerLock lock{kernel}; - thread->SetStatus(ThreadStatus::Ready); + KScopedSchedulerLock lock{kernel}; + thread->SetState(ThreadState::Runnable); } } } // Anonymous namespace @@ -116,14 +118,17 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name, std::shared_ptr<Process> process = std::make_shared<Process>(system); process->name = std::move(name); - process->resource_limit = ResourceLimit::Create(kernel); + + // TODO: This is inaccurate + // The process should hold a reference to the kernel-wide resource limit. + process->resource_limit = std::make_shared<KResourceLimit>(kernel, system); process->status = ProcessStatus::Created; process->program_id = 0; process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); - std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(0)); + std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))); std::uniform_int_distribution<u64> distribution; std::generate(process->random_entropy.begin(), process->random_entropy.end(), [&] { return distribution(rng); }); @@ -132,12 +137,32 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name, return process; } -std::shared_ptr<ResourceLimit> Process::GetResourceLimit() const { +std::shared_ptr<KResourceLimit> Process::GetResourceLimit() const { return resource_limit; } +void Process::IncrementThreadCount() { + ASSERT(num_threads >= 0); + num_created_threads++; + + if (const auto count = ++num_threads; count > peak_num_threads) { + peak_num_threads = count; + } +} + +void Process::DecrementThreadCount() { + ASSERT(num_threads > 0); + + if (const auto count = --num_threads; count == 0) { + UNIMPLEMENTED_MSG("Process termination is not implemented!"); + } +} + u64 Process::GetTotalPhysicalMemoryAvailable() const { - const u64 capacity{resource_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory) + + // TODO: This is expected to always return the application memory pool size after accurately + // reserving kernel resources. The current workaround uses a process-local resource limit of + // application memory pool size, which is inaccurate. + const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) + page_table->GetTotalHeapSize() + GetSystemResourceSize() + image_size + main_thread_stack_size}; @@ -161,68 +186,79 @@ u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const { return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage(); } -void Process::InsertConditionVariableThread(std::shared_ptr<Thread> thread) { - VAddr cond_var_addr = thread->GetCondVarWaitAddress(); - std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr]; - auto it = thread_list.begin(); - while (it != thread_list.end()) { - const std::shared_ptr<Thread> current_thread = *it; - if (current_thread->GetPriority() > thread->GetPriority()) { - thread_list.insert(it, thread); - return; +bool Process::ReleaseUserException(KThread* thread) { + KScopedSchedulerLock sl{kernel}; + + if (exception_thread == thread) { + exception_thread = nullptr; + + // Remove waiter thread. + s32 num_waiters{}; + KThread* next = thread->RemoveWaiterByKey( + std::addressof(num_waiters), + reinterpret_cast<uintptr_t>(std::addressof(exception_thread))); + if (next != nullptr) { + if (next->GetState() == ThreadState::Waiting) { + next->SetState(ThreadState::Runnable); + } else { + KScheduler::SetSchedulerUpdateNeeded(kernel); + } } - ++it; + + return true; + } else { + return false; } - thread_list.push_back(thread); } -void Process::RemoveConditionVariableThread(std::shared_ptr<Thread> thread) { - VAddr cond_var_addr = thread->GetCondVarWaitAddress(); - std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr]; - auto it = thread_list.begin(); - while (it != thread_list.end()) { - const std::shared_ptr<Thread> current_thread = *it; - if (current_thread.get() == thread.get()) { - thread_list.erase(it); - return; - } - ++it; - } +void Process::PinCurrentThread() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Get the current thread. + const s32 core_id = GetCurrentCoreId(kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); + + // Pin it. + PinThread(core_id, cur_thread); + cur_thread->Pin(); + + // An update is needed. + KScheduler::SetSchedulerUpdateNeeded(kernel); } -std::vector<std::shared_ptr<Thread>> Process::GetConditionVariableThreads( - const VAddr cond_var_addr) { - std::vector<std::shared_ptr<Thread>> result{}; - std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr]; - auto it = thread_list.begin(); - while (it != thread_list.end()) { - std::shared_ptr<Thread> current_thread = *it; - result.push_back(current_thread); - ++it; - } - return result; +void Process::UnpinCurrentThread() { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Get the current thread. + const s32 core_id = GetCurrentCoreId(kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); + + // Unpin it. + cur_thread->Unpin(); + UnpinThread(core_id, cur_thread); + + // An update is needed. + KScheduler::SetSchedulerUpdateNeeded(kernel); } -void Process::RegisterThread(const Thread* thread) { +void Process::RegisterThread(const KThread* thread) { thread_list.push_back(thread); } -void Process::UnregisterThread(const Thread* thread) { +void Process::UnregisterThread(const KThread* thread) { thread_list.remove(thread); } -ResultCode Process::ClearSignalState() { - SchedulerLock lock(system.Kernel()); - if (status == ProcessStatus::Exited) { - LOG_ERROR(Kernel, "called on a terminated process instance."); - return ERR_INVALID_STATE; - } +ResultCode Process::Reset() { + // Lock the process and the scheduler. + KScopedLightLock lk(state_lock); + KScopedSchedulerLock sl{kernel}; - if (!is_signaled) { - LOG_ERROR(Kernel, "called on a process instance that isn't signaled."); - return ERR_INVALID_STATE; - } + // Validate that we're in a state that we can reset. + R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState); + R_UNLESS(is_signaled, ResultInvalidState); + // Clear signaled. is_signaled = false; return RESULT_SUCCESS; } @@ -235,6 +271,17 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, system_resource_size = metadata.GetSystemResourceSize(); image_size = code_size; + // Set initial resource limits + resource_limit->SetLimitValue( + LimitableResource::PhysicalMemory, + kernel.MemoryManager().GetSize(Memory::MemoryManager::Pool::Application)); + KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory, + code_size + system_resource_size); + if (!memory_reservation.Succeeded()) { + LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes", + code_size + system_resource_size); + return ResultResourceLimitedExceeded; + } // Initialize proces address space if (const ResultCode result{ page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false, 0x8000000, @@ -276,24 +323,22 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, UNREACHABLE(); } - // Set initial resource limits - resource_limit->SetLimitValue( - ResourceType::PhysicalMemory, - kernel.MemoryManager().GetSize(Memory::MemoryManager::Pool::Application)); - resource_limit->SetLimitValue(ResourceType::Threads, 608); - resource_limit->SetLimitValue(ResourceType::Events, 700); - resource_limit->SetLimitValue(ResourceType::TransferMemory, 128); - resource_limit->SetLimitValue(ResourceType::Sessions, 894); - ASSERT(resource_limit->Reserve(ResourceType::PhysicalMemory, code_size)); + resource_limit->SetLimitValue(LimitableResource::Threads, 608); + resource_limit->SetLimitValue(LimitableResource::Events, 700); + resource_limit->SetLimitValue(LimitableResource::TransferMemory, 128); + resource_limit->SetLimitValue(LimitableResource::Sessions, 894); // Create TLS region tls_region_address = CreateTLSRegion(); + memory_reservation.Commit(); return handle_table.SetSize(capabilities.GetHandleTableSize()); } void Process::Run(s32 main_thread_priority, u64 stack_size) { AllocateMainThreadStack(stack_size); + resource_limit->Reserve(LimitableResource::Threads, 1); + resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size); const std::size_t heap_capacity{memory_usage_capacity - main_thread_stack_size - image_size}; ASSERT(!page_table->SetHeapCapacity(heap_capacity).IsError()); @@ -301,34 +346,37 @@ void Process::Run(s32 main_thread_priority, u64 stack_size) { ChangeStatus(ProcessStatus::Running); SetupMainThread(system, *this, main_thread_priority, main_thread_stack_top); - resource_limit->Reserve(ResourceType::Threads, 1); - resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size); } void Process::PrepareForTermination() { ChangeStatus(ProcessStatus::Exiting); - const auto stop_threads = [this](const std::vector<std::shared_ptr<Thread>>& thread_list) { + const auto stop_threads = [this](const std::vector<std::shared_ptr<KThread>>& thread_list) { for (auto& thread : thread_list) { if (thread->GetOwnerProcess() != this) continue; - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) + if (thread.get() == kernel.CurrentScheduler()->GetCurrentThread()) continue; // TODO(Subv): When are the other running/ready threads terminated? - ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynch, + ASSERT_MSG(thread->GetState() == ThreadState::Waiting, "Exiting processes with non-waiting threads is currently unimplemented"); - thread->Stop(); + thread->Exit(); } }; - stop_threads(system.GlobalScheduler().GetThreadList()); + stop_threads(system.GlobalSchedulerContext().GetThreadList()); FreeTLSRegion(tls_region_address); tls_region_address = 0; + if (resource_limit) { + resource_limit->Release(LimitableResource::PhysicalMemory, + main_thread_stack_size + image_size); + } + ChangeStatus(ProcessStatus::Exited); } @@ -346,7 +394,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) { } VAddr Process::CreateTLSRegion() { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)}; tls_page_iter != tls_pages.cend()) { return *tls_page_iter->ReserveSlot(); @@ -377,7 +425,7 @@ VAddr Process::CreateTLSRegion() { } void Process::FreeTLSRegion(VAddr tls_address) { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE); auto iter = std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) { @@ -405,21 +453,18 @@ void Process::LoadModule(CodeSet code_set, VAddr base_addr) { ReprotectSegment(code_set.DataSegment(), Memory::MemoryPermission::ReadAndWrite); } +bool Process::IsSignaled() const { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + return is_signaled; +} + Process::Process(Core::System& system) - : SynchronizationObject{system.Kernel()}, page_table{std::make_unique<Memory::PageTable>( - system)}, - handle_table{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {} + : KSynchronizationObject{system.Kernel()}, + page_table{std::make_unique<Memory::PageTable>(system)}, handle_table{system.Kernel()}, + address_arbiter{system}, condition_var{system}, state_lock{system.Kernel()}, system{system} {} Process::~Process() = default; -void Process::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "Object unavailable!"); -} - -bool Process::ShouldWait(const Thread* thread) const { - return !is_signaled; -} - void Process::ChangeStatus(ProcessStatus new_status) { if (status == new_status) { return; @@ -427,7 +472,7 @@ void Process::ChangeStatus(ProcessStatus new_status) { status = new_status; is_signaled = true; - Signal(); + NotifyAvailable(); } ResultCode Process::AllocateMainThreadStack(std::size_t stack_size) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index f45cb5674..320b0f347 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -11,11 +11,11 @@ #include <unordered_map> #include <vector> #include "common/common_types.h" -#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/k_address_arbiter.h" +#include "core/hle/kernel/k_condition_variable.h" +#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/process_capability.h" -#include "core/hle/kernel/synchronization_object.h" #include "core/hle/result.h" namespace Core { @@ -29,8 +29,8 @@ class ProgramMetadata; namespace Kernel { class KernelCore; -class ResourceLimit; -class Thread; +class KResourceLimit; +class KThread; class TLSPage; struct CodeSet; @@ -63,7 +63,7 @@ enum class ProcessStatus { DebugBreak, }; -class Process final : public SynchronizationObject { +class Process final : public KSynchronizationObject { public: explicit Process(Core::System& system); ~Process() override; @@ -123,24 +123,30 @@ public: return handle_table; } - /// Gets a reference to the process' address arbiter. - AddressArbiter& GetAddressArbiter() { - return address_arbiter; + ResultCode SignalToAddress(VAddr address) { + return condition_var.SignalToAddress(address); } - /// Gets a const reference to the process' address arbiter. - const AddressArbiter& GetAddressArbiter() const { - return address_arbiter; + ResultCode WaitForAddress(Handle handle, VAddr address, u32 tag) { + return condition_var.WaitForAddress(handle, address, tag); } - /// Gets a reference to the process' mutex lock. - Mutex& GetMutex() { - return mutex; + void SignalConditionVariable(u64 cv_key, int32_t count) { + return condition_var.Signal(cv_key, count); } - /// Gets a const reference to the process' mutex lock - const Mutex& GetMutex() const { - return mutex; + ResultCode WaitConditionVariable(VAddr address, u64 cv_key, u32 tag, s64 ns) { + return condition_var.Wait(address, cv_key, tag, ns); + } + + ResultCode SignalAddressArbiter(VAddr address, Svc::SignalType signal_type, s32 value, + s32 count) { + return address_arbiter.SignalToAddress(address, signal_type, value, count); + } + + ResultCode WaitAddressArbiter(VAddr address, Svc::ArbitrationType arb_type, s32 value, + s64 timeout) { + return address_arbiter.WaitForAddress(address, arb_type, value, timeout); } /// Gets the address to the process' dedicated TLS region. @@ -164,13 +170,18 @@ public: } /// Gets the resource limit descriptor for this process - std::shared_ptr<ResourceLimit> GetResourceLimit() const; + std::shared_ptr<KResourceLimit> GetResourceLimit() const; /// Gets the ideal CPU core ID for this process - u8 GetIdealCore() const { + u8 GetIdealCoreId() const { return ideal_core; } + /// Checks if the specified thread priority is valid. + bool CheckThreadPriority(s32 prio) const { + return ((1ULL << prio) & GetPriorityMask()) != 0; + } + /// Gets the bitmask of allowed cores that this process' threads can run on. u64 GetCoreMask() const { return capabilities.GetCoreMask(); @@ -206,6 +217,14 @@ public: return is_64bit_process; } + [[nodiscard]] bool IsSuspended() const { + return is_suspended; + } + + void SetSuspended(bool suspended) { + is_suspended = suspended; + } + /// Gets the total running time of the process instance in ticks. u64 GetCPUTimeTicks() const { return total_process_running_time_ticks; @@ -216,6 +235,43 @@ public: total_process_running_time_ticks += ticks; } + /// Gets the process schedule count, used for thread yelding + s64 GetScheduledCount() const { + return schedule_count; + } + + /// Increments the process schedule count, used for thread yielding. + void IncrementScheduledCount() { + ++schedule_count; + } + + void IncrementThreadCount(); + void DecrementThreadCount(); + + void SetRunningThread(s32 core, KThread* thread, u64 idle_count) { + running_threads[core] = thread; + running_thread_idle_counts[core] = idle_count; + } + + void ClearRunningThread(KThread* thread) { + for (size_t i = 0; i < running_threads.size(); ++i) { + if (running_threads[i] == thread) { + running_threads[i] = nullptr; + } + } + } + + [[nodiscard]] KThread* GetRunningThread(s32 core) const { + return running_threads[core]; + } + + bool ReleaseUserException(KThread* thread); + + [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const { + ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + return pinned_threads[core_id]; + } + /// Gets 8 bytes of random data for svcGetInfo RandomEntropy u64 GetRandomEntropy(std::size_t index) const { return random_entropy.at(index); @@ -236,26 +292,17 @@ public: u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const; /// Gets the list of all threads created with this process as their owner. - const std::list<const Thread*>& GetThreadList() const { + const std::list<const KThread*>& GetThreadList() const { return thread_list; } - /// Insert a thread into the condition variable wait container - void InsertConditionVariableThread(std::shared_ptr<Thread> thread); - - /// Remove a thread from the condition variable wait container - void RemoveConditionVariableThread(std::shared_ptr<Thread> thread); - - /// Obtain all condition variable threads waiting for some address - std::vector<std::shared_ptr<Thread>> GetConditionVariableThreads(VAddr cond_var_addr); - /// Registers a thread as being created under this process, /// adding it to this process' thread list. - void RegisterThread(const Thread* thread); + void RegisterThread(const KThread* thread); /// Unregisters a thread from this process, removing it /// from this process' thread list. - void UnregisterThread(const Thread* thread); + void UnregisterThread(const KThread* thread); /// Clears the signaled state of the process if and only if it's signaled. /// @@ -265,7 +312,7 @@ public: /// @pre The process must be in a signaled state. If this is called on a /// process instance that is not signaled, ERR_INVALID_STATE will be /// returned. - ResultCode ClearSignalState(); + ResultCode Reset(); /** * Loads process-specifics configuration info with metadata provided @@ -294,6 +341,17 @@ public: void LoadModule(CodeSet code_set, VAddr base_addr); + bool IsSignaled() const override; + + void Finalize() override {} + + void PinCurrentThread(); + void UnpinCurrentThread(); + + KLightLock& GetStateLock() { + return state_lock; + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Thread-local storage management @@ -304,11 +362,19 @@ public: void FreeTLSRegion(VAddr tls_address); private: - /// Checks if the specified thread should wait until this process is available. - bool ShouldWait(const Thread* thread) const override; + void PinThread(s32 core_id, KThread* thread) { + ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + ASSERT(thread != nullptr); + ASSERT(pinned_threads[core_id] == nullptr); + pinned_threads[core_id] = thread; + } - /// Acquires/locks this process for the specified thread if it's available. - void Acquire(Thread* thread) override; + void UnpinThread(s32 core_id, KThread* thread) { + ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + ASSERT(thread != nullptr); + ASSERT(pinned_threads[core_id] == thread); + pinned_threads[core_id] = nullptr; + } /// Changes the process status. If the status is different /// from the current process status, then this will trigger @@ -336,7 +402,7 @@ private: u32 system_resource_size = 0; /// Resource limit descriptor for this process - std::shared_ptr<ResourceLimit> resource_limit; + std::shared_ptr<KResourceLimit> resource_limit; /// The ideal CPU core for this process, threads are scheduled on this core by default. u8 ideal_core = 0; @@ -363,12 +429,12 @@ private: HandleTable handle_table; /// Per-process address arbiter. - AddressArbiter address_arbiter; + KAddressArbiter address_arbiter; /// The per-process mutex lock instance used for handling various /// forms of services, such as lock arbitration, and condition /// variable related facilities. - Mutex mutex; + KConditionVariable condition_var; /// Address indicating the location of the process' dedicated TLS region. VAddr tls_region_address = 0; @@ -377,10 +443,7 @@ private: std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{}; /// List of threads that are running with this process as their owner. - std::list<const Thread*> thread_list; - - /// List of threads waiting for a condition variable - std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> cond_var_threads; + std::list<const KThread*> thread_list; /// Address of the top of the main thread's stack VAddr main_thread_stack_top{}; @@ -397,6 +460,24 @@ private: /// Name of this process std::string name; + /// Schedule count of this process + s64 schedule_count{}; + + bool is_signaled{}; + bool is_suspended{}; + + std::atomic<s32> num_created_threads{}; + std::atomic<u16> num_threads{}; + u16 peak_num_threads{}; + + std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{}; + std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{}; + std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{}; + + KThread* exception_thread{}; + + KLightLock state_lock; + /// System context Core::System& system; }; diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp index 63880f13d..7c567049e 100644 --- a/src/core/hle/kernel/process_capability.cpp +++ b/src/core/hle/kernel/process_capability.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <bit> + #include "common/bit_util.h" #include "common/logging/log.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process_capability.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel { namespace { @@ -60,7 +62,7 @@ constexpr CapabilityType GetCapabilityType(u32 value) { u32 GetFlagBitOffset(CapabilityType type) { const auto value = static_cast<u32>(type); - return static_cast<u32>(Common::BitSize<u32>() - Common::CountLeadingZeroes32(value)); + return static_cast<u32>(Common::BitSize<u32>() - static_cast<u32>(std::countl_zero(value))); } } // Anonymous namespace @@ -121,13 +123,13 @@ ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities, // If there's only one, then there's a problem. if (i >= num_capabilities) { LOG_ERROR(Kernel, "Invalid combination! i={}", i); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } const auto size_flags = capabilities[i]; if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) { LOG_ERROR(Kernel, "Invalid capability type! size_flags={}", size_flags); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } const auto result = HandleMapPhysicalFlags(descriptor, size_flags, page_table); @@ -157,7 +159,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s const auto type = GetCapabilityType(flag); if (type == CapabilityType::Unset) { - return ERR_INVALID_CAPABILITY_DESCRIPTOR; + return ResultInvalidCapabilityDescriptor; } // Bail early on ignorable entries, as one would expect, @@ -174,7 +176,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s LOG_ERROR(Kernel, "Attempted to initialize flags that may only be initialized once. set_flags={}", set_flags); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } set_flags |= set_flag; @@ -199,8 +201,8 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s break; } - LOG_ERROR(Kernel, "Invalid capability type! type={}", static_cast<u32>(type)); - return ERR_INVALID_CAPABILITY_DESCRIPTOR; + LOG_ERROR(Kernel, "Invalid capability type! type={}", type); + return ResultInvalidCapabilityDescriptor; } void ProcessCapabilities::Clear() { @@ -223,7 +225,7 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) { if (priority_mask != 0 || core_mask != 0) { LOG_ERROR(Kernel, "Core or priority mask are not zero! priority_mask={}, core_mask={}", priority_mask, core_mask); - return ERR_INVALID_CAPABILITY_DESCRIPTOR; + return ResultInvalidCapabilityDescriptor; } const u32 core_num_min = (flags >> 16) & 0xFF; @@ -231,7 +233,7 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) { if (core_num_min > core_num_max) { LOG_ERROR(Kernel, "Core min is greater than core max! core_num_min={}, core_num_max={}", core_num_min, core_num_max); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } const u32 priority_min = (flags >> 10) & 0x3F; @@ -240,13 +242,13 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) { LOG_ERROR(Kernel, "Priority min is greater than priority max! priority_min={}, priority_max={}", core_num_min, priority_max); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } // The switch only has 4 usable cores. if (core_num_max >= 4) { LOG_ERROR(Kernel, "Invalid max cores specified! core_num_max={}", core_num_max); - return ERR_INVALID_PROCESSOR_ID; + return ResultInvalidCoreId; } const auto make_mask = [](u64 min, u64 max) { @@ -267,7 +269,7 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) // If we've already set this svc before, bail. if ((set_svc_bits & svc_bit) != 0) { - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } set_svc_bits |= svc_bit; @@ -281,7 +283,7 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) if (svc_number >= svc_capabilities.size()) { LOG_ERROR(Kernel, "Process svc capability is out of range! svc_number={}", svc_number); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } svc_capabilities[svc_number] = true; @@ -319,7 +321,7 @@ ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) { if (interrupt >= interrupt_capabilities.size()) { LOG_ERROR(Kernel, "Process interrupt capability is out of range! svc_number={}", interrupt); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } interrupt_capabilities[interrupt] = true; @@ -332,7 +334,7 @@ ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) { const u32 reserved = flags >> 17; if (reserved != 0) { LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved); - return ERR_RESERVED_VALUE; + return ResultReservedValue; } program_type = static_cast<ProgramType>((flags >> 14) & 0b111); @@ -352,7 +354,7 @@ ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) { LOG_ERROR(Kernel, "Kernel version is non zero or flags are too small! major_version={}, flags={}", major_version, flags); - return ERR_INVALID_CAPABILITY_DESCRIPTOR; + return ResultInvalidCapabilityDescriptor; } kernel_version = flags; @@ -363,7 +365,7 @@ ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) { const u32 reserved = flags >> 26; if (reserved != 0) { LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved); - return ERR_RESERVED_VALUE; + return ResultReservedValue; } handle_table_size = static_cast<s32>((flags >> 16) & 0x3FF); @@ -374,7 +376,7 @@ ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) { const u32 reserved = flags >> 19; if (reserved != 0) { LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved); - return ERR_RESERVED_VALUE; + return ResultReservedValue; } is_debuggable = (flags & 0x20000) != 0; diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp deleted file mode 100644 index 6e286419e..000000000 --- a/src/core/hle/kernel/readable_event.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" - -namespace Kernel { - -ReadableEvent::ReadableEvent(KernelCore& kernel) : SynchronizationObject{kernel} {} -ReadableEvent::~ReadableEvent() = default; - -bool ReadableEvent::ShouldWait(const Thread* thread) const { - return !is_signaled; -} - -void ReadableEvent::Acquire(Thread* thread) { - ASSERT_MSG(IsSignaled(), "object unavailable!"); -} - -void ReadableEvent::Signal() { - if (is_signaled) { - return; - } - - is_signaled = true; - SynchronizationObject::Signal(); -} - -void ReadableEvent::Clear() { - is_signaled = false; -} - -ResultCode ReadableEvent::Reset() { - SchedulerLock lock(kernel); - if (!is_signaled) { - LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}", - GetObjectId(), GetTypeName(), GetName()); - return ERR_INVALID_STATE; - } - - Clear(); - - return RESULT_SUCCESS; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h deleted file mode 100644 index 3264dd066..000000000 --- a/src/core/hle/kernel/readable_event.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/synchronization_object.h" - -union ResultCode; - -namespace Kernel { - -class KernelCore; -class WritableEvent; - -class ReadableEvent final : public SynchronizationObject { - friend class WritableEvent; - -public: - ~ReadableEvent() override; - - std::string GetTypeName() const override { - return "ReadableEvent"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - - /// Unconditionally clears the readable event's state. - void Clear(); - - /// Clears the readable event's state if and only if it - /// has already been signaled. - /// - /// @pre The event must be in a signaled state. If this event - /// is in an unsignaled state and this function is called, - /// then ERR_INVALID_STATE will be returned. - ResultCode Reset(); - - void Signal() override; - -private: - explicit ReadableEvent(KernelCore& kernel); - - std::string name; ///< Name of event (optional) -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp deleted file mode 100644 index 212e442f4..000000000 --- a/src/core/hle/kernel/resource_limit.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/result.h" - -namespace Kernel { -namespace { -constexpr std::size_t ResourceTypeToIndex(ResourceType type) { - return static_cast<std::size_t>(type); -} -} // Anonymous namespace - -ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {} -ResourceLimit::~ResourceLimit() = default; - -bool ResourceLimit::Reserve(ResourceType resource, s64 amount) { - return Reserve(resource, amount, 10000000000); -} - -bool ResourceLimit::Reserve(ResourceType resource, s64 amount, u64 timeout) { - const std::size_t index{ResourceTypeToIndex(resource)}; - - s64 new_value = current[index] + amount; - if (new_value > limit[index] && available[index] + amount <= limit[index]) { - // TODO(bunnei): This is wrong for multicore, we should wait the calling thread for timeout - new_value = current[index] + amount; - } - - if (new_value <= limit[index]) { - current[index] = new_value; - return true; - } - return false; -} - -void ResourceLimit::Release(ResourceType resource, u64 amount) { - Release(resource, amount, amount); -} - -void ResourceLimit::Release(ResourceType resource, u64 used_amount, u64 available_amount) { - const std::size_t index{ResourceTypeToIndex(resource)}; - - current[index] -= used_amount; - available[index] -= available_amount; -} - -std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel) { - return std::make_shared<ResourceLimit>(kernel); -} - -s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { - return limit.at(ResourceTypeToIndex(resource)) - current.at(ResourceTypeToIndex(resource)); -} - -s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { - return limit.at(ResourceTypeToIndex(resource)); -} - -ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) { - const std::size_t index{ResourceTypeToIndex(resource)}; - if (current[index] <= value) { - limit[index] = value; - return RESULT_SUCCESS; - } else { - LOG_ERROR(Kernel, "Limit value is too large! resource={}, value={}, index={}", - static_cast<u32>(resource), value, index); - return ERR_INVALID_STATE; - } -} -} // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h deleted file mode 100644 index 936cc4d0f..000000000 --- a/src/core/hle/kernel/resource_limit.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <memory> - -#include "common/common_types.h" -#include "core/hle/kernel/object.h" - -union ResultCode; - -namespace Kernel { - -class KernelCore; - -enum class ResourceType : u32 { - PhysicalMemory, - Threads, - Events, - TransferMemory, - Sessions, - - // Used as a count, not an actual type. - ResourceTypeCount -}; - -constexpr bool IsValidResourceType(ResourceType type) { - return type < ResourceType::ResourceTypeCount; -} - -class ResourceLimit final : public Object { -public: - explicit ResourceLimit(KernelCore& kernel); - ~ResourceLimit() override; - - /// Creates a resource limit object. - static std::shared_ptr<ResourceLimit> Create(KernelCore& kernel); - - std::string GetTypeName() const override { - return "ResourceLimit"; - } - std::string GetName() const override { - return GetTypeName(); - } - - static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - bool Reserve(ResourceType resource, s64 amount); - bool Reserve(ResourceType resource, s64 amount, u64 timeout); - void Release(ResourceType resource, u64 amount); - void Release(ResourceType resource, u64 used_amount, u64 available_amount); - - /** - * Gets the current value for the specified resource. - * @param resource Requested resource type - * @returns The current value of the resource type - */ - s64 GetCurrentResourceValue(ResourceType resource) const; - - /** - * Gets the max value for the specified resource. - * @param resource Requested resource type - * @returns The max value of the resource type - */ - s64 GetMaxResourceValue(ResourceType resource) const; - - /** - * Sets the limit value for a given resource type. - * - * @param resource The resource type to apply the limit to. - * @param value The limit to apply to the given resource type. - * - * @return A result code indicating if setting the limit value - * was successful or not. - * - * @note The supplied limit value *must* be greater than or equal to - * the current resource value for the given resource type, - * otherwise ERR_INVALID_STATE will be returned. - */ - ResultCode SetLimitValue(ResourceType resource, s64 value); - -private: - // TODO(Subv): Increment resource limit current values in their respective Kernel::T::Create - // functions - // - // Currently we have no way of distinguishing if a Create was called by the running application, - // or by a service module. Approach this once we have separated the service modules into their - // own processes - - using ResourceArray = - std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>; - - ResourceArray limit{}; - ResourceArray current{}; - ResourceArray available{}; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp deleted file mode 100644 index a4b234424..000000000 --- a/src/core/hle/kernel/scheduler.cpp +++ /dev/null @@ -1,845 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. -// -// SelectThreads, Yield functions originally by TuxSH. -// licensed under GPLv2 or later under exception provided by the author. - -#include <algorithm> -#include <mutex> -#include <set> -#include <unordered_set> -#include <utility> - -#include "common/assert.h" -#include "common/bit_util.h" -#include "common/fiber.h" -#include "common/logging/log.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/cpu_manager.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/time_manager.h" - -namespace Kernel { - -GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {} - -GlobalScheduler::~GlobalScheduler() = default; - -void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) { - std::scoped_lock lock{global_list_guard}; - thread_list.push_back(std::move(thread)); -} - -void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) { - std::scoped_lock lock{global_list_guard}; - thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), - thread_list.end()); -} - -u32 GlobalScheduler::SelectThreads() { - ASSERT(is_locked); - const auto update_thread = [](Thread* thread, Scheduler& sched) { - std::scoped_lock lock{sched.guard}; - if (thread != sched.selected_thread_set.get()) { - if (thread == nullptr) { - ++sched.idle_selection_count; - } - sched.selected_thread_set = SharedFrom(thread); - } - const bool reschedule_pending = - sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread); - sched.is_context_switch_pending = reschedule_pending; - std::atomic_thread_fence(std::memory_order_seq_cst); - return reschedule_pending; - }; - if (!is_reselection_pending.load()) { - return 0; - } - std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{}; - - u32 idle_cores{}; - - // Step 1: Get top thread in schedule queue. - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - Thread* top_thread = - scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); - if (top_thread != nullptr) { - // TODO(Blinkhawk): Implement Thread Pinning - } else { - idle_cores |= (1ul << core); - } - top_threads[core] = top_thread; - } - - while (idle_cores != 0) { - u32 core_id = Common::CountTrailingZeroes32(idle_cores); - - if (!suggested_queue[core_id].empty()) { - std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{}; - std::size_t num_candidates = 0; - auto iter = suggested_queue[core_id].begin(); - Thread* suggested = nullptr; - // Step 2: Try selecting a suggested thread. - while (iter != suggested_queue[core_id].end()) { - suggested = *iter; - iter++; - s32 suggested_core_id = suggested->GetProcessorID(); - Thread* top_thread = - suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr; - if (top_thread != suggested) { - if (top_thread != nullptr && - top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) { - suggested = nullptr; - break; - // There's a too high thread to do core migration, cancel - } - TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested); - break; - } - suggested = nullptr; - migration_candidates[num_candidates++] = suggested_core_id; - } - // Step 3: Select a suggested thread from another core - if (suggested == nullptr) { - for (std::size_t i = 0; i < num_candidates; i++) { - s32 candidate_core = migration_candidates[i]; - suggested = top_threads[candidate_core]; - auto it = scheduled_queue[candidate_core].begin(); - it++; - Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr; - if (next != nullptr) { - TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), - suggested); - top_threads[candidate_core] = next; - break; - } else { - suggested = nullptr; - } - } - } - top_threads[core_id] = suggested; - } - - idle_cores &= ~(1ul << core_id); - } - u32 cores_needing_context_switch{}; - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - Scheduler& sched = kernel.Scheduler(core); - ASSERT(top_threads[core] == nullptr || - static_cast<u32>(top_threads[core]->GetProcessorID()) == core); - if (update_thread(top_threads[core], sched)) { - cores_needing_context_switch |= (1ul << core); - } - } - return cores_needing_context_switch; -} - -bool GlobalScheduler::YieldThread(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should use critical section, etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); - const u32 priority = yielding_thread->GetPriority(); - - // Yield the thread - Reschedule(priority, core_id, yielding_thread); - const Thread* const winner = scheduled_queue[core_id].front(); - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, - // etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); - const u32 priority = yielding_thread->GetPriority(); - - // Yield the thread - Reschedule(priority, core_id, yielding_thread); - - std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads; - for (std::size_t i = 0; i < current_threads.size(); i++) { - current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); - } - - Thread* next_thread = scheduled_queue[core_id].front(priority); - Thread* winner = nullptr; - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (source_core >= 0) { - if (current_threads[source_core] != nullptr) { - if (thread == current_threads[source_core] || - current_threads[source_core]->GetPriority() < min_regular_priority) { - continue; - } - } - } - if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() || - next_thread->GetPriority() < thread->GetPriority()) { - if (thread->GetPriority() <= priority) { - winner = thread; - break; - } - } - } - - if (winner != nullptr) { - if (winner != yielding_thread) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - } - } else { - winner = next_thread; - } - - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, - // etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - Thread* winner = nullptr; - const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); - - // Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead - TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread); - - // If the core is idle, perform load balancing, excluding the threads that have just used this - // function... - if (scheduled_queue[core_id].empty()) { - // Here, "current_threads" is calculated after the ""yield"", unlike yield -1 - std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads; - for (std::size_t i = 0; i < current_threads.size(); i++) { - current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); - } - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (source_core < 0 || thread == current_threads[source_core]) { - continue; - } - if (current_threads[source_core] == nullptr || - current_threads[source_core]->GetPriority() >= min_regular_priority) { - winner = thread; - } - break; - } - if (winner != nullptr) { - if (winner != yielding_thread) { - TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner); - } - } else { - winner = yielding_thread; - } - } else { - winner = scheduled_queue[core_id].front(); - } - - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -void GlobalScheduler::PreemptThreads() { - ASSERT(is_locked); - for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { - const u32 priority = preemption_priorities[core_id]; - - if (scheduled_queue[core_id].size(priority) > 0) { - if (scheduled_queue[core_id].size(priority) > 1) { - scheduled_queue[core_id].front(priority)->IncrementYieldCount(); - } - scheduled_queue[core_id].yield(priority); - if (scheduled_queue[core_id].size(priority) > 1) { - scheduled_queue[core_id].front(priority)->IncrementYieldCount(); - } - } - - Thread* current_thread = - scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front(); - Thread* winner = nullptr; - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (thread->GetPriority() != priority) { - continue; - } - if (source_core >= 0) { - Thread* next_thread = scheduled_queue[source_core].empty() - ? nullptr - : scheduled_queue[source_core].front(); - if (next_thread != nullptr && next_thread->GetPriority() < 2) { - break; - } - if (next_thread == thread) { - continue; - } - } - if (current_thread != nullptr && - current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) { - winner = thread; - break; - } - } - - if (winner != nullptr) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - current_thread = - winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread; - } - - if (current_thread != nullptr && current_thread->GetPriority() > priority) { - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (thread->GetPriority() < priority) { - continue; - } - if (source_core >= 0) { - Thread* next_thread = scheduled_queue[source_core].empty() - ? nullptr - : scheduled_queue[source_core].front(); - if (next_thread != nullptr && next_thread->GetPriority() < 2) { - break; - } - if (next_thread == thread) { - continue; - } - } - if (current_thread != nullptr && - current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) { - winner = thread; - break; - } - } - - if (winner != nullptr) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - current_thread = winner; - } - } - - is_reselection_pending.store(true, std::memory_order_release); - } -} - -void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule, - Core::EmuThreadHandle global_thread) { - u32 current_core = global_thread.host_handle; - bool must_context_switch = global_thread.guest_handle != InvalidHandle && - (current_core < Core::Hardware::NUM_CPU_CORES); - while (cores_pending_reschedule != 0) { - u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule); - ASSERT(core < Core::Hardware::NUM_CPU_CORES); - if (!must_context_switch || core != current_core) { - auto& phys_core = kernel.PhysicalCore(core); - phys_core.Interrupt(); - } else { - must_context_switch = true; - } - cores_pending_reschedule &= ~(1ul << core); - } - if (must_context_switch) { - auto& core_scheduler = kernel.CurrentScheduler(); - kernel.ExitSVCProfile(); - core_scheduler.TryDoContextSwitch(); - kernel.EnterSVCProfile(); - } -} - -void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - suggested_queue[core].add(thread, priority); -} - -void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - suggested_queue[core].remove(thread, priority); -} - -void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); - scheduled_queue[core].add(thread, priority); -} - -void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); - scheduled_queue[core].add(thread, priority, false); -} - -void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - scheduled_queue[core].remove(thread, priority); - scheduled_queue[core].add(thread, priority); -} - -void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - scheduled_queue[core].remove(thread, priority); -} - -void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) { - ASSERT(is_locked); - const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT; - const s32 source_core = thread->GetProcessorID(); - if (source_core == destination_core || !schedulable) { - return; - } - thread->SetProcessorID(destination_core); - if (source_core >= 0) { - Unschedule(priority, static_cast<u32>(source_core), thread); - } - if (destination_core >= 0) { - Unsuggest(priority, static_cast<u32>(destination_core), thread); - Schedule(priority, static_cast<u32>(destination_core), thread); - } - if (source_core >= 0) { - Suggest(priority, static_cast<u32>(source_core), thread); - } -} - -bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread, - const Thread* winner) { - if (current_thread == winner) { - current_thread->IncrementYieldCount(); - return true; - } else { - is_reselection_pending.store(true, std::memory_order_release); - return false; - } -} - -void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) { - if (old_flags == thread->scheduling_state) { - return; - } - ASSERT(is_locked); - - if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) { - // In this case the thread was running, now it's pausing/exitting - if (thread->processor_id >= 0) { - Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast<u32>(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Unsuggest(thread->current_priority, core, thread); - } - } - } else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) { - // The thread is now set to running from being stopped - if (thread->processor_id >= 0) { - Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast<u32>(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Suggest(thread->current_priority, core, thread); - } - } - } - - SetReselectionPending(); -} - -void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) { - if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) { - return; - } - ASSERT(is_locked); - if (thread->processor_id >= 0) { - Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast<u32>(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Unsuggest(old_priority, core, thread); - } - } - - if (thread->processor_id >= 0) { - if (thread == kernel.CurrentScheduler().GetCurrentThread()) { - SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id), - thread); - } else { - Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread); - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast<u32>(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Suggest(thread->current_priority, core, thread); - } - } - thread->IncrementYieldCount(); - SetReselectionPending(); -} - -void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, - s32 old_core) { - if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) || - thread->current_priority >= THREADPRIO_COUNT) { - return; - } - ASSERT(is_locked); - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((old_affinity_mask >> core) & 1) != 0) { - if (core == static_cast<u32>(old_core)) { - Unschedule(thread->current_priority, core, thread); - } else { - Unsuggest(thread->current_priority, core, thread); - } - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((thread->affinity_mask >> core) & 1) != 0) { - if (core == static_cast<u32>(thread->processor_id)) { - Schedule(thread->current_priority, core, thread); - } else { - Suggest(thread->current_priority, core, thread); - } - } - } - - thread->IncrementYieldCount(); - SetReselectionPending(); -} - -void GlobalScheduler::Shutdown() { - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - scheduled_queue[core].clear(); - suggested_queue[core].clear(); - } - thread_list.clear(); -} - -void GlobalScheduler::Lock() { - Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID(); - ASSERT(!current_thread.IsInvalid()); - if (current_thread == current_owner) { - ++scope_lock; - } else { - inner_lock.lock(); - is_locked = true; - current_owner = current_thread; - ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle()); - scope_lock = 1; - } -} - -void GlobalScheduler::Unlock() { - if (--scope_lock != 0) { - ASSERT(scope_lock > 0); - return; - } - u32 cores_pending_reschedule = SelectThreads(); - Core::EmuThreadHandle leaving_thread = current_owner; - current_owner = Core::EmuThreadHandle::InvalidHandle(); - scope_lock = 1; - is_locked = false; - inner_lock.unlock(); - EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread); -} - -Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) { - switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this); -} - -Scheduler::~Scheduler() = default; - -bool Scheduler::HaveReadyThreads() const { - return system.GlobalScheduler().HaveReadyThreads(core_id); -} - -Thread* Scheduler::GetCurrentThread() const { - if (current_thread) { - return current_thread.get(); - } - return idle_thread.get(); -} - -Thread* Scheduler::GetSelectedThread() const { - return selected_thread.get(); -} - -u64 Scheduler::GetLastContextSwitchTicks() const { - return last_context_switch_time; -} - -void Scheduler::TryDoContextSwitch() { - auto& phys_core = system.Kernel().CurrentPhysicalCore(); - if (phys_core.IsInterrupted()) { - phys_core.ClearInterrupt(); - } - guard.lock(); - if (is_context_switch_pending) { - SwitchContext(); - } else { - guard.unlock(); - } -} - -void Scheduler::OnThreadStart() { - SwitchContextStep2(); -} - -void Scheduler::Unload() { - Thread* thread = current_thread.get(); - if (thread) { - thread->SetContinuousOnSVC(false); - thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - thread->SetIsRunning(false); - if (!thread->IsHLEThread() && !thread->HasExited()) { - Core::ARM_Interface& cpu_core = thread->ArmInterface(); - cpu_core.SaveContext(thread->GetContext32()); - cpu_core.SaveContext(thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - } - thread->context_guard.unlock(); - } -} - -void Scheduler::Reload() { - Thread* thread = current_thread.get(); - if (thread) { - ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, - "Thread must be runnable."); - - // Cancel any outstanding wakeup events for this thread - thread->SetIsRunning(true); - thread->SetWasRunning(false); - thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - - auto* const thread_owner_process = thread->GetOwnerProcess(); - if (thread_owner_process != nullptr) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - if (!thread->IsHLEThread()) { - Core::ARM_Interface& cpu_core = thread->ArmInterface(); - cpu_core.LoadContext(thread->GetContext32()); - cpu_core.LoadContext(thread->GetContext64()); - cpu_core.SetTlsAddress(thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); - cpu_core.ChangeProcessorID(this->core_id); - cpu_core.ClearExclusiveState(); - } - } -} - -void Scheduler::SwitchContextStep2() { - // Load context of new thread - if (selected_thread) { - ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, - "Thread must be runnable."); - - // Cancel any outstanding wakeup events for this thread - selected_thread->SetIsRunning(true); - selected_thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - selected_thread->SetWasRunning(false); - - auto* const thread_owner_process = current_thread->GetOwnerProcess(); - if (thread_owner_process != nullptr) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - if (!selected_thread->IsHLEThread()) { - Core::ARM_Interface& cpu_core = selected_thread->ArmInterface(); - cpu_core.LoadContext(selected_thread->GetContext32()); - cpu_core.LoadContext(selected_thread->GetContext64()); - cpu_core.SetTlsAddress(selected_thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(selected_thread->GetTPIDR_EL0()); - cpu_core.ChangeProcessorID(this->core_id); - cpu_core.ClearExclusiveState(); - } - } - - TryDoContextSwitch(); -} - -void Scheduler::SwitchContext() { - current_thread_prev = current_thread; - selected_thread = selected_thread_set; - Thread* previous_thread = current_thread_prev.get(); - Thread* new_thread = selected_thread.get(); - current_thread = selected_thread; - - is_context_switch_pending = false; - - if (new_thread == previous_thread) { - guard.unlock(); - return; - } - - Process* const previous_process = system.Kernel().CurrentProcess(); - - UpdateLastContextSwitchTime(previous_thread, previous_process); - - // Save context for previous thread - if (previous_thread) { - if (new_thread != nullptr && new_thread->IsSuspendThread()) { - previous_thread->SetWasRunning(true); - } - previous_thread->SetContinuousOnSVC(false); - previous_thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - previous_thread->SetIsRunning(false); - if (!previous_thread->IsHLEThread() && !previous_thread->HasExited()) { - Core::ARM_Interface& cpu_core = previous_thread->ArmInterface(); - cpu_core.SaveContext(previous_thread->GetContext32()); - cpu_core.SaveContext(previous_thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - } - previous_thread->context_guard.unlock(); - } - - std::shared_ptr<Common::Fiber>* old_context; - if (previous_thread != nullptr) { - old_context = &previous_thread->GetHostContext(); - } else { - old_context = &idle_thread->GetHostContext(); - } - guard.unlock(); - - Common::Fiber::YieldTo(*old_context, switch_fiber); - /// When a thread wakes up, the scheduler may have changed to other in another core. - auto& next_scheduler = system.Kernel().CurrentScheduler(); - next_scheduler.SwitchContextStep2(); -} - -void Scheduler::OnSwitch(void* this_scheduler) { - Scheduler* sched = static_cast<Scheduler*>(this_scheduler); - sched->SwitchToCurrent(); -} - -void Scheduler::SwitchToCurrent() { - while (true) { - { - std::scoped_lock lock{guard}; - selected_thread = selected_thread_set; - current_thread = selected_thread; - is_context_switch_pending = false; - } - while (!is_context_switch_pending) { - if (current_thread != nullptr && !current_thread->IsHLEThread()) { - current_thread->context_guard.lock(); - if (!current_thread->IsRunnable()) { - current_thread->context_guard.unlock(); - break; - } - if (current_thread->GetProcessorID() != core_id) { - current_thread->context_guard.unlock(); - break; - } - } - std::shared_ptr<Common::Fiber>* next_context; - if (current_thread != nullptr) { - next_context = ¤t_thread->GetHostContext(); - } else { - next_context = &idle_thread->GetHostContext(); - } - Common::Fiber::YieldTo(switch_fiber, *next_context); - } - } -} - -void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { - const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); - const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; - - if (thread != nullptr) { - thread->UpdateCPUTimeTicks(update_ticks); - } - - if (process != nullptr) { - process->UpdateCPUTimeTicks(update_ticks); - } - - last_context_switch_time = most_recent_switch_ticks; -} - -void Scheduler::Initialize() { - std::string name = "Idle Thread Id:" + std::to_string(core_id); - std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc(); - void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); - ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE); - auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0, - nullptr, std::move(init_func), init_func_parameter); - idle_thread = std::move(thread_res).Unwrap(); -} - -void Scheduler::Shutdown() { - current_thread = nullptr; - selected_thread = nullptr; -} - -SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} { - kernel.GlobalScheduler().Lock(); -} - -SchedulerLock::~SchedulerLock() { - kernel.GlobalScheduler().Unlock(); -} - -SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, - Thread* time_task, s64 nanoseconds) - : SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{ - nanoseconds} { - event_handle = InvalidHandle; -} - -SchedulerLockAndSleep::~SchedulerLockAndSleep() { - if (sleep_cancelled) { - return; - } - auto& time_manager = kernel.TimeManager(); - time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); -} - -void SchedulerLockAndSleep::Release() { - if (sleep_cancelled) { - return; - } - auto& time_manager = kernel.TimeManager(); - time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); - sleep_cancelled = true; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h deleted file mode 100644 index 36e3c26fb..000000000 --- a/src/core/hle/kernel/scheduler.h +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <atomic> -#include <memory> -#include <mutex> -#include <vector> - -#include "common/common_types.h" -#include "common/multi_level_queue.h" -#include "common/spin_lock.h" -#include "core/hardware_properties.h" -#include "core/hle/kernel/thread.h" - -namespace Common { -class Fiber; -} - -namespace Core { -class ARM_Interface; -class System; -} // namespace Core - -namespace Kernel { - -class KernelCore; -class Process; -class SchedulerLock; - -class GlobalScheduler final { -public: - explicit GlobalScheduler(KernelCore& kernel); - ~GlobalScheduler(); - - /// Adds a new thread to the scheduler - void AddThread(std::shared_ptr<Thread> thread); - - /// Removes a thread from the scheduler - void RemoveThread(std::shared_ptr<Thread> thread); - - /// Returns a list of all threads managed by the scheduler - const std::vector<std::shared_ptr<Thread>>& GetThreadList() const { - return thread_list; - } - - /// Notify the scheduler a thread's status has changed. - void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags); - - /// Notify the scheduler a thread's priority has changed. - void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority); - - /// Notify the scheduler a thread's core and/or affinity mask has changed. - void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core); - - /** - * Takes care of selecting the new scheduled threads in three steps: - * - * 1. First a thread is selected from the top of the priority queue. If no thread - * is obtained then we move to step two, else we are done. - * - * 2. Second we try to get a suggested thread that's not assigned to any core or - * that is not the top thread in that core. - * - * 3. Third is no suggested thread is found, we do a second pass and pick a running - * thread in another core and swap it with its current thread. - * - * returns the cores needing scheduling. - */ - u32 SelectThreads(); - - bool HaveReadyThreads(std::size_t core_id) const { - return !scheduled_queue[core_id].empty(); - } - - /** - * Takes a thread and moves it to the back of the it's priority list. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThread(Thread* thread); - - /** - * Takes a thread and moves it to the back of the it's priority list. - * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or - * a better priority than the next thread in the core. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThreadAndBalanceLoad(Thread* thread); - - /** - * Takes a thread and moves it out of the scheduling queue. - * and into the suggested queue. If no thread can be scheduled afterwards in that core, - * a suggested thread is obtained instead. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThreadAndWaitForLoadBalancing(Thread* thread); - - /** - * Rotates the scheduling queues of threads at a preemption priority and then does - * some core rebalancing. Preemption priorities can be found in the array - * 'preemption_priorities'. - * - * @note This operation happens every 10ms. - */ - void PreemptThreads(); - - u32 CpuCoresCount() const { - return Core::Hardware::NUM_CPU_CORES; - } - - void SetReselectionPending() { - is_reselection_pending.store(true, std::memory_order_release); - } - - bool IsReselectionPending() const { - return is_reselection_pending.load(std::memory_order_acquire); - } - - void Shutdown(); - -private: - friend class SchedulerLock; - - /// Lock the scheduler to the current thread. - void Lock(); - - /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling - /// and reschedules current core if needed. - void Unlock(); - - void EnableInterruptAndSchedule(u32 cores_pending_reschedule, - Core::EmuThreadHandle global_thread); - - /** - * Add a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Suggest(u32 priority, std::size_t core, Thread* thread); - - /** - * Remove a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Unsuggest(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * back the queue in its priority level. - */ - void Schedule(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * front the queue in its priority level. - */ - void SchedulePrepend(u32 priority, std::size_t core, Thread* thread); - - /// Reschedule an already scheduled thread based on a new priority - void Reschedule(u32 priority, std::size_t core, Thread* thread); - - /// Unschedules a thread. - void Unschedule(u32 priority, std::size_t core, Thread* thread); - - /** - * Transfers a thread into an specific core. If the destination_core is -1 - * it will be unscheduled from its source code and added into its suggested - * queue. - */ - void TransferToCore(u32 priority, s32 destination_core, Thread* thread); - - bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner); - - static constexpr u32 min_regular_priority = 2; - std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES> - scheduled_queue; - std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES> - suggested_queue; - std::atomic<bool> is_reselection_pending{false}; - - // The priority levels at which the global scheduler preempts threads every 10 ms. They are - // ordered from Core 0 to Core 3. - std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62}; - - /// Scheduler lock mechanisms. - bool is_locked{}; - Common::SpinLock inner_lock{}; - std::atomic<s64> scope_lock{}; - Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; - - Common::SpinLock global_list_guard{}; - - /// Lists all thread ids that aren't deleted/etc. - std::vector<std::shared_ptr<Thread>> thread_list; - KernelCore& kernel; -}; - -class Scheduler final { -public: - explicit Scheduler(Core::System& system, std::size_t core_id); - ~Scheduler(); - - /// Returns whether there are any threads that are ready to run. - bool HaveReadyThreads() const; - - /// Reschedules to the next available thread (call after current thread is suspended) - void TryDoContextSwitch(); - - /// The next two are for SingleCore Only. - /// Unload current thread before preempting core. - void Unload(); - /// Reload current thread after core preemption. - void Reload(); - - /// Gets the current running thread - Thread* GetCurrentThread() const; - - /// Gets the currently selected thread from the top of the multilevel queue - Thread* GetSelectedThread() const; - - /// Gets the timestamp for the last context switch in ticks. - u64 GetLastContextSwitchTicks() const; - - bool ContextSwitchPending() const { - return is_context_switch_pending; - } - - void Initialize(); - - /// Shutdowns the scheduler. - void Shutdown(); - - void OnThreadStart(); - - std::shared_ptr<Common::Fiber>& ControlContext() { - return switch_fiber; - } - - const std::shared_ptr<Common::Fiber>& ControlContext() const { - return switch_fiber; - } - -private: - friend class GlobalScheduler; - - /// Switches the CPU's active thread context to that of the specified thread - void SwitchContext(); - - /// When a thread wakes up, it must run this through it's new scheduler - void SwitchContextStep2(); - - /** - * Called on every context switch to update the internal timestamp - * This also updates the running time ticks for the given thread and - * process using the following difference: - * - * ticks += most_recent_ticks - last_context_switch_ticks - * - * The internal tick timestamp for the scheduler is simply the - * most recent tick count retrieved. No special arithmetic is - * applied to it. - */ - void UpdateLastContextSwitchTime(Thread* thread, Process* process); - - static void OnSwitch(void* this_scheduler); - void SwitchToCurrent(); - - std::shared_ptr<Thread> current_thread = nullptr; - std::shared_ptr<Thread> selected_thread = nullptr; - std::shared_ptr<Thread> current_thread_prev = nullptr; - std::shared_ptr<Thread> selected_thread_set = nullptr; - std::shared_ptr<Thread> idle_thread = nullptr; - - std::shared_ptr<Common::Fiber> switch_fiber = nullptr; - - Core::System& system; - u64 last_context_switch_time = 0; - u64 idle_selection_count = 0; - const std::size_t core_id; - - Common::SpinLock guard{}; - - bool is_context_switch_pending = false; -}; - -class SchedulerLock { -public: - [[nodiscard]] explicit SchedulerLock(KernelCore& kernel); - ~SchedulerLock(); - -protected: - KernelCore& kernel; -}; - -class SchedulerLockAndSleep : public SchedulerLock { -public: - explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task, - s64 nanoseconds); - ~SchedulerLockAndSleep(); - - void CancelSleep() { - sleep_cancelled = true; - } - - void Release(); - -private: - Handle& event_handle; - Thread* time_task; - s64 nanoseconds; - bool sleep_cancelled{}; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index a549ae9d7..5d17346ad 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -5,20 +5,20 @@ #include <tuple> #include "common/assert.h" #include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel { -ServerPort::ServerPort(KernelCore& kernel) : SynchronizationObject{kernel} {} +ServerPort::ServerPort(KernelCore& kernel) : KSynchronizationObject{kernel} {} ServerPort::~ServerPort() = default; ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() { if (pending_sessions.empty()) { - return ERR_NOT_FOUND; + return ResultNotFound; } auto session = std::move(pending_sessions.back()); @@ -28,15 +28,9 @@ ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() { void ServerPort::AppendPendingSession(std::shared_ptr<ServerSession> pending_session) { pending_sessions.push_back(std::move(pending_session)); -} - -bool ServerPort::ShouldWait(const Thread* thread) const { - // If there are no pending sessions, we wait until a new one is added. - return pending_sessions.empty(); -} - -void ServerPort::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); + if (pending_sessions.size() == 1) { + NotifyAvailable(); + } } bool ServerPort::IsSignaled() const { diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index 41b191b86..29b4f2509 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -9,8 +9,8 @@ #include <utility> #include <vector> #include "common/common_types.h" +#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/object.h" -#include "core/hle/kernel/synchronization_object.h" #include "core/hle/result.h" namespace Kernel { @@ -20,7 +20,7 @@ class KernelCore; class ServerSession; class SessionRequestHandler; -class ServerPort final : public SynchronizationObject { +class ServerPort final : public KSynchronizationObject { public: explicit ServerPort(KernelCore& kernel); ~ServerPort() override; @@ -79,11 +79,10 @@ public: /// waiting to be accepted by this port. void AppendPendingSession(std::shared_ptr<ServerSession> pending_session); - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - bool IsSignaled() const override; + void Finalize() override {} + private: /// ServerSessions waiting to be accepted by the port std::vector<std::shared_ptr<ServerSession>> pending_sessions; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 7e6391c6c..790dbb998 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -8,51 +8,40 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" -#include "core/core.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" #include "core/memory.h" namespace Kernel { -ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {} -ServerSession::~ServerSession() = default; +ServerSession::ServerSession(KernelCore& kernel) : KSynchronizationObject{kernel} {} + +ServerSession::~ServerSession() { + kernel.ReleaseServiceThread(service_thread); +} ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel, std::shared_ptr<Session> parent, std::string name) { std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)}; - session->request_event = - Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) { - session->CompleteSyncRequest(); - }); session->name = std::move(name); session->parent = std::move(parent); + session->service_thread = kernel.CreateServiceThread(session->name); return MakeResult(std::move(session)); } -bool ServerSession::ShouldWait(const Thread* thread) const { - // Closed sessions should never wait, an error will be returned from svcReplyAndReceive. - if (!parent->Client()) { - return false; - } - - // Wait if we have no pending requests, or if we're currently handling a request. - return pending_requesting_threads.empty() || currently_handling != nullptr; -} - bool ServerSession::IsSignaled() const { // Closed sessions should never wait, an error will be returned from svcReplyAndReceive. if (!parent->Client()) { @@ -63,15 +52,6 @@ bool ServerSession::IsSignaled() const { return !pending_requesting_threads.empty() && currently_handling == nullptr; } -void ServerSession::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // We are now handling a request, pop it from the stack. - // TODO(Subv): What happens if the client endpoint is closed before any requests are made? - ASSERT(!pending_requesting_threads.empty()); - currently_handling = pending_requesting_threads.back(); - pending_requesting_threads.pop_back(); -} - void ServerSession::ClientDisconnected() { // We keep a shared pointer to the hle handler to keep it alive throughout // the call to ClientDisconnected, as ClientDisconnected invalidates the @@ -131,29 +111,28 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con } } - LOG_CRITICAL(IPC, "Unknown domain command={}", - static_cast<int>(domain_message_header.command.Value())); + LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value()); ASSERT(false); return RESULT_SUCCESS; } -ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread, +ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory) { u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))}; auto context = std::make_shared<HLERequestContext>(kernel, memory, SharedFrom(this), std::move(thread)); context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); - request_queue.Push(std::move(context)); + + if (auto strong_ptr = service_thread.lock()) { + strong_ptr->QueueSyncRequest(*this, std::move(context)); + return RESULT_SUCCESS; + } return RESULT_SUCCESS; } -ResultCode ServerSession::CompleteSyncRequest() { - ASSERT(!request_queue.Empty()); - - auto& context = *request_queue.Front(); - +ResultCode ServerSession::CompleteSyncRequest(HLERequestContext& context) { ResultCode result = RESULT_SUCCESS; // If the session has been converted to a domain, handle the domain request if (IsDomain() && context.HasDomainMessageHeader()) { @@ -172,24 +151,20 @@ ResultCode ServerSession::CompleteSyncRequest() { // Some service requests require the thread to block { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (!context.IsThreadWaiting()) { - context.GetThread().ResumeFromWait(); - context.GetThread().SetSynchronizationResults(nullptr, result); + context.GetThread().Wakeup(); + context.GetThread().SetSyncedObject(nullptr, result); } } - request_queue.Pop(); - return result; } -ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, - Core::Memory::Memory& memory) { - const ResultCode result = QueueSyncRequest(std::move(thread), memory); - const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000}; - Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {}); - return result; +ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<KThread> thread, + Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing) { + return QueueSyncRequest(std::move(thread), memory); } } // namespace Kernel diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 403aaf10b..c42d5ee59 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -10,7 +10,8 @@ #include <vector> #include "common/threadsafe_queue.h" -#include "core/hle/kernel/synchronization_object.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/service_thread.h" #include "core/hle/result.h" namespace Core::Memory { @@ -18,8 +19,9 @@ class Memory; } namespace Core::Timing { +class CoreTiming; struct EventType; -} +} // namespace Core::Timing namespace Kernel { @@ -27,7 +29,7 @@ class HLERequestContext; class KernelCore; class Session; class SessionRequestHandler; -class Thread; +class KThread; /** * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS @@ -41,7 +43,9 @@ class Thread; * After the server replies to the request, the response is marshalled back to the caller's * TLS buffer and control is transferred back to it. */ -class ServerSession final : public SynchronizationObject { +class ServerSession final : public KSynchronizationObject { + friend class ServiceThread; + public: explicit ServerSession(KernelCore& kernel); ~ServerSession() override; @@ -73,8 +77,6 @@ public: return parent.get(); } - bool IsSignaled() const override; - /** * Sets the HLE handler for the session. This handler will be called to service IPC requests * instead of the regular IPC machinery. (The regular IPC machinery is currently not @@ -87,16 +89,14 @@ public: /** * Handle a sync request from the emulated application. * - * @param thread Thread that initiated the request. - * @param memory Memory context to handle the sync request under. + * @param thread Thread that initiated the request. + * @param memory Memory context to handle the sync request under. + * @param core_timing Core timing context to schedule the request event under. * * @returns ResultCode from the operation. */ - ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); - - bool ShouldWait(const Thread* thread) const override; - - void Acquire(Thread* thread) override; + ResultCode HandleSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing); /// Called when a client disconnection occurs. void ClientDisconnected(); @@ -124,12 +124,16 @@ public: convert_to_domain = true; } + bool IsSignaled() const override; + + void Finalize() override {} + private: /// Queues a sync request from the emulated application. - ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); + ResultCode QueueSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory); /// Completes a sync request from the emulated application. - ResultCode CompleteSyncRequest(); + ResultCode CompleteSyncRequest(HLERequestContext& context); /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an /// object handle. @@ -147,12 +151,12 @@ private: /// List of threads that are pending a response after a sync request. This list is processed in /// a LIFO manner, thus, the last request will be dispatched first. /// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test. - std::vector<std::shared_ptr<Thread>> pending_requesting_threads; + std::vector<std::shared_ptr<KThread>> pending_requesting_threads; /// Thread whose request is currently being handled. A request is considered "handled" when a /// response is sent via svcReplyAndReceive. /// TODO(Subv): Find a better name for this. - std::shared_ptr<Thread> currently_handling; + std::shared_ptr<KThread> currently_handling; /// When set to True, converts the session to a domain at the end of the command bool convert_to_domain{}; @@ -160,11 +164,8 @@ private: /// The name of this session (optional) std::string name; - /// Core timing event used to schedule the service request at some point in the future - std::shared_ptr<Core::Timing::EventType> request_event; - - /// Queue of scheduled service requests - Common::MPSCQueue<std::shared_ptr<Kernel::HLERequestContext>> request_queue; + /// Thread to dispatch service requests + std::weak_ptr<ServiceThread> service_thread; }; } // namespace Kernel diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp new file mode 100644 index 000000000..ee46f3e21 --- /dev/null +++ b/src/core/hle/kernel/service_thread.cpp @@ -0,0 +1,110 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <condition_variable> +#include <functional> +#include <mutex> +#include <thread> +#include <vector> +#include <queue> + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/service_thread.h" +#include "core/hle/lock.h" +#include "video_core/renderer_base.h" + +namespace Kernel { + +class ServiceThread::Impl final { +public: + explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name); + ~Impl(); + + void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context); + +private: + std::vector<std::thread> threads; + std::queue<std::function<void()>> requests; + std::mutex queue_mutex; + std::condition_variable condition; + const std::string service_name; + bool stop{}; +}; + +ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name) + : service_name{name} { + for (std::size_t i = 0; i < num_threads; ++i) + threads.emplace_back([this, &kernel] { + Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str()); + + // Wait for first request before trying to acquire a render context + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + } + + kernel.RegisterHostThread(); + + while (true) { + std::function<void()> task; + + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + if (stop || requests.empty()) { + return; + } + task = std::move(requests.front()); + requests.pop(); + } + + task(); + } + }); +} + +void ServiceThread::Impl::QueueSyncRequest(ServerSession& session, + std::shared_ptr<HLERequestContext>&& context) { + { + std::unique_lock lock{queue_mutex}; + + // ServerSession owns the service thread, so we cannot caption a strong pointer here in the + // event that the ServerSession is terminated. + std::weak_ptr<ServerSession> weak_ptr{SharedFrom(&session)}; + requests.emplace([weak_ptr, context{std::move(context)}]() { + if (auto strong_ptr = weak_ptr.lock()) { + strong_ptr->CompleteSyncRequest(*context); + } + }); + } + condition.notify_one(); +} + +ServiceThread::Impl::~Impl() { + { + std::unique_lock lock{queue_mutex}; + stop = true; + } + condition.notify_all(); + for (std::thread& thread : threads) { + thread.join(); + } +} + +ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name) + : impl{std::make_unique<Impl>(kernel, num_threads, name)} {} + +ServiceThread::~ServiceThread() = default; + +void ServiceThread::QueueSyncRequest(ServerSession& session, + std::shared_ptr<HLERequestContext>&& context) { + impl->QueueSyncRequest(session, std::move(context)); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h new file mode 100644 index 000000000..025ab8fb5 --- /dev/null +++ b/src/core/hle/kernel/service_thread.h @@ -0,0 +1,28 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +namespace Kernel { + +class HLERequestContext; +class KernelCore; +class ServerSession; + +class ServiceThread final { +public: + explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name); + ~ServiceThread(); + + void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context); + +private: + class Impl; + std::unique_ptr<Impl> impl; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp index e4dd53e24..8830d4e91 100644 --- a/src/core/hle/kernel/session.cpp +++ b/src/core/hle/kernel/session.cpp @@ -4,15 +4,23 @@ #include "common/assert.h" #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" namespace Kernel { -Session::Session(KernelCore& kernel) : SynchronizationObject{kernel} {} -Session::~Session() = default; +Session::Session(KernelCore& kernel) : KSynchronizationObject{kernel} {} +Session::~Session() { + // Release reserved resource when the Session pair was created. + kernel.GetSystemResourceLimit()->Release(LimitableResource::Sessions, 1); +} Session::SessionPair Session::Create(KernelCore& kernel, std::string name) { + // Reserve a new session from the resource limit. + KScopedResourceReservation session_reservation(kernel.GetSystemResourceLimit(), + LimitableResource::Sessions); + ASSERT(session_reservation.Succeeded()); auto session{std::make_shared<Session>(kernel)}; auto client_session{Kernel::ClientSession::Create(kernel, session, name + "_Client").Unwrap()}; auto server_session{Kernel::ServerSession::Create(kernel, session, name + "_Server").Unwrap()}; @@ -21,21 +29,13 @@ Session::SessionPair Session::Create(KernelCore& kernel, std::string name) { session->client = client_session; session->server = server_session; + session_reservation.Commit(); return std::make_pair(std::move(client_session), std::move(server_session)); } -bool Session::ShouldWait(const Thread* thread) const { - UNIMPLEMENTED(); - return {}; -} - bool Session::IsSignaled() const { UNIMPLEMENTED(); return true; } -void Session::Acquire(Thread* thread) { - UNIMPLEMENTED(); -} - } // namespace Kernel diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h index 7cd9c0d77..fa3c5651a 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/kernel/session.h @@ -8,7 +8,7 @@ #include <string> #include <utility> -#include "core/hle/kernel/synchronization_object.h" +#include "core/hle/kernel/k_synchronization_object.h" namespace Kernel { @@ -19,7 +19,7 @@ class ServerSession; * Parent structure to link the client and server endpoints of a session with their associated * client port. */ -class Session final : public SynchronizationObject { +class Session final : public KSynchronizationObject { public: explicit Session(KernelCore& kernel); ~Session() override; @@ -37,11 +37,9 @@ public: return HANDLE_TYPE; } - bool ShouldWait(const Thread* thread) const override; - bool IsSignaled() const override; - void Acquire(Thread* thread) override; + void Finalize() override {} std::shared_ptr<ClientSession> Client() { if (auto result{client.lock()}) { diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 0cd467110..2eadd51d7 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -4,6 +4,7 @@ #include "common/assert.h" #include "core/core.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/shared_memory.h" @@ -13,7 +14,9 @@ namespace Kernel { SharedMemory::SharedMemory(KernelCore& kernel, Core::DeviceMemory& device_memory) : Object{kernel}, device_memory{device_memory} {} -SharedMemory::~SharedMemory() = default; +SharedMemory::~SharedMemory() { + kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemory, size); +} std::shared_ptr<SharedMemory> SharedMemory::Create( KernelCore& kernel, Core::DeviceMemory& device_memory, Process* owner_process, @@ -21,6 +24,11 @@ std::shared_ptr<SharedMemory> SharedMemory::Create( Memory::MemoryPermission user_permission, PAddr physical_address, std::size_t size, std::string name) { + const auto resource_limit = kernel.GetSystemResourceLimit(); + KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory, + size); + ASSERT(memory_reservation.Succeeded()); + std::shared_ptr<SharedMemory> shared_memory{ std::make_shared<SharedMemory>(kernel, device_memory)}; @@ -32,6 +40,7 @@ std::shared_ptr<SharedMemory> SharedMemory::Create( shared_memory->size = size; shared_memory->name = name; + memory_reservation.Commit(); return shared_memory; } diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 0ef87235c..623bd8b11 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -71,6 +71,8 @@ public: return device_memory.GetPointer(physical_address + offset); } + void Finalize() override {} + private: Core::DeviceMemory& device_memory; Process* owner_process{}; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 01ae57053..31d899e06 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -10,38 +10,44 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/common_funcs.h" #include "common/fiber.h" #include "common/logging/log.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/cpu_manager.h" -#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_address_arbiter.h" +#include "core/hle/kernel/k_condition_variable.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_block.h" +#include "core/hle/kernel/memory/memory_layout.h" #include "core/hle/kernel/memory/page_table.h" -#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/kernel/svc_types.h" #include "core/hle/kernel/svc_wrap.h" -#include "core/hle/kernel/synchronization.h" -#include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" #include "core/hle/kernel/transfer_memory.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/lock.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -65,49 +71,49 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds VAddr src_addr, u64 size) { if (!Common::Is4KBAligned(dst_addr)) { LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(src_addr)) { LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (size == 0) { LOG_ERROR(Kernel_SVC, "Size is 0"); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!IsValidAddressRange(dst_addr, size)) { LOG_ERROR(Kernel_SVC, "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", dst_addr, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!IsValidAddressRange(src_addr, size)) { LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", src_addr, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!manager.IsInsideAddressSpace(src_addr, size)) { LOG_ERROR(Kernel_SVC, "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", src_addr, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (manager.IsOutsideStackRegion(dst_addr, size)) { LOG_ERROR(Kernel_SVC, "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", dst_addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (manager.IsInsideHeapRegion(dst_addr, size)) { @@ -115,7 +121,7 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds "Destination does not fit within the heap region, addr=0x{:016X}, " "size=0x{:016X}", dst_addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (manager.IsInsideAliasRegion(dst_addr, size)) { @@ -123,7 +129,7 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds "Destination does not fit within the map region, addr=0x{:016X}, " "size=0x{:016X}", dst_addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } return RESULT_SUCCESS; @@ -132,33 +138,40 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds enum class ResourceLimitValueType { CurrentValue, LimitValue, + PeakValue, }; ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit, u32 resource_type, ResourceLimitValueType value_type) { std::lock_guard lock{HLE::g_hle_lock}; - const auto type = static_cast<ResourceType>(resource_type); + const auto type = static_cast<LimitableResource>(resource_type); if (!IsValidResourceType(type)) { LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } const auto* const current_process = system.Kernel().CurrentProcess(); ASSERT(current_process != nullptr); const auto resource_limit_object = - current_process->GetHandleTable().Get<ResourceLimit>(resource_limit); + current_process->GetHandleTable().Get<KResourceLimit>(resource_limit); if (!resource_limit_object) { LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}", resource_limit); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } - if (value_type == ResourceLimitValueType::CurrentValue) { - return MakeResult(resource_limit_object->GetCurrentResourceValue(type)); + switch (value_type) { + case ResourceLimitValueType::CurrentValue: + return MakeResult(resource_limit_object->GetCurrentValue(type)); + case ResourceLimitValueType::LimitValue: + return MakeResult(resource_limit_object->GetLimitValue(type)); + case ResourceLimitValueType::PeakValue: + return MakeResult(resource_limit_object->GetPeakValue(type)); + default: + LOG_ERROR(Kernel_SVC, "Invalid resource value_type: '{}'", value_type); + return ResultInvalidEnumValue; } - - return MakeResult(resource_limit_object->GetMaxResourceValue(type)); } } // Anonymous namespace @@ -171,12 +184,12 @@ static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_s if ((heap_size % 0x200000) != 0) { LOG_ERROR(Kernel_SVC, "The heap size is not a multiple of 2MB, heap_size=0x{:016X}", heap_size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (heap_size >= 0x200000000) { LOG_ERROR(Kernel_SVC, "The heap size is not less than 8GB, heap_size=0x{:016X}", heap_size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; @@ -202,19 +215,19 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si if (!Common::Is4KBAligned(address)) { LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (size == 0 || !Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.", size); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!IsValidAddressRange(address, size)) { LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})", address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } const auto attributes{static_cast<Memory::MemoryAttribute>(mask | attribute)}; @@ -223,7 +236,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si LOG_ERROR(Kernel_SVC, "Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}", attribute, mask); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; @@ -234,8 +247,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attribute) { - return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size), - mask, attribute); + return SetMemoryAttribute(system, address, size, mask, attribute); } /// Maps a memory range into a different range. @@ -255,8 +267,7 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr } static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr), - static_cast<std::size_t>(size)); + return MapMemory(system, dst_addr, src_addr, size); } /// Unmaps a region that was previously mapped with svcMapMemory @@ -276,8 +287,7 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad } static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr), - static_cast<std::size_t>(size)); + return UnmapMemory(system, dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -290,7 +300,7 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle, LOG_ERROR(Kernel_SVC, "Port Name Address is not a valid virtual address, port_name_address=0x{:016X}", port_name_address); - return ERR_NOT_FOUND; + return ResultNotFound; } static constexpr std::size_t PortNameMaxLength = 11; @@ -299,7 +309,7 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle, if (port_name.size() > PortNameMaxLength) { LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength, port_name.size()); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); @@ -308,11 +318,9 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle, const auto it = kernel.FindNamedPort(port_name); if (!kernel.IsValidNamedPort(it)) { LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name); - return ERR_NOT_FOUND; + return ResultNotFound; } - ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Sessions, 1)); - auto client_port = it->second; std::shared_ptr<ClientSession> client_session; @@ -332,40 +340,26 @@ static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle, /// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Core::System& system, Handle handle) { - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + auto& kernel = system.Kernel(); + const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); std::shared_ptr<ClientSession> session = handle_table.Get<ClientSession>(handle); if (!session) { LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); - auto thread = system.CurrentScheduler().GetCurrentThread(); + auto thread = kernel.CurrentScheduler()->GetCurrentThread(); { - SchedulerLock lock(system.Kernel()); - thread->InvalidateHLECallback(); - thread->SetStatus(ThreadStatus::WaitIPC); - session->SendSyncRequest(SharedFrom(thread), system.Memory()); - } - - if (thread->HasHLECallback()) { - Handle event_handle = thread->GetHLETimeEvent(); - if (event_handle != InvalidHandle) { - auto& time_manager = system.Kernel().TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - - { - SchedulerLock lock(system.Kernel()); - auto* sync_object = thread->GetHLESyncObject(); - sync_object->RemoveWaitingThread(SharedFrom(thread)); - } - - thread->InvokeHLECallback(SharedFrom(thread)); + KScopedSchedulerLock lock(kernel); + thread->SetState(ThreadState::Waiting); + thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); + session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming()); } - return thread->GetSignalingResult(); + KSynchronizationObject* dummy{}; + return thread->GetWaitResult(std::addressof(dummy)); } static ResultCode SendSyncRequest32(Core::System& system, Handle handle) { @@ -373,27 +367,29 @@ static ResultCode SendSyncRequest32(Core::System& system, Handle handle) { } /// Get the ID for the specified thread. -static ResultCode GetThreadId(Core::System& system, u64* thread_id, Handle thread_handle) { +static ResultCode GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + // Get the thread from its handle. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", thread_handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - *thread_id = thread->GetThreadID(); + // Get the thread's id. + *out_thread_id = thread->GetThreadID(); return RESULT_SUCCESS; } -static ResultCode GetThreadId32(Core::System& system, u32* thread_id_low, u32* thread_id_high, - Handle thread_handle) { - u64 thread_id{}; - const ResultCode result{GetThreadId(system, &thread_id, thread_handle)}; +static ResultCode GetThreadId32(Core::System& system, u32* out_thread_id_low, + u32* out_thread_id_high, Handle thread_handle) { + u64 out_thread_id{}; + const ResultCode result{GetThreadId(system, &out_thread_id, thread_handle)}; - *thread_id_low = static_cast<u32>(thread_id >> 32); - *thread_id_high = static_cast<u32>(thread_id & std::numeric_limits<u32>::max()); + *out_thread_id_low = static_cast<u32>(out_thread_id >> 32); + *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max()); return result; } @@ -409,12 +405,12 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han return RESULT_SUCCESS; } - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle); if (thread) { const Process* const owner_process = thread->GetOwnerProcess(); if (!owner_process) { LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered."); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } *process_id = owner_process->GetProcessID(); @@ -424,7 +420,7 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han // NOTE: This should also handle debug objects before returning. LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32* process_id_high, @@ -437,7 +433,7 @@ static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32* } /// Wait for the given handles to synchronize, timeout after the specified nanoseconds -static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address, +static ResultCode WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, u64 handle_count, s64 nano_seconds) { LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}", handles_address, handle_count, nano_seconds); @@ -447,7 +443,7 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr LOG_ERROR(Kernel_SVC, "Handle address is not a valid virtual address, handle_address=0x{:016X}", handles_address); - return ERR_INVALID_POINTER; + return ResultInvalidPointer; } static constexpr u64 MaxHandles = 0x40; @@ -455,32 +451,30 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr if (handle_count > MaxHandles) { LOG_ERROR(Kernel_SVC, "Handle count specified is too large, expected {} but got {}", MaxHandles, handle_count); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } auto& kernel = system.Kernel(); - Thread::ThreadSynchronizationObjects objects(handle_count); + std::vector<KSynchronizationObject*> objects(handle_count); const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); for (u64 i = 0; i < handle_count; ++i) { const Handle handle = memory.Read32(handles_address + i * sizeof(Handle)); - const auto object = handle_table.Get<SynchronizationObject>(handle); + const auto object = handle_table.Get<KSynchronizationObject>(handle); if (object == nullptr) { LOG_ERROR(Kernel_SVC, "Object is a nullptr"); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } - objects[i] = object; + objects[i] = object.get(); } - auto& synchronization = kernel.Synchronization(); - const auto [result, handle_result] = synchronization.WaitFor(objects, nano_seconds); - *index = handle_result; - return result; + return KSynchronizationObject::Wait(kernel, index, objects.data(), + static_cast<s32>(objects.size()), nano_seconds); } static ResultCode WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, - s32 handle_count, u32 timeout_high, Handle* index) { + s32 handle_count, u32 timeout_high, s32* index) { const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)}; return WaitSynchronization(system, index, handles_address, handle_count, nano_seconds); } @@ -489,15 +483,17 @@ static ResultCode WaitSynchronization32(Core::System& system, u32 timeout_low, u static ResultCode CancelSynchronization(Core::System& system, Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle); + // Get the thread from its handle. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); + std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); + if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", - thread_handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - thread->CancelWait(); + // Cancel the thread's wait. + thread->WaitCancel(); return RESULT_SUCCESS; } @@ -505,57 +501,53 @@ static ResultCode CancelSynchronization32(Core::System& system, Handle thread_ha return CancelSynchronization(system, thread_handle); } -/// Attempts to locks a mutex, creating it if it does not already exist -static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_handle, - VAddr mutex_addr, Handle requesting_thread_handle) { - LOG_TRACE(Kernel_SVC, - "called holding_thread_handle=0x{:08X}, mutex_addr=0x{:X}, " - "requesting_current_thread_handle=0x{:08X}", - holding_thread_handle, mutex_addr, requesting_thread_handle); +/// Attempts to locks a mutex +static ResultCode ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, + u32 tag) { + LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}", + thread_handle, address, tag); - if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) { - LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}", - mutex_addr); - return ERR_INVALID_ADDRESS_STATE; + // Validate the input address. + if (Memory::IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; } - - if (!Common::IsWordAligned(mutex_addr)) { - LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr); - return ERR_INVALID_ADDRESS; + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; } - auto* const current_process = system.Kernel().CurrentProcess(); - return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle, - requesting_thread_handle); + return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag); } -static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle, - u32 mutex_addr, Handle requesting_thread_handle) { - return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr), - requesting_thread_handle); +static ResultCode ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, + u32 tag) { + return ArbitrateLock(system, thread_handle, address, tag); } /// Unlock a mutex -static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) { - LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr); +static ResultCode ArbitrateUnlock(Core::System& system, VAddr address) { + LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address); - if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) { - LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}", - mutex_addr); - return ERR_INVALID_ADDRESS_STATE; - } + // Validate the input address. - if (!Common::IsWordAligned(mutex_addr)) { - LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr); - return ERR_INVALID_ADDRESS; + if (Memory::IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, + "Attempting to arbitrate an unlock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; } - auto* const current_process = system.Kernel().CurrentProcess(); - return current_process->GetMutex().Release(mutex_addr); + return system.Kernel().CurrentProcess()->SignalToAddress(address); } -static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) { - return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr)); +static ResultCode ArbitrateUnlock32(Core::System& system, u32 address) { + return ArbitrateUnlock(system, address); } enum class BreakType : u32 { @@ -658,7 +650,6 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); if (!break_reason.signal_debugger) { - SchedulerLock lock(system.Kernel()); LOG_CRITICAL( Debug_Emulated, "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", @@ -666,22 +657,18 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { handle_debug_buffer(info1, info2); - auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); - const auto thread_processor_id = current_thread->GetProcessorID(); + auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); + const auto thread_processor_id = current_thread->GetActiveCore(); system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); - - // Kill the current thread - system.Kernel().ExceptionalExit(); - current_thread->Stop(); } } static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { - Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2)); + Break(system, reason, info1, info2); } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) { +static void OutputDebugString(Core::System& system, VAddr address, u64 len) { if (len == 0) { return; } @@ -755,7 +742,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha if (info_sub_id != 0) { LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, info_sub_id); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } const auto& current_process_handle_table = @@ -764,7 +751,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha if (!process) { LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}", info_id, info_sub_id, handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } switch (info_id_type) { @@ -846,7 +833,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha } LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } case GetInfoType::IsCurrentProcessBeingDebugged: @@ -856,13 +843,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha case GetInfoType::RegisterResourceLimit: { if (handle != 0) { LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } if (info_sub_id != 0) { LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, info_sub_id); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } Process* const current_process = system.Kernel().CurrentProcess(); @@ -887,13 +874,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha if (handle != 0) { LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } if (info_sub_id >= Process::RANDOM_ENTROPY_SIZE) { LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", Process::RANDOM_ENTROPY_SIZE, info_sub_id); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); @@ -910,26 +897,26 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, info_sub_id); - return ERR_INVALID_COMBINATION; + return ResultInvalidCombination; } - const auto thread = system.Kernel().CurrentProcess()->GetHandleTable().Get<Thread>( + const auto thread = system.Kernel().CurrentProcess()->GetHandleTable().Get<KThread>( static_cast<Handle>(handle)); if (!thread) { LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", static_cast<Handle>(handle)); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } const auto& core_timing = system.CoreTiming(); - const auto& scheduler = system.CurrentScheduler(); + const auto& scheduler = *system.Kernel().CurrentScheduler(); const auto* const current_thread = scheduler.GetCurrentThread(); const bool same_thread = current_thread == thread.get(); const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks(); u64 out_ticks = 0; if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { - const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks(); + const u64 thread_ticks = current_thread->GetCpuTime(); out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) { @@ -942,13 +929,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha default: LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } } static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, u32 info_id, u32 handle, u32 sub_id_high) { - const u64 sub_id{static_cast<u64>(sub_id_low | (static_cast<u64>(sub_id_high) << 32))}; + const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; u64 res_value{}; const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)}; @@ -965,22 +952,22 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) if (!Common::Is4KBAligned(addr)) { LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (size == 0) { LOG_ERROR(Kernel_SVC, "Size is zero"); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!(addr < addr + size)) { LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } Process* const current_process{system.Kernel().CurrentProcess()}; @@ -988,28 +975,28 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) if (current_process->GetSystemResourceSize() == 0) { LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ERR_INVALID_STATE; + return ResultInvalidState; } if (!page_table.IsInsideAddressSpace(addr, size)) { LOG_ERROR(Kernel_SVC, "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (page_table.IsOutsideAliasRegion(addr, size)) { LOG_ERROR(Kernel_SVC, "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } return page_table.MapPhysicalMemory(addr, size); } static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size)); + return MapPhysicalMemory(system, addr, size); } /// Unmaps memory previously mapped via MapPhysicalMemory @@ -1019,22 +1006,22 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size if (!Common::Is4KBAligned(addr)) { LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (size == 0) { LOG_ERROR(Kernel_SVC, "Size is zero"); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!(addr < addr + size)) { LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } Process* const current_process{system.Kernel().CurrentProcess()}; @@ -1042,153 +1029,164 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size if (current_process->GetSystemResourceSize() == 0) { LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ERR_INVALID_STATE; + return ResultInvalidState; } if (!page_table.IsInsideAddressSpace(addr, size)) { LOG_ERROR(Kernel_SVC, "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (page_table.IsOutsideAliasRegion(addr, size)) { LOG_ERROR(Kernel_SVC, "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } return page_table.UnmapPhysicalMemory(addr, size); } static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size)); + return UnmapPhysicalMemory(system, addr, size); } /// Sets the thread activity -static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity); - if (activity > static_cast<u32>(ThreadActivity::Paused)) { - return ERR_INVALID_ENUM_VALUE; +static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, + thread_activity); + + // Validate the activity. + constexpr auto IsValidThreadActivity = [](ThreadActivity activity) { + return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused; + }; + if (!IsValidThreadActivity(thread_activity)) { + LOG_ERROR(Kernel_SVC, "Invalid thread activity value provided (activity={})", + thread_activity); + return ResultInvalidEnumValue; } - const auto* current_process = system.Kernel().CurrentProcess(); - const std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); + // Get the thread from its handle. + auto& kernel = system.Kernel(); + const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - if (thread->GetOwnerProcess() != current_process) { - LOG_ERROR(Kernel_SVC, - "The current process does not own the current thread, thread_handle={:08X} " - "thread_pid={}, " - "current_process_pid={}", - handle, thread->GetOwnerProcess()->GetProcessID(), - current_process->GetProcessID()); - return ERR_INVALID_HANDLE; + // Check that the activity is being set on a non-current thread for the current process. + if (thread->GetOwnerProcess() != kernel.CurrentProcess()) { + LOG_ERROR(Kernel_SVC, "Invalid owning process for the created thread."); + return ResultInvalidHandle; + } + if (thread.get() == GetCurrentThreadPointer(kernel)) { + LOG_ERROR(Kernel_SVC, "Thread is busy"); + return ResultBusy; } - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) { - LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); - return ERR_BUSY; + // Set the activity. + const auto set_result = thread->SetActivity(thread_activity); + if (set_result.IsError()) { + LOG_ERROR(Kernel_SVC, "Failed to set thread activity."); + return set_result; } - return thread->SetActivity(static_cast<ThreadActivity>(activity)); + return RESULT_SUCCESS; } -static ResultCode SetThreadActivity32(Core::System& system, Handle handle, u32 activity) { - return SetThreadActivity(system, handle, activity); +static ResultCode SetThreadActivity32(Core::System& system, Handle thread_handle, + Svc::ThreadActivity thread_activity) { + return SetThreadActivity(system, thread_handle, thread_activity); } /// Gets the thread context -static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle); +static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) { + LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context, + thread_handle); + // Get the thread from its handle. const auto* current_process = system.Kernel().CurrentProcess(); - const std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); + const std::shared_ptr<KThread> thread = + current_process->GetHandleTable().Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={})", thread_handle); + return ResultInvalidHandle; } + // Require the handle be to a non-current thread in the current process. if (thread->GetOwnerProcess() != current_process) { - LOG_ERROR(Kernel_SVC, - "The current process does not own the current thread, thread_handle={:08X} " - "thread_pid={}, " - "current_process_pid={}", - handle, thread->GetOwnerProcess()->GetProcessID(), - current_process->GetProcessID()); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Thread owning process is not the current process."); + return ResultInvalidHandle; } - - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) { - LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); - return ERR_BUSY; + if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) { + LOG_ERROR(Kernel_SVC, "Current thread is busy."); + return ResultBusy; } - Core::ARM_Interface::ThreadContext64 ctx = thread->GetContext64(); - // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. - ctx.pstate &= 0xFF0FFE20; - - // If 64-bit, we can just write the context registers directly and we're good. - // However, if 32-bit, we have to ensure some registers are zeroed out. - if (!current_process->Is64BitProcess()) { - std::fill(ctx.cpu_registers.begin() + 15, ctx.cpu_registers.end(), 0); - std::fill(ctx.vector_registers.begin() + 16, ctx.vector_registers.end(), u128{}); + // Get the thread context. + std::vector<u8> context; + const auto context_result = thread->GetThreadContext3(context); + if (context_result.IsError()) { + LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve thread context (result: {})", + context_result.raw); + return context_result; } - system.Memory().WriteBlock(thread_context, &ctx, sizeof(ctx)); + // Copy the thread context to user space. + system.Memory().WriteBlock(out_context, context.data(), context.size()); + return RESULT_SUCCESS; } -static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) { - return GetThreadContext(system, static_cast<VAddr>(thread_context), handle); +static ResultCode GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) { + return GetThreadContext(system, out_context, thread_handle); } /// Gets the priority for the specified thread -static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) { +static ResultCode GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) { LOG_TRACE(Kernel_SVC, "called"); + // Get the thread from its handle. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle); if (!thread) { - *priority = 0; - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", handle); + return ResultInvalidHandle; } - *priority = thread->GetPriority(); + // Get the thread's priority. + *out_priority = thread->GetPriority(); return RESULT_SUCCESS; } -static ResultCode GetThreadPriority32(Core::System& system, u32* priority, Handle handle) { - return GetThreadPriority(system, priority, handle); +static ResultCode GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) { + return GetThreadPriority(system, out_priority, handle); } /// Sets the priority for the specified thread static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 priority) { LOG_TRACE(Kernel_SVC, "called"); - if (priority > THREADPRIO_LOWEST) { - LOG_ERROR( - Kernel_SVC, - "An invalid priority was specified, expected {} but got {} for thread_handle={:08X}", - THREADPRIO_LOWEST, priority, handle); - return ERR_INVALID_THREAD_PRIORITY; + // Validate the priority. + if (HighestThreadPriority > priority || priority > LowestThreadPriority) { + LOG_ERROR(Kernel_SVC, "Invalid thread priority specified (priority={})", priority); + return ResultInvalidPriority; } - const auto* const current_process = system.Kernel().CurrentProcess(); - - std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); + // Get the thread from its handle. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid handle provided (handle={:08X})", handle); + return ResultInvalidHandle; } - thread->SetPriority(priority); - + // Set the thread priority. + thread->SetBasePriority(priority); return RESULT_SUCCESS; } @@ -1215,23 +1213,23 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han if (!Common::Is4KBAligned(addr)) { LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (size == 0) { LOG_ERROR(Kernel_SVC, "Size is 0"); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!IsValidAddressRange(addr, size)) { LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}", addr, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } const auto permission_type = static_cast<Memory::MemoryPermission>(permissions); @@ -1239,7 +1237,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han Memory::MemoryPermission::ReadAndWrite) { LOG_ERROR(Kernel_SVC, "Expected Read or ReadWrite permission but got permissions=0x{:08X}", permissions); - return ERR_INVALID_MEMORY_PERMISSIONS; + return ResultInvalidMemoryPermissions; } auto* const current_process{system.Kernel().CurrentProcess()}; @@ -1250,7 +1248,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han "Addr does not fit within the valid region, addr=0x{:016X}, " "size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (page_table.IsInsideHeapRegion(addr, size)) { @@ -1258,7 +1256,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han "Addr does not fit within the heap region, addr=0x{:016X}, " "size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } if (page_table.IsInsideAliasRegion(addr, size)) { @@ -1266,14 +1264,14 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han "Address does not fit within the map region, addr=0x{:016X}, " "size=0x{:016X}", addr, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } auto shared_memory{current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle)}; if (!shared_memory) { LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}", shared_memory_handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } return shared_memory->Map(*current_process, addr, size, permission_type); @@ -1281,8 +1279,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr, u32 size, u32 permissions) { - return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr), - static_cast<std::size_t>(size), permissions); + return MapSharedMemory(system, shared_memory_handle, addr, size, permissions); } static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address, @@ -1295,7 +1292,7 @@ static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_add if (!process) { LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", process_handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } auto& memory{system.Memory()}; @@ -1342,18 +1339,18 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand if (!Common::Is4KBAligned(src_address)) { LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", src_address); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(dst_address)) { LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", dst_address); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (size == 0 || !Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!IsValidAddressRange(dst_address, size)) { @@ -1361,7 +1358,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand "Destination address range overflows the address space (dst_address=0x{:016X}, " "size=0x{:016X}).", dst_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!IsValidAddressRange(src_address, size)) { @@ -1369,7 +1366,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand "Source address range overflows the address space (src_address=0x{:016X}, " "size=0x{:016X}).", src_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); @@ -1377,7 +1374,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand if (!process) { LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", process_handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } auto& page_table = process->PageTable(); @@ -1386,7 +1383,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand "Source address range is not within the address space (src_address=0x{:016X}, " "size=0x{:016X}).", src_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!page_table.IsInsideASLRRegion(dst_address, size)) { @@ -1394,7 +1391,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " "size=0x{:016X}).", dst_address, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } return page_table.MapProcessCodeMemory(dst_address, src_address, size); @@ -1410,18 +1407,18 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha if (!Common::Is4KBAligned(dst_address)) { LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", dst_address); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(src_address)) { LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", src_address); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (size == 0 || Common::Is4KBAligned(size)) { LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); - return ERR_INVALID_SIZE; + return ResultInvalidSize; } if (!IsValidAddressRange(dst_address, size)) { @@ -1429,7 +1426,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha "Destination address range overflows the address space (dst_address=0x{:016X}, " "size=0x{:016X}).", dst_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!IsValidAddressRange(src_address, size)) { @@ -1437,7 +1434,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha "Source address range overflows the address space (src_address=0x{:016X}, " "size=0x{:016X}).", src_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); @@ -1445,7 +1442,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha if (!process) { LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", process_handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } auto& page_table = process->PageTable(); @@ -1454,7 +1451,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha "Source address range is not within the address space (src_address=0x{:016X}, " "size=0x{:016X}).", src_address, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } if (!page_table.IsInsideASLRRegion(dst_address, size)) { @@ -1462,7 +1459,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " "size=0x{:016X}).", dst_address, size); - return ERR_INVALID_MEMORY_RANGE; + return ResultInvalidMemoryRange; } return page_table.UnmapProcessCodeMemory(dst_address, src_address, size); @@ -1480,62 +1477,67 @@ static void ExitProcess(Core::System& system) { current_process->PrepareForTermination(); // Kill the current thread - system.CurrentScheduler().GetCurrentThread()->Stop(); + system.Kernel().CurrentScheduler()->GetCurrentThread()->Exit(); } static void ExitProcess32(Core::System& system) { ExitProcess(system); } +static constexpr bool IsValidCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES)); +} + /// Creates a new thread static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, - VAddr stack_top, u32 priority, s32 processor_id) { + VAddr stack_bottom, u32 priority, s32 core_id) { LOG_DEBUG(Kernel_SVC, - "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, " - "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", - entry_point, arg, stack_top, priority, processor_id, *out_handle); + "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " + "priority=0x{:08X}, core_id=0x{:08X}", + entry_point, arg, stack_bottom, priority, core_id); - auto* const current_process = system.Kernel().CurrentProcess(); - - if (processor_id == THREADPROCESSORID_IDEAL) { - // Set the target CPU to the one specified by the process. - processor_id = current_process->GetIdealCore(); - ASSERT(processor_id != THREADPROCESSORID_IDEAL); + // Adjust core id, if it's the default magic. + auto& kernel = system.Kernel(); + auto& process = *kernel.CurrentProcess(); + if (core_id == IdealCoreUseProcessValue) { + core_id = process.GetIdealCoreId(); } - if (processor_id < THREADPROCESSORID_0 || processor_id > THREADPROCESSORID_3) { - LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id); - return ERR_INVALID_PROCESSOR_ID; + // Validate arguments. + if (!IsValidCoreId(core_id)) { + LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id); + return ResultInvalidCoreId; } - - const u64 core_mask = current_process->GetCoreMask(); - if ((core_mask | (1ULL << processor_id)) != core_mask) { - LOG_ERROR(Kernel_SVC, "Invalid thread core specified ({})", processor_id); - return ERR_INVALID_PROCESSOR_ID; + if (((1ULL << core_id) & process.GetCoreMask()) == 0) { + LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id); + return ResultInvalidCoreId; } - if (priority > THREADPRIO_LOWEST) { - LOG_ERROR(Kernel_SVC, - "Invalid thread priority specified ({}). Must be within the range 0-64", - priority); - return ERR_INVALID_THREAD_PRIORITY; + if (HighestThreadPriority > priority || priority > LowestThreadPriority) { + LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority); + return ResultInvalidPriority; } - - if (((1ULL << priority) & current_process->GetPriorityMask()) == 0) { - LOG_ERROR(Kernel_SVC, "Invalid thread priority specified ({})", priority); - return ERR_INVALID_THREAD_PRIORITY; + if (!process.CheckThreadPriority(priority)) { + LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority); + return ResultInvalidPriority; } - auto& kernel = system.Kernel(); - - ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1)); + KScopedResourceReservation thread_reservation( + kernel.CurrentProcess(), LimitableResource::Threads, 1, + system.CoreTiming().GetGlobalTimeNs().count() + 100000000); + if (!thread_reservation.Succeeded()) { + LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); + return ResultResourceLimitedExceeded; + } - ThreadType type = THREADTYPE_USER; - CASCADE_RESULT(std::shared_ptr<Thread> thread, - Thread::Create(system, type, "", entry_point, priority, arg, processor_id, - stack_top, current_process)); + std::shared_ptr<KThread> thread; + { + KScopedLightLock lk{process.GetStateLock()}; + CASCADE_RESULT(thread, KThread::Create(system, ThreadType::User, "", entry_point, priority, + arg, core_id, stack_bottom, &process)); + } - const auto new_thread_handle = current_process->GetHandleTable().Create(thread); + const auto new_thread_handle = process.GetHandleTable().Create(thread); if (new_thread_handle.Failed()) { LOG_ERROR(Kernel_SVC, "Failed to create handle with error=0x{:X}", new_thread_handle.Code().raw); @@ -1546,31 +1548,38 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e // Set the thread name for debugging purposes. thread->SetName( fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle)); + thread_reservation.Commit(); return RESULT_SUCCESS; } static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) { - return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg), - static_cast<VAddr>(stack_top), priority, processor_id); + return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); } /// Starts the thread for the provided handle static ResultCode StartThread(Core::System& system, Handle thread_handle) { LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + // Get the thread from its handle. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", - thread_handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - ASSERT(thread->GetStatus() == ThreadStatus::Dormant); + // Try to start the thread. + const auto run_result = thread->Run(); + if (run_result.IsError()) { + LOG_ERROR(Kernel_SVC, + "Unable to successfuly start thread (thread handle={:08X}, result={})", + thread_handle, run_result.raw); + return run_result; + } - return thread->Start(); + return RESULT_SUCCESS; } static ResultCode StartThread32(Core::System& system, Handle thread_handle) { @@ -1581,9 +1590,9 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) { static void ExitThread(Core::System& system) { LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); - auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); - system.GlobalScheduler().RemoveThread(SharedFrom(current_thread)); - current_thread->Stop(); + auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); + system.GlobalSchedulerContext().RemoveThread(SharedFrom(current_thread)); + current_thread->Exit(); } static void ExitThread32(Core::System& system) { @@ -1592,277 +1601,190 @@ static void ExitThread32(Core::System& system) { /// Sleep the current thread static void SleepThread(Core::System& system, s64 nanoseconds) { - LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds); - - enum class SleepType : s64 { - YieldWithoutLoadBalancing = 0, - YieldWithLoadBalancing = -1, - YieldAndWaitForLoadBalancing = -2, - }; - - auto& scheduler = system.CurrentScheduler(); - auto* const current_thread = scheduler.GetCurrentThread(); - bool is_redundant = false; - - if (nanoseconds <= 0) { - switch (static_cast<SleepType>(nanoseconds)) { - case SleepType::YieldWithoutLoadBalancing: { - auto pair = current_thread->YieldSimple(); - is_redundant = pair.second; - break; - } - case SleepType::YieldWithLoadBalancing: { - auto pair = current_thread->YieldAndBalanceLoad(); - is_redundant = pair.second; - break; - } - case SleepType::YieldAndWaitForLoadBalancing: { - auto pair = current_thread->YieldAndWaitForLoadBalancing(); - is_redundant = pair.second; - break; - } - default: - UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); - } + auto& kernel = system.Kernel(); + const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); + + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); + + // When the input tick is positive, sleep. + if (nanoseconds > 0) { + // Convert the timeout from nanoseconds to ticks. + // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + + // Sleep. + // NOTE: Nintendo does not check the result of this sleep. + static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); + } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { + KScheduler::YieldWithoutCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::WithCoreMigration) { + KScheduler::YieldWithCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::ToAnyThread) { + KScheduler::YieldToAnyThread(kernel); } else { - current_thread->Sleep(nanoseconds); - } - - if (is_redundant && !system.Kernel().IsMulticore()) { - system.Kernel().ExitSVCProfile(); - system.CoreTiming().AddTicks(1000U); - system.GetCpuManager().PreemptSingleCore(); - system.Kernel().EnterSVCProfile(); + // Nintendo does nothing at all if an otherwise invalid value is passed. + UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { - const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) | - (static_cast<u64>(nanoseconds_high) << 32)); + const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); SleepThread(system, nanoseconds); } /// Wait process wide key atomic -static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_addr, - VAddr condition_variable_addr, Handle thread_handle, - s64 nano_seconds) { - LOG_TRACE( - Kernel_SVC, - "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}", - mutex_addr, condition_variable_addr, thread_handle, nano_seconds); - - if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) { - LOG_ERROR( - Kernel_SVC, - "Given mutex address must not be within the kernel address space. address=0x{:016X}", - mutex_addr); - return ERR_INVALID_ADDRESS_STATE; - } - - if (!Common::IsWordAligned(mutex_addr)) { - LOG_ERROR(Kernel_SVC, "Given mutex address must be word-aligned. address=0x{:016X}", - mutex_addr); - return ERR_INVALID_ADDRESS; - } - - ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4)); - auto& kernel = system.Kernel(); - Handle event_handle; - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); - auto* const current_process = system.Kernel().CurrentProcess(); - { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds); - const auto& handle_table = current_process->GetHandleTable(); - std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); - ASSERT(thread); - - current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - - if (thread->IsPendingTermination()) { - lock.CancelSleep(); - return ERR_THREAD_TERMINATING; - } - - const auto release_result = current_process->GetMutex().Release(mutex_addr); - if (release_result.IsError()) { - lock.CancelSleep(); - return release_result; - } - - if (nano_seconds == 0) { - lock.CancelSleep(); - return RESULT_TIMEOUT; - } - - current_thread->SetCondVarWaitAddress(condition_variable_addr); - current_thread->SetMutexWaitAddress(mutex_addr); - current_thread->SetWaitHandle(thread_handle); - current_thread->SetStatus(ThreadStatus::WaitCondVar); - current_process->InsertConditionVariableThread(SharedFrom(current_thread)); - } - - if (event_handle != InvalidHandle) { - auto& time_manager = kernel.TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - - { - SchedulerLock lock(kernel); - - auto* owner = current_thread->GetLockOwner(); - if (owner != nullptr) { - owner->RemoveMutexWaiter(SharedFrom(current_thread)); +static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, + u32 tag, s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, + cv_key, tag, timeout_ns); + + // Validate input. + if (Memory::IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); } - - current_process->RemoveConditionVariableThread(SharedFrom(current_thread)); + } else { + timeout = timeout_ns; } - // Note: Deliberately don't attempt to inherit the lock owner's priority. - return current_thread->GetSignalingResult(); + // Wait on the condition variable. + return system.Kernel().CurrentProcess()->WaitConditionVariable( + address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout); } -static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr, - u32 condition_variable_addr, Handle thread_handle, - u32 nanoseconds_low, u32 nanoseconds_high) { - const s64 nanoseconds = - static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32)); - return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr), - static_cast<VAddr>(condition_variable_addr), thread_handle, - nanoseconds); +static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, + u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns); } /// Signal process wide key -static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_addr, s32 target) { - LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", - condition_variable_addr, target); +static void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) { + LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count); - ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4)); + // Signal the condition variable. + return system.Kernel().CurrentProcess()->SignalConditionVariable( + Common::AlignDown(cv_key, sizeof(u32)), count); +} - // Retrieve a list of all threads that are waiting for this condition variable. - auto& kernel = system.Kernel(); - SchedulerLock lock(kernel); - auto* const current_process = kernel.CurrentProcess(); - std::vector<std::shared_ptr<Thread>> waiting_threads = - current_process->GetConditionVariableThreads(condition_variable_addr); - - // Only process up to 'target' threads, unless 'target' is less equal 0, in which case process - // them all. - std::size_t last = waiting_threads.size(); - if (target > 0) { - last = std::min(waiting_threads.size(), static_cast<std::size_t>(target)); - } - for (std::size_t index = 0; index < last; ++index) { - auto& thread = waiting_threads[index]; - - ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr); - - // liberate Cond Var Thread. - current_process->RemoveConditionVariableThread(thread); - - const std::size_t current_core = system.CurrentCoreIndex(); - auto& monitor = system.Monitor(); - - // Atomically read the value of the mutex. - u32 mutex_val = 0; - u32 update_val = 0; - const VAddr mutex_address = thread->GetMutexWaitAddress(); - do { - // If the mutex is not yet acquired, acquire it. - mutex_val = monitor.ExclusiveRead32(current_core, mutex_address); - - if (mutex_val != 0) { - update_val = mutex_val | Mutex::MutexHasWaitersFlag; - } else { - update_val = thread->GetWaitHandle(); - } - } while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val)); - monitor.ClearExclusive(); - if (mutex_val == 0) { - // We were able to acquire the mutex, resume this thread. - auto* const lock_owner = thread->GetLockOwner(); - if (lock_owner != nullptr) { - lock_owner->RemoveMutexWaiter(thread); - } +static void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) { + SignalProcessWideKey(system, cv_key, count); +} - thread->SetLockOwner(nullptr); - thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS); - thread->ResumeFromWait(); - } else { - // The mutex is already owned by some other thread, make this thread wait on it. - const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - auto owner = handle_table.Get<Thread>(owner_handle); - ASSERT(owner); - if (thread->GetStatus() == ThreadStatus::WaitCondVar) { - thread->SetStatus(ThreadStatus::WaitMutex); - } +namespace { - owner->AddMutexWaiter(thread); - } +constexpr bool IsValidSignalType(Svc::SignalType type) { + switch (type) { + case Svc::SignalType::Signal: + case Svc::SignalType::SignalAndIncrementIfEqual: + case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: + return true; + default: + return false; } } -static void SignalProcessWideKey32(Core::System& system, u32 condition_variable_addr, s32 target) { - SignalProcessWideKey(system, condition_variable_addr, target); +constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { + switch (type) { + case Svc::ArbitrationType::WaitIfLessThan: + case Svc::ArbitrationType::DecrementAndWaitIfLessThan: + case Svc::ArbitrationType::WaitIfEqual: + return true; + default: + return false; + } } -// Wait for an address (via Address Arbiter) -static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value, - s64 timeout) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", address, - type, value, timeout); - - // If the passed address is a kernel virtual address, return invalid memory state. - if (Core::Memory::IsKernelVirtualAddress(address)) { - LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address); - return ERR_INVALID_ADDRESS_STATE; - } +} // namespace - // If the address is not properly aligned to 4 bytes, return invalid address. - if (!Common::IsWordAligned(address)) { - LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address); - return ERR_INVALID_ADDRESS; +// Wait for an address (via Address Arbiter) +static ResultCode WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type, + s32 value, s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", + address, arb_type, value, timeout_ns); + + // Validate input. + if (Memory::IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidArbitrationType(arb_type)) { + LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type); + return ResultInvalidEnumValue; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; } - const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type); - auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter(); - const ResultCode result = - address_arbiter.WaitForAddress(address, arbitration_type, value, timeout); - return result; + return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout); } -static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value, - u32 timeout_low, u32 timeout_high) { - s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32)); - return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout); +static ResultCode WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type, + s32 value, u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitForAddress(system, address, arb_type, value, timeout); } // Signals to an address (via Address Arbiter) -static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value, - s32 num_to_wake) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", - address, type, value, num_to_wake); +static ResultCode SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type, + s32 value, s32 count) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", + address, signal_type, value, count); - // If the passed address is a kernel virtual address, return invalid memory state. - if (Core::Memory::IsKernelVirtualAddress(address)) { - LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address); - return ERR_INVALID_ADDRESS_STATE; + // Validate input. + if (Memory::IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; } - - // If the address is not properly aligned to 4 bytes, return invalid address. - if (!Common::IsWordAligned(address)) { - LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address); - return ERR_INVALID_ADDRESS; + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidSignalType(signal_type)) { + LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type); + return ResultInvalidEnumValue; } - const auto signal_type = static_cast<AddressArbiter::SignalType>(type); - auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter(); - return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake); + return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value, + count); } -static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value, - s32 num_to_wake) { - return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake); +static ResultCode SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type, + s32 value, s32 count) { + return SignalToAddress(system, address, signal_type, value, count); } static void KernelDebug([[maybe_unused]] Core::System& system, @@ -1893,7 +1815,7 @@ static u64 GetSystemTick(Core::System& system) { } static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { - u64 time = GetSystemTick(system); + const auto time = GetSystemTick(system); *time_low = static_cast<u32>(time); *time_high = static_cast<u32>(time >> 32); } @@ -1914,20 +1836,28 @@ static ResultCode CloseHandle32(Core::System& system, Handle handle) { static ResultCode ResetSignal(Core::System& system, Handle handle) { LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); + // Get the current handle table. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - auto event = handle_table.Get<ReadableEvent>(handle); - if (event) { - return event->Reset(); + // Try to reset as readable event. + { + auto readable_event = handle_table.Get<KReadableEvent>(handle); + if (readable_event) { + return readable_event->Reset(); + } } - auto process = handle_table.Get<Process>(handle); - if (process) { - return process->ClearSignalState(); + // Try to reset as process. + { + auto process = handle_table.Get<Process>(handle); + if (process) { + return process->Reset(); + } } - LOG_ERROR(Kernel_SVC, "Invalid handle (0x{:08X})", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle); + + return ResultInvalidHandle; } static ResultCode ResetSignal32(Core::System& system, Handle handle) { @@ -1943,18 +1873,18 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd if (!Common::Is4KBAligned(addr)) { LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!Common::Is4KBAligned(size) || size == 0) { LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size); - return ERR_INVALID_ADDRESS; + return ResultInvalidAddress; } if (!IsValidAddressRange(addr, size)) { LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})", addr, size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } const auto perms{static_cast<Memory::MemoryPermission>(permissions)}; @@ -1962,10 +1892,17 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd perms == Memory::MemoryPermission::Write) { LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})", permissions); - return ERR_INVALID_MEMORY_PERMISSIONS; + return ResultInvalidMemoryPermissions; } auto& kernel = system.Kernel(); + // Reserve a new transfer memory from the process resource limit. + KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), + LimitableResource::TransferMemory); + if (!trmem_reservation.Succeeded()) { + LOG_ERROR(Kernel_SVC, "Could not reserve a new transfer memory"); + return ResultResourceLimitedExceeded; + } auto transfer_mem_handle = TransferMemory::Create(kernel, system.Memory(), addr, size, perms); if (const auto reserve_result{transfer_mem_handle->Reserve()}; reserve_result.IsError()) { @@ -1977,6 +1914,7 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd if (result.Failed()) { return result.Code(); } + trmem_reservation.Commit(); *handle = *result; return RESULT_SUCCESS; @@ -1984,176 +1922,207 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size, u32 permissions) { - return CreateTransferMemory(system, handle, static_cast<VAddr>(addr), - static_cast<std::size_t>(size), permissions); + return CreateTransferMemory(system, handle, addr, size, permissions); } -static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core, - u64* mask) { +static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, + u64* out_affinity_mask) { LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); + // Get the thread from its handle. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", - thread_handle); - *core = 0; - *mask = 0; - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle specified (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - *core = thread->GetIdealCore(); - *mask = thread->GetAffinityMask(); + // Get the core mask. + const auto result = thread->GetCoreMask(out_core_id, out_affinity_mask); + if (result.IsError()) { + LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve core mask (result={})", result.raw); + return result; + } return RESULT_SUCCESS; } -static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, u32* core, - u32* mask_low, u32* mask_high) { - u64 mask{}; - const auto result = GetThreadCoreMask(system, thread_handle, core, &mask); - *mask_high = static_cast<u32>(mask >> 32); - *mask_low = static_cast<u32>(mask); +static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, + u32* out_affinity_mask_low, u32* out_affinity_mask_high) { + u64 out_affinity_mask{}; + const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask); + *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32); + *out_affinity_mask_low = static_cast<u32>(out_affinity_mask); return result; } -static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core, +static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, u64 affinity_mask) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}", - thread_handle, core, affinity_mask); - - const auto* const current_process = system.Kernel().CurrentProcess(); + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core_id=0x{:X}, affinity_mask=0x{:016X}", + thread_handle, core_id, affinity_mask); - if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) { - const u8 ideal_cpu_core = current_process->GetIdealCore(); + const auto& current_process = *system.Kernel().CurrentProcess(); - ASSERT(ideal_cpu_core != static_cast<u8>(THREADPROCESSORID_IDEAL)); - - // Set the target CPU to the ideal core specified by the process. - core = ideal_cpu_core; - affinity_mask = 1ULL << core; + // Determine the core id/affinity mask. + if (core_id == Svc::IdealCoreUseProcessValue) { + core_id = current_process.GetIdealCoreId(); + affinity_mask = (1ULL << core_id); } else { - const u64 core_mask = current_process->GetCoreMask(); - - if ((core_mask | affinity_mask) != core_mask) { - LOG_ERROR( - Kernel_SVC, - "Invalid processor ID specified (core_mask=0x{:08X}, affinity_mask=0x{:016X})", - core_mask, affinity_mask); - return ERR_INVALID_PROCESSOR_ID; + // Validate the affinity mask. + const u64 process_core_mask = current_process.GetCoreMask(); + if ((affinity_mask | process_core_mask) != process_core_mask) { + LOG_ERROR(Kernel_SVC, + "Affinity mask does match the process core mask (affinity mask={:016X}, core " + "mask={:016X})", + affinity_mask, process_core_mask); + return ResultInvalidCoreId; } - if (affinity_mask == 0) { - LOG_ERROR(Kernel_SVC, "Specfified affinity mask is zero."); - return ERR_INVALID_COMBINATION; + LOG_ERROR(Kernel_SVC, "Affinity mask is zero."); + return ResultInvalidCombination; } - if (core < Core::Hardware::NUM_CPU_CORES) { - if ((affinity_mask & (1ULL << core)) == 0) { - LOG_ERROR(Kernel_SVC, - "Core is not enabled for the current mask, core={}, mask={:016X}", core, - affinity_mask); - return ERR_INVALID_COMBINATION; + // Validate the core id. + if (IsValidCoreId(core_id)) { + if (((1ULL << core_id) & affinity_mask) == 0) { + LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id); + return ResultInvalidCombination; + } + } else { + if (core_id != IdealCoreNoUpdate && core_id != IdealCoreDontCare) { + LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id); + return ResultInvalidCoreId; } - } else if (core != static_cast<u32>(THREADPROCESSORID_DONT_CARE) && - core != static_cast<u32>(THREADPROCESSORID_DONT_UPDATE)) { - LOG_ERROR(Kernel_SVC, "Invalid processor ID specified (core={}).", core); - return ERR_INVALID_PROCESSOR_ID; } } - const auto& handle_table = current_process->GetHandleTable(); - const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); + // Get the thread from its handle. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle); if (!thread) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", - thread_handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Invalid thread handle (handle={:08X})", thread_handle); + return ResultInvalidHandle; } - return thread->SetCoreAndAffinityMask(core, affinity_mask); + // Set the core mask. + const auto set_result = thread->SetCoreMask(core_id, affinity_mask); + if (set_result.IsError()) { + LOG_ERROR(Kernel_SVC, "Unable to successfully set core mask (result={})", set_result.raw); + return set_result; + } + return RESULT_SUCCESS; } -static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core, +static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, u32 affinity_mask_low, u32 affinity_mask_high) { - const u64 affinity_mask = - static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32); - return SetThreadCoreMask(system, thread_handle, core, affinity_mask); + const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); + return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask); } -static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) { - LOG_DEBUG(Kernel_SVC, "called"); +static ResultCode SignalEvent(Core::System& system, Handle event_handle) { + LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); auto& kernel = system.Kernel(); - const auto [readable_event, writable_event] = - WritableEvent::CreateEventPair(kernel, "CreateEvent"); + // Get the current handle table. + const HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable(); - HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable(); - - const auto write_create_result = handle_table.Create(writable_event); - if (write_create_result.Failed()) { - return write_create_result.Code(); + // Reserve a new event from the process resource limit. + KScopedResourceReservation event_reservation(kernel.CurrentProcess(), + LimitableResource::Events); + if (!event_reservation.Succeeded()) { + LOG_ERROR(Kernel, "Could not reserve a new event"); + return ResultResourceLimitedExceeded; } - *write_handle = *write_create_result; - const auto read_create_result = handle_table.Create(readable_event); - if (read_create_result.Failed()) { - handle_table.Close(*write_create_result); - return read_create_result.Code(); + // Get the writable event. + auto writable_event = handle_table.Get<KWritableEvent>(event_handle); + if (!writable_event) { + LOG_ERROR(Kernel_SVC, "Invalid event handle provided (handle={:08X})", event_handle); + return ResultInvalidHandle; } - *read_handle = *read_create_result; - LOG_DEBUG(Kernel_SVC, - "successful. Writable event handle=0x{:08X}, Readable event handle=0x{:08X}", - *write_create_result, *read_create_result); - return RESULT_SUCCESS; + // Commit the successfuly reservation. + event_reservation.Commit(); + + return writable_event->Signal(); } -static ResultCode CreateEvent32(Core::System& system, Handle* write_handle, Handle* read_handle) { - return CreateEvent(system, write_handle, read_handle); +static ResultCode SignalEvent32(Core::System& system, Handle event_handle) { + return SignalEvent(system, event_handle); } -static ResultCode ClearEvent(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle); +static ResultCode ClearEvent(Core::System& system, Handle event_handle) { + LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + // Get the current handle table. const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - auto writable_event = handle_table.Get<WritableEvent>(handle); - if (writable_event) { - writable_event->Clear(); - return RESULT_SUCCESS; + // Try to clear the writable event. + { + auto writable_event = handle_table.Get<KWritableEvent>(event_handle); + if (writable_event) { + return writable_event->Clear(); + } } - auto readable_event = handle_table.Get<ReadableEvent>(handle); - if (readable_event) { - readable_event->Clear(); - return RESULT_SUCCESS; + // Try to clear the readable event. + { + auto readable_event = handle_table.Get<KReadableEvent>(event_handle); + if (readable_event) { + return readable_event->Clear(); + } } - LOG_ERROR(Kernel_SVC, "Event handle does not exist, handle=0x{:08X}", handle); - return ERR_INVALID_HANDLE; + LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle); + + return ResultInvalidHandle; } -static ResultCode ClearEvent32(Core::System& system, Handle handle) { - return ClearEvent(system, handle); +static ResultCode ClearEvent32(Core::System& system, Handle event_handle) { + return ClearEvent(system, event_handle); } -static ResultCode SignalEvent(Core::System& system, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle); +static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { + LOG_DEBUG(Kernel_SVC, "called"); - HandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - auto writable_event = handle_table.Get<WritableEvent>(handle); + // Get the kernel reference and handle table. + auto& kernel = system.Kernel(); + HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable(); - if (!writable_event) { - LOG_ERROR(Kernel_SVC, "Non-existent writable event handle used (0x{:08X})", handle); - return ERR_INVALID_HANDLE; + // Create a new event. + const auto event = KEvent::Create(kernel, "CreateEvent"); + if (!event) { + LOG_ERROR(Kernel_SVC, "Unable to create new events. Event creation limit reached."); + return ResultOutOfResource; + } + + // Initialize the event. + event->Initialize(); + + // Add the writable event to the handle table. + const auto write_create_result = handle_table.Create(event->GetWritableEvent()); + if (write_create_result.Failed()) { + return write_create_result.Code(); + } + *out_write = *write_create_result; + + // Add the writable event to the handle table. + auto handle_guard = SCOPE_GUARD({ handle_table.Close(*write_create_result); }); + + // Add the readable event to the handle table. + const auto read_create_result = handle_table.Create(event->GetReadableEvent()); + if (read_create_result.Failed()) { + return read_create_result.Code(); } + *out_read = *read_create_result; - writable_event->Signal(); + // We succeeded. + handle_guard.Cancel(); return RESULT_SUCCESS; } -static ResultCode SignalEvent32(Core::System& system, Handle handle) { - return SignalEvent(system, handle); +static ResultCode CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) { + return CreateEvent(system, out_write, out_read); } static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { @@ -2169,13 +2138,13 @@ static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_ if (!process) { LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", process_handle); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } const auto info_type = static_cast<InfoType>(type); if (info_type != InfoType::Status) { LOG_ERROR(Kernel_SVC, "Expected info_type to be Status but got {} instead", type); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } *out = static_cast<u64>(process->GetStatus()); @@ -2187,7 +2156,7 @@ static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) LOG_DEBUG(Kernel_SVC, "called"); auto& kernel = system.Kernel(); - auto resource_limit = ResourceLimit::Create(kernel); + auto resource_limit = std::make_shared<KResourceLimit>(kernel, system); auto* const current_process = kernel.CurrentProcess(); ASSERT(current_process != nullptr); @@ -2234,30 +2203,30 @@ static ResultCode SetResourceLimitLimitValue(Core::System& system, Handle resour LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}, Value={}", resource_limit, resource_type, value); - const auto type = static_cast<ResourceType>(resource_type); + const auto type = static_cast<LimitableResource>(resource_type); if (!IsValidResourceType(type)) { LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type); - return ERR_INVALID_ENUM_VALUE; + return ResultInvalidEnumValue; } auto* const current_process = system.Kernel().CurrentProcess(); ASSERT(current_process != nullptr); auto resource_limit_object = - current_process->GetHandleTable().Get<ResourceLimit>(resource_limit); + current_process->GetHandleTable().Get<KResourceLimit>(resource_limit); if (!resource_limit_object) { LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}", resource_limit); - return ERR_INVALID_HANDLE; + return ResultInvalidHandle; } const auto set_result = resource_limit_object->SetLimitValue(type, static_cast<s64>(value)); if (set_result.IsError()) { - LOG_ERROR( - Kernel_SVC, - "Attempted to lower resource limit ({}) for category '{}' below its current value ({})", - resource_limit_object->GetMaxResourceValue(type), resource_type, - resource_limit_object->GetCurrentResourceValue(type)); + LOG_ERROR(Kernel_SVC, + "Attempted to lower resource limit ({}) for category '{}' below its current " + "value ({})", + resource_limit_object->GetLimitValue(type), resource_type, + resource_limit_object->GetCurrentValue(type)); return set_result; } @@ -2274,7 +2243,7 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes, LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}", out_process_ids_size); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } const auto& kernel = system.Kernel(); @@ -2284,7 +2253,7 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes, out_process_ids, total_copy_size)) { LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", out_process_ids, out_process_ids + total_copy_size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } auto& memory = system.Memory(); @@ -2313,7 +2282,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd if ((out_thread_ids_size & 0xF0000000) != 0) { LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}", out_thread_ids_size); - return ERR_OUT_OF_RANGE; + return ResultOutOfRange; } const auto* const current_process = system.Kernel().CurrentProcess(); @@ -2323,7 +2292,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) { LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", out_thread_ids, out_thread_ids + total_copy_size); - return ERR_INVALID_ADDRESS_STATE; + return ResultInvalidCurrentMemory; } auto& memory = system.Memory(); @@ -2341,9 +2310,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd return RESULT_SUCCESS; } -static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address, - u32 size) { - // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope +static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system, + [[maybe_unused]] Handle handle, + [[maybe_unused]] u32 address, [[maybe_unused]] u32 size) { + // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op, // as all emulation is done in the same cache level in host architecture, thus data cache // does not need flushing. LOG_DEBUG(Kernel_SVC, "called"); @@ -2639,6 +2609,9 @@ void Call(Core::System& system, u32 immediate) { auto& kernel = system.Kernel(); kernel.EnterSVCProfile(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + thread->SetIsCallingSvc(); + const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate) : GetSVCInfo32(immediate); if (info) { @@ -2652,6 +2625,12 @@ void Call(Core::System& system, u32 immediate) { } kernel.ExitSVCProfile(); + + if (!thread->IsCallingSvc()) { + auto* host_context = thread->GetHostContext().get(); + host_context->Rewind(); + } + system.EnterDynarmicProfile(); } diff --git a/src/core/hle/kernel/svc_common.h b/src/core/hle/kernel/svc_common.h new file mode 100644 index 000000000..4af049551 --- /dev/null +++ b/src/core/hle/kernel/svc_common.h @@ -0,0 +1,14 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Kernel::Svc { + +constexpr s32 ArgumentHandleCountMax = 0x40; +constexpr u32 HandleWaitMask{1u << 30}; + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_results.h b/src/core/hle/kernel/svc_results.h new file mode 100644 index 000000000..a26d9f2c9 --- /dev/null +++ b/src/core/hle/kernel/svc_results.h @@ -0,0 +1,41 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Kernel { + +// Confirmed Switch kernel error codes + +constexpr ResultCode ResultMaxConnectionsReached{ErrorModule::Kernel, 7}; +constexpr ResultCode ResultInvalidCapabilityDescriptor{ErrorModule::Kernel, 14}; +constexpr ResultCode ResultNoSynchronizationObject{ErrorModule::Kernel, 57}; +constexpr ResultCode ResultTerminationRequested{ErrorModule::Kernel, 59}; +constexpr ResultCode ResultInvalidSize{ErrorModule::Kernel, 101}; +constexpr ResultCode ResultInvalidAddress{ErrorModule::Kernel, 102}; +constexpr ResultCode ResultOutOfResource{ErrorModule::Kernel, 103}; +constexpr ResultCode ResultOutOfMemory{ErrorModule::Kernel, 104}; +constexpr ResultCode ResultHandleTableFull{ErrorModule::Kernel, 105}; +constexpr ResultCode ResultInvalidCurrentMemory{ErrorModule::Kernel, 106}; +constexpr ResultCode ResultInvalidMemoryPermissions{ErrorModule::Kernel, 108}; +constexpr ResultCode ResultInvalidMemoryRange{ErrorModule::Kernel, 110}; +constexpr ResultCode ResultInvalidPriority{ErrorModule::Kernel, 112}; +constexpr ResultCode ResultInvalidCoreId{ErrorModule::Kernel, 113}; +constexpr ResultCode ResultInvalidHandle{ErrorModule::Kernel, 114}; +constexpr ResultCode ResultInvalidPointer{ErrorModule::Kernel, 115}; +constexpr ResultCode ResultInvalidCombination{ErrorModule::Kernel, 116}; +constexpr ResultCode ResultTimedOut{ErrorModule::Kernel, 117}; +constexpr ResultCode ResultCancelled{ErrorModule::Kernel, 118}; +constexpr ResultCode ResultOutOfRange{ErrorModule::Kernel, 119}; +constexpr ResultCode ResultInvalidEnumValue{ErrorModule::Kernel, 120}; +constexpr ResultCode ResultNotFound{ErrorModule::Kernel, 121}; +constexpr ResultCode ResultBusy{ErrorModule::Kernel, 122}; +constexpr ResultCode ResultSessionClosedByRemote{ErrorModule::Kernel, 123}; +constexpr ResultCode ResultInvalidState{ErrorModule::Kernel, 125}; +constexpr ResultCode ResultReservedValue{ErrorModule::Kernel, 126}; +constexpr ResultCode ResultResourceLimitedExceeded{ErrorModule::Kernel, 132}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 986724beb..ec463b97c 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -23,8 +23,8 @@ enum class MemoryState : u32 { Ipc = 0x0A, Stack = 0x0B, ThreadLocal = 0x0C, - Transfered = 0x0D, - SharedTransfered = 0x0E, + Transferred = 0x0D, + SharedTransferred = 0x0E, SharedCode = 0x0F, Inaccessible = 0x10, NonSecureIpc = 0x11, @@ -65,4 +65,34 @@ struct MemoryInfo { u32 padding{}; }; +enum class SignalType : u32 { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyByWaitingCountIfEqual = 2, +}; + +enum class ArbitrationType : u32 { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2, +}; + +enum class YieldType : s64 { + WithoutCoreMigration = 0, + WithCoreMigration = -1, + ToAnyThread = -2, +}; + +enum class ThreadActivity : u32 { + Runnable = 0, + Paused = 1, +}; + +constexpr inline s32 IdealCoreDontCare = -1; +constexpr inline s32 IdealCoreUseProcessValue = -2; +constexpr inline s32 IdealCoreNoUpdate = -3; + +constexpr inline s32 LowestThreadPriority = 63; +constexpr inline s32 HighestThreadPriority = 0; + } // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 0b6dd9df0..96afd544b 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/hle/kernel/svc_types.h" #include "core/hle/result.h" namespace Kernel { @@ -57,6 +58,14 @@ void SvcWrap64(Core::System& system) { func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw); } +// Used by SetThreadActivity +template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)> +void SvcWrap64(Core::System& system) { + FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), + static_cast<Svc::ThreadActivity>(Param(system, 1))) + .raw); +} + template <ResultCode func(Core::System&, u32, u64, u64, u64)> void SvcWrap64(Core::System& system) { FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), @@ -157,9 +166,18 @@ void SvcWrap64(Core::System& system) { .raw); } -template <ResultCode func(Core::System&, u32, u32*, u64*)> +// Used by SetThreadCoreMask +template <ResultCode func(Core::System&, Handle, s32, u64)> void SvcWrap64(Core::System& system) { - u32 param_1 = 0; + FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), + static_cast<s32>(Param(system, 1)), Param(system, 2)) + .raw); +} + +// Used by GetThreadCoreMask +template <ResultCode func(Core::System&, Handle, s32*, u64*)> +void SvcWrap64(Core::System& system) { + s32 param_1 = 0; u64 param_2 = 0; const ResultCode retval = func(system, static_cast<u32>(Param(system, 2)), ¶m_1, ¶m_2); @@ -215,9 +233,10 @@ void SvcWrap64(Core::System& system) { func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)).raw); } -template <ResultCode func(Core::System&, u32*, u64, u64, s64)> +// Used by WaitSynchronization +template <ResultCode func(Core::System&, s32*, u64, u64, s64)> void SvcWrap64(Core::System& system) { - u32 param_1 = 0; + s32 param_1 = 0; const u32 retval = func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2)), static_cast<s64>(Param(system, 3))) .raw; @@ -276,18 +295,22 @@ void SvcWrap64(Core::System& system) { FuncReturn(system, retval); } -template <ResultCode func(Core::System&, u64, u32, s32, s64)> +// Used by WaitForAddress +template <ResultCode func(Core::System&, u64, Svc::ArbitrationType, s32, s64)> void SvcWrap64(Core::System& system) { - FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)), - static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3))) - .raw); + FuncReturn(system, + func(system, Param(system, 0), static_cast<Svc::ArbitrationType>(Param(system, 1)), + static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3))) + .raw); } -template <ResultCode func(Core::System&, u64, u32, s32, s32)> +// Used by SignalToAddress +template <ResultCode func(Core::System&, u64, Svc::SignalType, s32, s32)> void SvcWrap64(Core::System& system) { - FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)), - static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) - .raw); + FuncReturn(system, + func(system, Param(system, 0), static_cast<Svc::SignalType>(Param(system, 1)), + static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) + .raw); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -467,12 +490,35 @@ void SvcWrap32(Core::System& system) { FuncReturn(system, retval); } +// Used by GetThreadCoreMask32 +template <ResultCode func(Core::System&, Handle, s32*, u32*, u32*)> +void SvcWrap32(Core::System& system) { + s32 param_1 = 0; + u32 param_2 = 0; + u32 param_3 = 0; + + const u32 retval = func(system, Param32(system, 2), ¶m_1, ¶m_2, ¶m_3).raw; + system.CurrentArmInterface().SetReg(1, param_1); + system.CurrentArmInterface().SetReg(2, param_2); + system.CurrentArmInterface().SetReg(3, param_3); + FuncReturn(system, retval); +} + // Used by SignalProcessWideKey32 template <void func(Core::System&, u32, s32)> void SvcWrap32(Core::System& system) { func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1))); } +// Used by SetThreadActivity32 +template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)> +void SvcWrap32(Core::System& system) { + const u32 retval = func(system, static_cast<Handle>(Param(system, 0)), + static_cast<Svc::ThreadActivity>(Param(system, 1))) + .raw; + FuncReturn(system, retval); +} + // Used by SetThreadPriority32 template <ResultCode func(Core::System&, Handle, u32)> void SvcWrap32(Core::System& system) { @@ -481,7 +527,7 @@ void SvcWrap32(Core::System& system) { FuncReturn(system, retval); } -// Used by SetThreadCoreMask32 +// Used by SetMemoryAttribute32 template <ResultCode func(Core::System&, Handle, u32, u32, u32)> void SvcWrap32(Core::System& system) { const u32 retval = @@ -491,6 +537,16 @@ void SvcWrap32(Core::System& system) { FuncReturn(system, retval); } +// Used by SetThreadCoreMask32 +template <ResultCode func(Core::System&, Handle, s32, u32, u32)> +void SvcWrap32(Core::System& system) { + const u32 retval = + func(system, static_cast<Handle>(Param(system, 0)), static_cast<s32>(Param(system, 1)), + static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3))) + .raw; + FuncReturn(system, retval); +} + // Used by WaitProcessWideKeyAtomic32 template <ResultCode func(Core::System&, u32, u32, Handle, u32, u32)> void SvcWrap32(Core::System& system) { @@ -503,22 +559,23 @@ void SvcWrap32(Core::System& system) { } // Used by WaitForAddress32 -template <ResultCode func(Core::System&, u32, u32, s32, u32, u32)> +template <ResultCode func(Core::System&, u32, Svc::ArbitrationType, s32, u32, u32)> void SvcWrap32(Core::System& system) { const u32 retval = func(system, static_cast<u32>(Param(system, 0)), - static_cast<u32>(Param(system, 1)), static_cast<s32>(Param(system, 2)), - static_cast<u32>(Param(system, 3)), static_cast<u32>(Param(system, 4))) + static_cast<Svc::ArbitrationType>(Param(system, 1)), + static_cast<s32>(Param(system, 2)), static_cast<u32>(Param(system, 3)), + static_cast<u32>(Param(system, 4))) .raw; FuncReturn(system, retval); } // Used by SignalToAddress32 -template <ResultCode func(Core::System&, u32, u32, s32, s32)> +template <ResultCode func(Core::System&, u32, Svc::SignalType, s32, s32)> void SvcWrap32(Core::System& system) { - const u32 retval = - func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)), - static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) - .raw; + const u32 retval = func(system, static_cast<u32>(Param(system, 0)), + static_cast<Svc::SignalType>(Param(system, 1)), + static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3))) + .raw; FuncReturn(system, retval); } @@ -539,9 +596,9 @@ void SvcWrap32(Core::System& system) { } // Used by WaitSynchronization32 -template <ResultCode func(Core::System&, u32, u32, s32, u32, Handle*)> +template <ResultCode func(Core::System&, u32, u32, s32, u32, s32*)> void SvcWrap32(Core::System& system) { - u32 param_1 = 0; + s32 param_1 = 0; const u32 retval = func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2), Param32(system, 3), ¶m_1) .raw; diff --git a/src/core/hle/kernel/synchronization.cpp b/src/core/hle/kernel/synchronization.cpp deleted file mode 100644 index 8b875d853..000000000 --- a/src/core/hle/kernel/synchronization.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/core.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/synchronization.h" -#include "core/hle/kernel/synchronization_object.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/time_manager.h" - -namespace Kernel { - -Synchronization::Synchronization(Core::System& system) : system{system} {} - -void Synchronization::SignalObject(SynchronizationObject& obj) const { - auto& kernel = system.Kernel(); - SchedulerLock lock(kernel); - if (obj.IsSignaled()) { - for (auto thread : obj.GetWaitingThreads()) { - if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) { - if (thread->GetStatus() != ThreadStatus::WaitHLEEvent) { - ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch); - ASSERT(thread->IsWaitingSync()); - } - thread->SetSynchronizationResults(&obj, RESULT_SUCCESS); - thread->ResumeFromWait(); - } - } - obj.ClearWaitingThreads(); - } -} - -std::pair<ResultCode, Handle> Synchronization::WaitFor( - std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) { - auto& kernel = system.Kernel(); - auto* const thread = system.CurrentScheduler().GetCurrentThread(); - Handle event_handle = InvalidHandle; - { - SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds); - const auto itr = - std::find_if(sync_objects.begin(), sync_objects.end(), - [thread](const std::shared_ptr<SynchronizationObject>& object) { - return object->IsSignaled(); - }); - - if (itr != sync_objects.end()) { - // We found a ready object, acquire it and set the result value - SynchronizationObject* object = itr->get(); - object->Acquire(thread); - const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr)); - lock.CancelSleep(); - return {RESULT_SUCCESS, index}; - } - - if (nano_seconds == 0) { - lock.CancelSleep(); - return {RESULT_TIMEOUT, InvalidHandle}; - } - - if (thread->IsPendingTermination()) { - lock.CancelSleep(); - return {ERR_THREAD_TERMINATING, InvalidHandle}; - } - - if (thread->IsSyncCancelled()) { - thread->SetSyncCancelled(false); - lock.CancelSleep(); - return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle}; - } - - for (auto& object : sync_objects) { - object->AddWaitingThread(SharedFrom(thread)); - } - - thread->SetSynchronizationObjects(&sync_objects); - thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - thread->SetStatus(ThreadStatus::WaitSynch); - thread->SetWaitingSync(true); - } - thread->SetWaitingSync(false); - - if (event_handle != InvalidHandle) { - auto& time_manager = kernel.TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - - { - SchedulerLock lock(kernel); - ResultCode signaling_result = thread->GetSignalingResult(); - SynchronizationObject* signaling_object = thread->GetSignalingObject(); - thread->SetSynchronizationObjects(nullptr); - auto shared_thread = SharedFrom(thread); - for (auto& obj : sync_objects) { - obj->RemoveWaitingThread(shared_thread); - } - if (signaling_object != nullptr) { - const auto itr = std::find_if( - sync_objects.begin(), sync_objects.end(), - [signaling_object](const std::shared_ptr<SynchronizationObject>& object) { - return object.get() == signaling_object; - }); - ASSERT(itr != sync_objects.end()); - signaling_object->Acquire(thread); - const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr)); - return {signaling_result, index}; - } - return {signaling_result, -1}; - } -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/synchronization.h b/src/core/hle/kernel/synchronization.h deleted file mode 100644 index 379f4b1d3..000000000 --- a/src/core/hle/kernel/synchronization.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <utility> -#include <vector> - -#include "core/hle/kernel/object.h" -#include "core/hle/result.h" - -namespace Core { -class System; -} // namespace Core - -namespace Kernel { - -class SynchronizationObject; - -/** - * The 'Synchronization' class is an interface for handling synchronization methods - * used by Synchronization objects and synchronization SVCs. This centralizes processing of - * such - */ -class Synchronization { -public: - explicit Synchronization(Core::System& system); - - /// Signals a synchronization object, waking up all its waiting threads - void SignalObject(SynchronizationObject& obj) const; - - /// Tries to see if waiting for any of the sync_objects is necessary, if not - /// it returns Success and the handle index of the signaled sync object. In - /// case not, the current thread will be locked and wait for nano_seconds or - /// for a synchronization object to signal. - std::pair<ResultCode, Handle> WaitFor( - std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds); - -private: - Core::System& system; -}; -} // namespace Kernel diff --git a/src/core/hle/kernel/synchronization_object.cpp b/src/core/hle/kernel/synchronization_object.cpp deleted file mode 100644 index ba4d39157..000000000 --- a/src/core/hle/kernel/synchronization_object.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include "common/assert.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/synchronization.h" -#include "core/hle/kernel/synchronization_object.h" -#include "core/hle/kernel/thread.h" - -namespace Kernel { - -SynchronizationObject::SynchronizationObject(KernelCore& kernel) : Object{kernel} {} -SynchronizationObject::~SynchronizationObject() = default; - -void SynchronizationObject::Signal() { - kernel.Synchronization().SignalObject(*this); -} - -void SynchronizationObject::AddWaitingThread(std::shared_ptr<Thread> thread) { - auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); - if (itr == waiting_threads.end()) - waiting_threads.push_back(std::move(thread)); -} - -void SynchronizationObject::RemoveWaitingThread(std::shared_ptr<Thread> thread) { - auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); - // If a thread passed multiple handles to the same object, - // the kernel might attempt to remove the thread from the object's - // waiting threads list multiple times. - if (itr != waiting_threads.end()) - waiting_threads.erase(itr); -} - -void SynchronizationObject::ClearWaitingThreads() { - waiting_threads.clear(); -} - -const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const { - return waiting_threads; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/synchronization_object.h b/src/core/hle/kernel/synchronization_object.h deleted file mode 100644 index f89b24204..000000000 --- a/src/core/hle/kernel/synchronization_object.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <vector> - -#include "core/hle/kernel/object.h" - -namespace Kernel { - -class KernelCore; -class Synchronization; -class Thread; - -/// Class that represents a Kernel object that a thread can be waiting on -class SynchronizationObject : public Object { -public: - explicit SynchronizationObject(KernelCore& kernel); - ~SynchronizationObject() override; - - /** - * Check if the specified thread should wait until the object is available - * @param thread The thread about which we're deciding. - * @return True if the current thread should wait due to this object being unavailable - */ - virtual bool ShouldWait(const Thread* thread) const = 0; - - /// Acquire/lock the object for the specified thread if it is available - virtual void Acquire(Thread* thread) = 0; - - /// Signal this object - virtual void Signal(); - - virtual bool IsSignaled() const { - return is_signaled; - } - - /** - * Add a thread to wait on this object - * @param thread Pointer to thread to add - */ - void AddWaitingThread(std::shared_ptr<Thread> thread); - - /** - * Removes a thread from waiting on this object (e.g. if it was resumed already) - * @param thread Pointer to thread to remove - */ - void RemoveWaitingThread(std::shared_ptr<Thread> thread); - - /// Get a const reference to the waiting threads list for debug use - const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const; - - void ClearWaitingThreads(); - -protected: - bool is_signaled{}; // Tells if this sync object is signalled; - -private: - /// Threads waiting for this object to become available - std::vector<std::shared_ptr<Thread>> waiting_threads; -}; - -// Specialization of DynamicObjectCast for SynchronizationObjects -template <> -inline std::shared_ptr<SynchronizationObject> DynamicObjectCast<SynchronizationObject>( - std::shared_ptr<Object> object) { - if (object != nullptr && object->IsWaitable()) { - return std::static_pointer_cast<SynchronizationObject>(object); - } - return nullptr; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp deleted file mode 100644 index d132aba34..000000000 --- a/src/core/hle/kernel/thread.cpp +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright 2014 Citra Emulator Project / PPSSPP Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cinttypes> -#include <optional> -#include <vector> - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/fiber.h" -#include "common/logging/log.h" -#include "common/thread_queue_list.h" -#include "core/arm/arm_interface.h" -#include "core/arm/unicorn/arm_unicorn.h" -#include "core/core.h" -#include "core/cpu_manager.h" -#include "core/hardware_properties.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/time_manager.h" -#include "core/hle/result.h" -#include "core/memory.h" - -#ifdef ARCHITECTURE_x86_64 -#include "core/arm/dynarmic/arm_dynarmic_32.h" -#include "core/arm/dynarmic/arm_dynarmic_64.h" -#endif - -namespace Kernel { - -bool Thread::ShouldWait(const Thread* thread) const { - return status != ThreadStatus::Dead; -} - -bool Thread::IsSignaled() const { - return status == ThreadStatus::Dead; -} - -void Thread::Acquire(Thread* thread) { - ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); -} - -Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {} -Thread::~Thread() = default; - -void Thread::Stop() { - { - SchedulerLock lock(kernel); - SetStatus(ThreadStatus::Dead); - Signal(); - kernel.GlobalHandleTable().Close(global_handle); - - if (owner_process) { - owner_process->UnregisterThread(this); - - // Mark the TLS slot in the thread's page as free. - owner_process->FreeTLSRegion(tls_address); - } - arm_interface.reset(); - has_exited = true; - } - global_handle = 0; -} - -void Thread::ResumeFromWait() { - SchedulerLock lock(kernel); - switch (status) { - case ThreadStatus::Paused: - case ThreadStatus::WaitSynch: - case ThreadStatus::WaitHLEEvent: - case ThreadStatus::WaitSleep: - case ThreadStatus::WaitIPC: - case ThreadStatus::WaitMutex: - case ThreadStatus::WaitCondVar: - case ThreadStatus::WaitArb: - case ThreadStatus::Dormant: - break; - - case ThreadStatus::Ready: - // The thread's wakeup callback must have already been cleared when the thread was first - // awoken. - ASSERT(hle_callback == nullptr); - // If the thread is waiting on multiple wait objects, it might be awoken more than once - // before actually resuming. We can ignore subsequent wakeups if the thread status has - // already been set to ThreadStatus::Ready. - return; - - case ThreadStatus::Running: - DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId()); - return; - case ThreadStatus::Dead: - // This should never happen, as threads must complete before being stopped. - DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.", - GetObjectId()); - return; - } - - SetStatus(ThreadStatus::Ready); -} - -void Thread::OnWakeUp() { - SchedulerLock lock(kernel); - - SetStatus(ThreadStatus::Ready); -} - -ResultCode Thread::Start() { - SchedulerLock lock(kernel); - SetStatus(ThreadStatus::Ready); - return RESULT_SUCCESS; -} - -void Thread::CancelWait() { - SchedulerLock lock(kernel); - if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) { - is_sync_cancelled = true; - return; - } - // TODO(Blinkhawk): Implement cancel of server session - is_sync_cancelled = false; - SetSynchronizationResults(nullptr, ERR_SYNCHRONIZATION_CANCELED); - SetStatus(ThreadStatus::Ready); -} - -static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top, - u32 entry_point, u32 arg) { - context = {}; - context.cpu_registers[0] = arg; - context.cpu_registers[15] = entry_point; - context.cpu_registers[13] = stack_top; -} - -static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context, VAddr stack_top, - VAddr entry_point, u64 arg) { - context = {}; - context.cpu_registers[0] = arg; - context.pc = entry_point; - context.sp = stack_top; - // TODO(merry): Perform a hardware test to determine the below value. - context.fpcr = 0; -} - -std::shared_ptr<Common::Fiber>& Thread::GetHostContext() { - return host_context; -} - -ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags, - std::string name, VAddr entry_point, u32 priority, - u64 arg, s32 processor_id, VAddr stack_top, - Process* owner_process) { - std::function<void(void*)> init_func = Core::CpuManager::GetGuestThreadStartFunc(); - void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); - return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top, - owner_process, std::move(init_func), init_func_parameter); -} - -ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags, - std::string name, VAddr entry_point, u32 priority, - u64 arg, s32 processor_id, VAddr stack_top, - Process* owner_process, - std::function<void(void*)>&& thread_start_func, - void* thread_start_parameter) { - auto& kernel = system.Kernel(); - // Check if priority is in ranged. Lowest priority -> highest priority id. - if (priority > THREADPRIO_LOWEST && ((type_flags & THREADTYPE_IDLE) == 0)) { - LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); - return ERR_INVALID_THREAD_PRIORITY; - } - - if (processor_id > THREADPROCESSORID_MAX) { - LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id); - return ERR_INVALID_PROCESSOR_ID; - } - - if (owner_process) { - if (!system.Memory().IsValidVirtualAddress(*owner_process, entry_point)) { - LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); - // TODO (bunnei): Find the correct error code to use here - return RESULT_UNKNOWN; - } - } - - std::shared_ptr<Thread> thread = std::make_shared<Thread>(kernel); - - thread->thread_id = kernel.CreateNewThreadID(); - thread->status = ThreadStatus::Dormant; - thread->entry_point = entry_point; - thread->stack_top = stack_top; - thread->tpidr_el0 = 0; - thread->nominal_priority = thread->current_priority = priority; - thread->last_running_ticks = 0; - thread->processor_id = processor_id; - thread->ideal_core = processor_id; - thread->affinity_mask = 1ULL << processor_id; - thread->wait_objects = nullptr; - thread->mutex_wait_address = 0; - thread->condvar_wait_address = 0; - thread->wait_handle = 0; - thread->name = std::move(name); - thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap(); - thread->owner_process = owner_process; - thread->type = type_flags; - if ((type_flags & THREADTYPE_IDLE) == 0) { - auto& scheduler = kernel.GlobalScheduler(); - scheduler.AddThread(thread); - } - if (owner_process) { - thread->tls_address = thread->owner_process->CreateTLSRegion(); - thread->owner_process->RegisterThread(thread.get()); - } else { - thread->tls_address = 0; - } - // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used - // to initialize the context - thread->arm_interface.reset(); - if ((type_flags & THREADTYPE_HLE) == 0) { -#ifdef ARCHITECTURE_x86_64 - if (owner_process && !owner_process->Is64BitProcess()) { - thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_32>( - system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(), - processor_id); - } else { - thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_64>( - system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(), - processor_id); - } - -#else - if (owner_process && !owner_process->Is64BitProcess()) { - thread->arm_interface = std::make_shared<Core::ARM_Unicorn>( - system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch32, - processor_id); - } else { - thread->arm_interface = std::make_shared<Core::ARM_Unicorn>( - system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch64, - processor_id); - } - LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); -#endif - ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top), - static_cast<u32>(entry_point), static_cast<u32>(arg)); - ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); - } - thread->host_context = - std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter); - - return MakeResult<std::shared_ptr<Thread>>(std::move(thread)); -} - -void Thread::SetPriority(u32 priority) { - SchedulerLock lock(kernel); - ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, - "Invalid priority value."); - nominal_priority = priority; - UpdatePriority(); -} - -void Thread::SetSynchronizationResults(SynchronizationObject* object, ResultCode result) { - signaling_object = object; - signaling_result = result; -} - -s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const { - ASSERT_MSG(!wait_objects->empty(), "Thread is not waiting for anything"); - const auto match = std::find(wait_objects->rbegin(), wait_objects->rend(), object); - return static_cast<s32>(std::distance(match, wait_objects->rend()) - 1); -} - -VAddr Thread::GetCommandBufferAddress() const { - // Offset from the start of TLS at which the IPC command buffer begins. - constexpr u64 command_header_offset = 0x80; - return GetTLSAddress() + command_header_offset; -} - -Core::ARM_Interface& Thread::ArmInterface() { - return *arm_interface; -} - -const Core::ARM_Interface& Thread::ArmInterface() const { - return *arm_interface; -} - -void Thread::SetStatus(ThreadStatus new_status) { - if (new_status == status) { - return; - } - - switch (new_status) { - case ThreadStatus::Ready: - case ThreadStatus::Running: - SetSchedulingStatus(ThreadSchedStatus::Runnable); - break; - case ThreadStatus::Dormant: - SetSchedulingStatus(ThreadSchedStatus::None); - break; - case ThreadStatus::Dead: - SetSchedulingStatus(ThreadSchedStatus::Exited); - break; - default: - SetSchedulingStatus(ThreadSchedStatus::Paused); - break; - } - - status = new_status; -} - -void Thread::AddMutexWaiter(std::shared_ptr<Thread> thread) { - if (thread->lock_owner.get() == this) { - // If the thread is already waiting for this thread to release the mutex, ensure that the - // waiters list is consistent and return without doing anything. - const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(iter != wait_mutex_threads.end()); - return; - } - - // A thread can't wait on two different mutexes at the same time. - ASSERT(thread->lock_owner == nullptr); - - // Ensure that the thread is not already in the list of mutex waiters - const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(iter == wait_mutex_threads.end()); - - // Keep the list in an ordered fashion - const auto insertion_point = std::find_if( - wait_mutex_threads.begin(), wait_mutex_threads.end(), - [&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); }); - wait_mutex_threads.insert(insertion_point, thread); - thread->lock_owner = SharedFrom(this); - - UpdatePriority(); -} - -void Thread::RemoveMutexWaiter(std::shared_ptr<Thread> thread) { - ASSERT(thread->lock_owner.get() == this); - - // Ensure that the thread is in the list of mutex waiters - const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(iter != wait_mutex_threads.end()); - - wait_mutex_threads.erase(iter); - - thread->lock_owner = nullptr; - UpdatePriority(); -} - -void Thread::UpdatePriority() { - // If any of the threads waiting on the mutex have a higher priority - // (taking into account priority inheritance), then this thread inherits - // that thread's priority. - u32 new_priority = nominal_priority; - if (!wait_mutex_threads.empty()) { - if (wait_mutex_threads.front()->current_priority < new_priority) { - new_priority = wait_mutex_threads.front()->current_priority; - } - } - - if (new_priority == current_priority) { - return; - } - - if (GetStatus() == ThreadStatus::WaitCondVar) { - owner_process->RemoveConditionVariableThread(SharedFrom(this)); - } - - SetCurrentPriority(new_priority); - - if (GetStatus() == ThreadStatus::WaitCondVar) { - owner_process->InsertConditionVariableThread(SharedFrom(this)); - } - - if (!lock_owner) { - return; - } - - // Ensure that the thread is within the correct location in the waiting list. - auto old_owner = lock_owner; - lock_owner->RemoveMutexWaiter(SharedFrom(this)); - old_owner->AddMutexWaiter(SharedFrom(this)); - - // Recursively update the priority of the thread that depends on the priority of this one. - lock_owner->UpdatePriority(); -} - -bool Thread::AllSynchronizationObjectsReady() const { - return std::none_of(wait_objects->begin(), wait_objects->end(), - [this](const std::shared_ptr<SynchronizationObject>& object) { - return object->ShouldWait(this); - }); -} - -bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) { - ASSERT(hle_callback); - return hle_callback(std::move(thread)); -} - -ResultCode Thread::SetActivity(ThreadActivity value) { - SchedulerLock lock(kernel); - - auto sched_status = GetSchedulingStatus(); - - if (sched_status != ThreadSchedStatus::Runnable && sched_status != ThreadSchedStatus::Paused) { - return ERR_INVALID_STATE; - } - - if (IsPendingTermination()) { - return RESULT_SUCCESS; - } - - if (value == ThreadActivity::Paused) { - if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) != 0) { - return ERR_INVALID_STATE; - } - AddSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag); - } else { - if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) == 0) { - return ERR_INVALID_STATE; - } - RemoveSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag); - } - return RESULT_SUCCESS; -} - -ResultCode Thread::Sleep(s64 nanoseconds) { - Handle event_handle{}; - { - SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds); - SetStatus(ThreadStatus::WaitSleep); - } - - if (event_handle != InvalidHandle) { - auto& time_manager = kernel.TimeManager(); - time_manager.UnscheduleTimeEvent(event_handle); - } - return RESULT_SUCCESS; -} - -std::pair<ResultCode, bool> Thread::YieldSimple() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThread(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - -std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - -std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - -void Thread::AddSchedulingFlag(ThreadSchedFlags flag) { - const u32 old_state = scheduling_state; - pausing_state |= static_cast<u32>(flag); - const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus()); - scheduling_state = base_scheduling | pausing_state; - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); -} - -void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) { - const u32 old_state = scheduling_state; - pausing_state &= ~static_cast<u32>(flag); - const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus()); - scheduling_state = base_scheduling | pausing_state; - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); -} - -void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) { - const u32 old_state = scheduling_state; - scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) | - static_cast<u32>(new_status); - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); -} - -void Thread::SetCurrentPriority(u32 new_priority) { - const u32 old_priority = std::exchange(current_priority, new_priority); - kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority); -} - -ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { - SchedulerLock lock(kernel); - const auto HighestSetCore = [](u64 mask, u32 max_cores) { - for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) { - if (((mask >> core) & 1) != 0) { - return core; - } - } - return -1; - }; - - const bool use_override = affinity_override_count != 0; - if (new_core == THREADPROCESSORID_DONT_UPDATE) { - new_core = use_override ? ideal_core_override : ideal_core; - if ((new_affinity_mask & (1ULL << new_core)) == 0) { - LOG_ERROR(Kernel, "New affinity mask is incorrect! new_core={}, new_affinity_mask={}", - new_core, new_affinity_mask); - return ERR_INVALID_COMBINATION; - } - } - if (use_override) { - ideal_core_override = new_core; - affinity_mask_override = new_affinity_mask; - } else { - const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask); - ideal_core = new_core; - if (old_affinity_mask != new_affinity_mask) { - const s32 old_core = processor_id; - if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) { - if (static_cast<s32>(ideal_core) < 0) { - processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES); - } else { - processor_id = ideal_core; - } - } - kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core); - } - } - return RESULT_SUCCESS; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h deleted file mode 100644 index 8daf79fac..000000000 --- a/src/core/hle/kernel/thread.h +++ /dev/null @@ -1,681 +0,0 @@ -// Copyright 2014 Citra Emulator Project / PPSSPP Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <functional> -#include <string> -#include <utility> -#include <vector> - -#include "common/common_types.h" -#include "common/spin_lock.h" -#include "core/arm/arm_interface.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/synchronization_object.h" -#include "core/hle/result.h" - -namespace Common { -class Fiber; -} - -namespace Core { -class ARM_Interface; -class System; -} // namespace Core - -namespace Kernel { - -class GlobalScheduler; -class KernelCore; -class Process; -class Scheduler; - -enum ThreadPriority : u32 { - THREADPRIO_HIGHEST = 0, ///< Highest thread priority - THREADPRIO_MAX_CORE_MIGRATION = 2, ///< Highest priority for a core migration - THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps - THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps - THREADPRIO_LOWEST = 63, ///< Lowest thread priority - THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities. -}; - -enum ThreadType : u32 { - THREADTYPE_USER = 0x1, - THREADTYPE_KERNEL = 0x2, - THREADTYPE_HLE = 0x4, - THREADTYPE_IDLE = 0x8, - THREADTYPE_SUSPEND = 0x10, -}; - -enum ThreadProcessorId : s32 { - /// Indicates that no particular processor core is preferred. - THREADPROCESSORID_DONT_CARE = -1, - - /// Run thread on the ideal core specified by the process. - THREADPROCESSORID_IDEAL = -2, - - /// Indicates that the preferred processor ID shouldn't be updated in - /// a core mask setting operation. - THREADPROCESSORID_DONT_UPDATE = -3, - - THREADPROCESSORID_0 = 0, ///< Run thread on core 0 - THREADPROCESSORID_1 = 1, ///< Run thread on core 1 - THREADPROCESSORID_2 = 2, ///< Run thread on core 2 - THREADPROCESSORID_3 = 3, ///< Run thread on core 3 - THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this - - /// Allowed CPU mask - THREADPROCESSORID_DEFAULT_MASK = (1 << THREADPROCESSORID_0) | (1 << THREADPROCESSORID_1) | - (1 << THREADPROCESSORID_2) | (1 << THREADPROCESSORID_3) -}; - -enum class ThreadStatus { - Running, ///< Currently running - Ready, ///< Ready to run - Paused, ///< Paused by SetThreadActivity or debug - WaitHLEEvent, ///< Waiting for hle event to finish - WaitSleep, ///< Waiting due to a SleepThread SVC - WaitIPC, ///< Waiting for the reply from an IPC request - WaitSynch, ///< Waiting due to WaitSynchronization - WaitMutex, ///< Waiting due to an ArbitrateLock svc - WaitCondVar, ///< Waiting due to an WaitProcessWideKey svc - WaitArb, ///< Waiting due to a SignalToAddress/WaitForAddress svc - Dormant, ///< Created but not yet made ready - Dead ///< Run to completion, or forcefully terminated -}; - -enum class ThreadWakeupReason { - Signal, // The thread was woken up by WakeupAllWaitingThreads due to an object signal. - Timeout // The thread was woken up due to a wait timeout. -}; - -enum class ThreadActivity : u32 { - Normal = 0, - Paused = 1, -}; - -enum class ThreadSchedStatus : u32 { - None = 0, - Paused = 1, - Runnable = 2, - Exited = 3, -}; - -enum class ThreadSchedFlags : u32 { - ProcessPauseFlag = 1 << 4, - ThreadPauseFlag = 1 << 5, - ProcessDebugPauseFlag = 1 << 6, - KernelInitPauseFlag = 1 << 8, -}; - -enum class ThreadSchedMasks : u32 { - LowMask = 0x000f, - HighMask = 0xfff0, - ForcePauseMask = 0x0070, -}; - -class Thread final : public SynchronizationObject { -public: - explicit Thread(KernelCore& kernel); - ~Thread() override; - - using MutexWaitingThreads = std::vector<std::shared_ptr<Thread>>; - - using ThreadContext32 = Core::ARM_Interface::ThreadContext32; - using ThreadContext64 = Core::ARM_Interface::ThreadContext64; - - using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>; - - using HLECallback = std::function<bool(std::shared_ptr<Thread> thread)>; - - /** - * Creates and returns a new thread. The new thread is immediately scheduled - * @param system The instance of the whole system - * @param name The friendly name desired for the thread - * @param entry_point The address at which the thread should start execution - * @param priority The thread's priority - * @param arg User data to pass to the thread - * @param processor_id The ID(s) of the processors on which the thread is desired to be run - * @param stack_top The address of the thread's stack top - * @param owner_process The parent process for the thread, if null, it's a kernel thread - * @return A shared pointer to the newly created thread - */ - static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags, - std::string name, VAddr entry_point, - u32 priority, u64 arg, s32 processor_id, - VAddr stack_top, Process* owner_process); - - /** - * Creates and returns a new thread. The new thread is immediately scheduled - * @param system The instance of the whole system - * @param name The friendly name desired for the thread - * @param entry_point The address at which the thread should start execution - * @param priority The thread's priority - * @param arg User data to pass to the thread - * @param processor_id The ID(s) of the processors on which the thread is desired to be run - * @param stack_top The address of the thread's stack top - * @param owner_process The parent process for the thread, if null, it's a kernel thread - * @param thread_start_func The function where the host context will start. - * @param thread_start_parameter The parameter which will passed to host context on init - * @return A shared pointer to the newly created thread - */ - static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags, - std::string name, VAddr entry_point, - u32 priority, u64 arg, s32 processor_id, - VAddr stack_top, Process* owner_process, - std::function<void(void*)>&& thread_start_func, - void* thread_start_parameter); - - std::string GetName() const override { - return name; - } - - void SetName(std::string new_name) { - name = std::move(new_name); - } - - std::string GetTypeName() const override { - return "Thread"; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::Thread; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - bool ShouldWait(const Thread* thread) const override; - void Acquire(Thread* thread) override; - bool IsSignaled() const override; - - /** - * Gets the thread's current priority - * @return The current thread's priority - */ - u32 GetPriority() const { - return current_priority; - } - - /** - * Gets the thread's nominal priority. - * @return The current thread's nominal priority. - */ - u32 GetNominalPriority() const { - return nominal_priority; - } - - /** - * Sets the thread's current priority - * @param priority The new priority - */ - void SetPriority(u32 priority); - - /// Adds a thread to the list of threads that are waiting for a lock held by this thread. - void AddMutexWaiter(std::shared_ptr<Thread> thread); - - /// Removes a thread from the list of threads that are waiting for a lock held by this thread. - void RemoveMutexWaiter(std::shared_ptr<Thread> thread); - - /// Recalculates the current priority taking into account priority inheritance. - void UpdatePriority(); - - /// Changes the core that the thread is running or scheduled to run on. - ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask); - - /** - * Gets the thread's thread ID - * @return The thread's ID - */ - u64 GetThreadID() const { - return thread_id; - } - - /// Resumes a thread from waiting - void ResumeFromWait(); - - void OnWakeUp(); - - ResultCode Start(); - - /// Cancels a waiting operation that this thread may or may not be within. - /// - /// When the thread is within a waiting state, this will set the thread's - /// waiting result to signal a canceled wait. The function will then resume - /// this thread. - /// - void CancelWait(); - - void SetSynchronizationResults(SynchronizationObject* object, ResultCode result); - - Core::ARM_Interface& ArmInterface(); - - const Core::ARM_Interface& ArmInterface() const; - - SynchronizationObject* GetSignalingObject() const { - return signaling_object; - } - - ResultCode GetSignalingResult() const { - return signaling_result; - } - - /** - * Retrieves the index that this particular object occupies in the list of objects - * that the thread passed to WaitSynchronization, starting the search from the last element. - * - * It is used to set the output index of WaitSynchronization when the thread is awakened. - * - * When a thread wakes up due to an object signal, the kernel will use the index of the last - * matching object in the wait objects list in case of having multiple instances of the same - * object in the list. - * - * @param object Object to query the index of. - */ - s32 GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const; - - /** - * Stops a thread, invalidating it from further use - */ - void Stop(); - - /* - * Returns the Thread Local Storage address of the current thread - * @returns VAddr of the thread's TLS - */ - VAddr GetTLSAddress() const { - return tls_address; - } - - /* - * Returns the value of the TPIDR_EL0 Read/Write system register for this thread. - * @returns The value of the TPIDR_EL0 register. - */ - u64 GetTPIDR_EL0() const { - return tpidr_el0; - } - - /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread. - void SetTPIDR_EL0(u64 value) { - tpidr_el0 = value; - } - - /* - * Returns the address of the current thread's command buffer, located in the TLS. - * @returns VAddr of the thread's command buffer. - */ - VAddr GetCommandBufferAddress() const; - - ThreadContext32& GetContext32() { - return context_32; - } - - const ThreadContext32& GetContext32() const { - return context_32; - } - - ThreadContext64& GetContext64() { - return context_64; - } - - const ThreadContext64& GetContext64() const { - return context_64; - } - - bool IsHLEThread() const { - return (type & THREADTYPE_HLE) != 0; - } - - bool IsSuspendThread() const { - return (type & THREADTYPE_SUSPEND) != 0; - } - - bool IsIdleThread() const { - return (type & THREADTYPE_IDLE) != 0; - } - - bool WasRunning() const { - return was_running; - } - - void SetWasRunning(bool value) { - was_running = value; - } - - std::shared_ptr<Common::Fiber>& GetHostContext(); - - ThreadStatus GetStatus() const { - return status; - } - - void SetStatus(ThreadStatus new_status); - - u64 GetLastRunningTicks() const { - return last_running_ticks; - } - - u64 GetTotalCPUTimeTicks() const { - return total_cpu_time_ticks; - } - - void UpdateCPUTimeTicks(u64 ticks) { - total_cpu_time_ticks += ticks; - } - - s32 GetProcessorID() const { - return processor_id; - } - - void SetProcessorID(s32 new_core) { - processor_id = new_core; - } - - Process* GetOwnerProcess() { - return owner_process; - } - - const Process* GetOwnerProcess() const { - return owner_process; - } - - const ThreadSynchronizationObjects& GetSynchronizationObjects() const { - return *wait_objects; - } - - void SetSynchronizationObjects(ThreadSynchronizationObjects* objects) { - wait_objects = objects; - } - - void ClearSynchronizationObjects() { - for (const auto& waiting_object : *wait_objects) { - waiting_object->RemoveWaitingThread(SharedFrom(this)); - } - wait_objects->clear(); - } - - /// Determines whether all the objects this thread is waiting on are ready. - bool AllSynchronizationObjectsReady() const; - - const MutexWaitingThreads& GetMutexWaitingThreads() const { - return wait_mutex_threads; - } - - Thread* GetLockOwner() const { - return lock_owner.get(); - } - - void SetLockOwner(std::shared_ptr<Thread> owner) { - lock_owner = std::move(owner); - } - - VAddr GetCondVarWaitAddress() const { - return condvar_wait_address; - } - - void SetCondVarWaitAddress(VAddr address) { - condvar_wait_address = address; - } - - VAddr GetMutexWaitAddress() const { - return mutex_wait_address; - } - - void SetMutexWaitAddress(VAddr address) { - mutex_wait_address = address; - } - - Handle GetWaitHandle() const { - return wait_handle; - } - - void SetWaitHandle(Handle handle) { - wait_handle = handle; - } - - VAddr GetArbiterWaitAddress() const { - return arb_wait_address; - } - - void SetArbiterWaitAddress(VAddr address) { - arb_wait_address = address; - } - - bool HasHLECallback() const { - return hle_callback != nullptr; - } - - void SetHLECallback(HLECallback callback) { - hle_callback = std::move(callback); - } - - void SetHLETimeEvent(Handle time_event) { - hle_time_event = time_event; - } - - void SetHLESyncObject(SynchronizationObject* object) { - hle_object = object; - } - - Handle GetHLETimeEvent() const { - return hle_time_event; - } - - SynchronizationObject* GetHLESyncObject() const { - return hle_object; - } - - void InvalidateHLECallback() { - SetHLECallback(nullptr); - } - - bool InvokeHLECallback(std::shared_ptr<Thread> thread); - - u32 GetIdealCore() const { - return ideal_core; - } - - u64 GetAffinityMask() const { - return affinity_mask; - } - - ResultCode SetActivity(ThreadActivity value); - - /// Sleeps this thread for the given amount of nanoseconds. - ResultCode Sleep(s64 nanoseconds); - - /// Yields this thread without rebalancing loads. - std::pair<ResultCode, bool> YieldSimple(); - - /// Yields this thread and does a load rebalancing. - std::pair<ResultCode, bool> YieldAndBalanceLoad(); - - /// Yields this thread and if the core is left idle, loads are rebalanced - std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing(); - - void IncrementYieldCount() { - yield_count++; - } - - u64 GetYieldCount() const { - return yield_count; - } - - ThreadSchedStatus GetSchedulingStatus() const { - return static_cast<ThreadSchedStatus>(scheduling_state & - static_cast<u32>(ThreadSchedMasks::LowMask)); - } - - bool IsRunnable() const { - return scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable); - } - - bool IsRunning() const { - return is_running; - } - - void SetIsRunning(bool value) { - is_running = value; - } - - bool IsSyncCancelled() const { - return is_sync_cancelled; - } - - void SetSyncCancelled(bool value) { - is_sync_cancelled = value; - } - - Handle GetGlobalHandle() const { - return global_handle; - } - - bool IsWaitingForArbitration() const { - return waiting_for_arbitration; - } - - void WaitForArbitration(bool set) { - waiting_for_arbitration = set; - } - - bool IsWaitingSync() const { - return is_waiting_on_sync; - } - - void SetWaitingSync(bool is_waiting) { - is_waiting_on_sync = is_waiting; - } - - bool IsPendingTermination() const { - return will_be_terminated || GetSchedulingStatus() == ThreadSchedStatus::Exited; - } - - bool IsPaused() const { - return pausing_state != 0; - } - - bool IsContinuousOnSVC() const { - return is_continuous_on_svc; - } - - void SetContinuousOnSVC(bool is_continuous) { - is_continuous_on_svc = is_continuous; - } - - bool IsPhantomMode() const { - return is_phantom_mode; - } - - void SetPhantomMode(bool phantom) { - is_phantom_mode = phantom; - } - - bool HasExited() const { - return has_exited; - } - -private: - friend class GlobalScheduler; - friend class Scheduler; - - void SetSchedulingStatus(ThreadSchedStatus new_status); - void AddSchedulingFlag(ThreadSchedFlags flag); - void RemoveSchedulingFlag(ThreadSchedFlags flag); - - void SetCurrentPriority(u32 new_priority); - - Common::SpinLock context_guard{}; - ThreadContext32 context_32{}; - ThreadContext64 context_64{}; - std::unique_ptr<Core::ARM_Interface> arm_interface{}; - std::shared_ptr<Common::Fiber> host_context{}; - - u64 thread_id = 0; - - ThreadStatus status = ThreadStatus::Dormant; - - VAddr entry_point = 0; - VAddr stack_top = 0; - - ThreadType type; - - /// Nominal thread priority, as set by the emulated application. - /// The nominal priority is the thread priority without priority - /// inheritance taken into account. - u32 nominal_priority = 0; - - /// Current thread priority. This may change over the course of the - /// thread's lifetime in order to facilitate priority inheritance. - u32 current_priority = 0; - - u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks. - u64 last_running_ticks = 0; ///< CPU tick when thread was last running - u64 yield_count = 0; ///< Number of redundant yields carried by this thread. - ///< a redundant yield is one where no scheduling is changed - - s32 processor_id = 0; - - VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread - u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register. - - /// Process that owns this thread - Process* owner_process; - - /// Objects that the thread is waiting on, in the same order as they were - /// passed to WaitSynchronization. - ThreadSynchronizationObjects* wait_objects; - - SynchronizationObject* signaling_object; - ResultCode signaling_result{RESULT_SUCCESS}; - - /// List of threads that are waiting for a mutex that is held by this thread. - MutexWaitingThreads wait_mutex_threads; - - /// Thread that owns the lock that this thread is waiting for. - std::shared_ptr<Thread> lock_owner; - - /// If waiting on a ConditionVariable, this is the ConditionVariable address - VAddr condvar_wait_address = 0; - /// If waiting on a Mutex, this is the mutex address - VAddr mutex_wait_address = 0; - /// The handle used to wait for the mutex. - Handle wait_handle = 0; - - /// If waiting for an AddressArbiter, this is the address being waited on. - VAddr arb_wait_address{0}; - bool waiting_for_arbitration{}; - - /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. - Handle global_handle = 0; - - /// Callback for HLE Events - HLECallback hle_callback; - Handle hle_time_event; - SynchronizationObject* hle_object; - - Scheduler* scheduler = nullptr; - - u32 ideal_core{0xFFFFFFFF}; - u64 affinity_mask{0x1}; - - s32 ideal_core_override = -1; - u64 affinity_mask_override = 0x1; - u32 affinity_override_count = 0; - - u32 scheduling_state = 0; - u32 pausing_state = 0; - bool is_running = false; - bool is_waiting_on_sync = false; - bool is_sync_cancelled = false; - - bool is_continuous_on_svc = false; - - bool will_be_terminated = false; - bool is_phantom_mode = false; - bool has_exited = false; - - bool was_running = false; - - std::string name; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp index 95f2446c9..fd0630019 100644 --- a/src/core/hle/kernel/time_manager.cpp +++ b/src/core/hle/kernel/time_manager.cpp @@ -7,9 +7,9 @@ #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" namespace Kernel { @@ -18,41 +18,30 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} { time_manager_event_type = Core::Timing::CreateEvent( "Kernel::TimeManagerCallback", [this](std::uintptr_t thread_handle, std::chrono::nanoseconds) { - const SchedulerLock lock(system.Kernel()); - const auto proper_handle = static_cast<Handle>(thread_handle); - if (cancelled_events[proper_handle]) { - return; + std::shared_ptr<KThread> thread; + { + std::lock_guard lock{mutex}; + thread = SharedFrom<KThread>(reinterpret_cast<KThread*>(thread_handle)); } - auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); - thread->OnWakeUp(); + thread->Wakeup(); }); } -void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) { - event_handle = timetask->GetGlobalHandle(); +void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) { + std::lock_guard lock{mutex}; if (nanoseconds > 0) { - ASSERT(timetask); - ASSERT(timetask->GetStatus() != ThreadStatus::Ready); - ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex); + ASSERT(thread); + ASSERT(thread->GetState() != ThreadState::Runnable); system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds}, - time_manager_event_type, event_handle); - } else { - event_handle = InvalidHandle; + time_manager_event_type, + reinterpret_cast<uintptr_t>(thread)); } - cancelled_events[event_handle] = false; } -void TimeManager::UnscheduleTimeEvent(Handle event_handle) { - if (event_handle == InvalidHandle) { - return; - } - system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle); - cancelled_events[event_handle] = true; -} - -void TimeManager::CancelTimeEvent(Thread* time_task) { - Handle event_handle = time_task->GetGlobalHandle(); - UnscheduleTimeEvent(event_handle); +void TimeManager::UnscheduleTimeEvent(KThread* thread) { + std::lock_guard lock{mutex}; + system.CoreTiming().UnscheduleEvent(time_manager_event_type, + reinterpret_cast<uintptr_t>(thread)); } } // namespace Kernel diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h index 307a18765..0d7f05f30 100644 --- a/src/core/hle/kernel/time_manager.h +++ b/src/core/hle/kernel/time_manager.h @@ -5,6 +5,7 @@ #pragma once #include <memory> +#include <mutex> #include <unordered_map> #include "core/hle/kernel/object.h" @@ -19,7 +20,7 @@ struct EventType; namespace Kernel { -class Thread; +class KThread; /** * The `TimeManager` takes care of scheduling time events on threads and executes their TimeUp @@ -30,18 +31,15 @@ public: explicit TimeManager(Core::System& system); /// Schedule a time event on `timetask` thread that will expire in 'nanoseconds' - /// returns a non-invalid handle in `event_handle` if correctly scheduled - void ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds); + void ScheduleTimeEvent(KThread* time_task, s64 nanoseconds); /// Unschedule an existing time event - void UnscheduleTimeEvent(Handle event_handle); - - void CancelTimeEvent(Thread* time_task); + void UnscheduleTimeEvent(KThread* thread); private: Core::System& system; std::shared_ptr<Core::Timing::EventType> time_manager_event_type; - std::unordered_map<Handle, bool> cancelled_events; + std::mutex mutex; }; } // namespace Kernel diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp index 765f408c3..6b0fc1591 100644 --- a/src/core/hle/kernel/transfer_memory.cpp +++ b/src/core/hle/kernel/transfer_memory.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" @@ -17,6 +18,7 @@ TransferMemory::TransferMemory(KernelCore& kernel, Core::Memory::Memory& memory) TransferMemory::~TransferMemory() { // Release memory region when transfer memory is destroyed Reset(); + owner_process->GetResourceLimit()->Release(LimitableResource::TransferMemory, 1); } std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h index 05e9f7464..777799d12 100644 --- a/src/core/hle/kernel/transfer_memory.h +++ b/src/core/hle/kernel/transfer_memory.h @@ -72,6 +72,8 @@ public: /// is closed. ResultCode Reset(); + void Finalize() override {} + private: /// The base address for the memory managed by this instance. VAddr base_address{}; diff --git a/src/core/hle/kernel/writable_event.cpp b/src/core/hle/kernel/writable_event.cpp deleted file mode 100644 index fc2f7c424..000000000 --- a/src/core/hle/kernel/writable_event.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include "common/assert.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/object.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/writable_event.h" - -namespace Kernel { - -WritableEvent::WritableEvent(KernelCore& kernel) : Object{kernel} {} -WritableEvent::~WritableEvent() = default; - -EventPair WritableEvent::CreateEventPair(KernelCore& kernel, std::string name) { - std::shared_ptr<WritableEvent> writable_event(new WritableEvent(kernel)); - std::shared_ptr<ReadableEvent> readable_event(new ReadableEvent(kernel)); - - writable_event->name = name + ":Writable"; - writable_event->readable = readable_event; - readable_event->name = name + ":Readable"; - - return {std::move(readable_event), std::move(writable_event)}; -} - -std::shared_ptr<ReadableEvent> WritableEvent::GetReadableEvent() const { - return readable; -} - -void WritableEvent::Signal() { - readable->Signal(); -} - -void WritableEvent::Clear() { - readable->Clear(); -} - -bool WritableEvent::IsSignaled() const { - return readable->IsSignaled(); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/writable_event.h b/src/core/hle/kernel/writable_event.h deleted file mode 100644 index 6189cf65c..000000000 --- a/src/core/hle/kernel/writable_event.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> - -#include "core/hle/kernel/object.h" - -namespace Kernel { - -class KernelCore; -class ReadableEvent; -class WritableEvent; - -struct EventPair { - std::shared_ptr<ReadableEvent> readable; - std::shared_ptr<WritableEvent> writable; -}; - -class WritableEvent final : public Object { -public: - ~WritableEvent() override; - - /** - * Creates an event - * @param kernel The kernel instance to create this event under. - * @param name Optional name of event - */ - static EventPair CreateEventPair(KernelCore& kernel, std::string name = "Unknown"); - - std::string GetTypeName() const override { - return "WritableEvent"; - } - std::string GetName() const override { - return name; - } - - static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::shared_ptr<ReadableEvent> GetReadableEvent() const; - - void Signal(); - void Clear(); - bool IsSignaled() const; - -private: - explicit WritableEvent(KernelCore& kernel); - - std::shared_ptr<ReadableEvent> readable; - - std::string name; ///< Name of event (optional) -}; - -} // namespace Kernel diff --git a/src/core/hle/result.h b/src/core/hle/result.h index b6bdbd988..8feda7ad7 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -119,7 +119,7 @@ union ResultCode { BitField<0, 9, ErrorModule> module; BitField<9, 13, u32> description; - constexpr explicit ResultCode(u32 raw) : raw(raw) {} + constexpr explicit ResultCode(u32 raw_) : raw(raw_) {} constexpr ResultCode(ErrorModule module_, u32 description_) : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index eb54cb123..3ec0e1eca 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -11,6 +11,7 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/constants.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -31,9 +32,15 @@ namespace Service::Account { -constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30}; +constexpr ResultCode ERR_INVALID_USER_ID{ErrorModule::Account, 20}; +constexpr ResultCode ERR_INVALID_APPLICATION_ID{ErrorModule::Account, 22}; +constexpr ResultCode ERR_INVALID_BUFFER{ErrorModule::Account, 30}; +constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 31}; constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; +// Thumbnails are hard coded to be at least this size +constexpr std::size_t THUMBNAIL_SIZE = 0x24000; + static std::string GetImagePath(Common::UUID uuid) { return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; @@ -46,8 +53,8 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) { class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> { public: - explicit IManagerForSystemService(Common::UUID user_id) - : ServiceFramework("IManagerForSystemService") { + explicit IManagerForSystemService(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IManagerForSystemService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CheckAvailability"}, @@ -82,8 +89,8 @@ public: // 3.0.0+ class IFloatingRegistrationRequest final : public ServiceFramework<IFloatingRegistrationRequest> { public: - explicit IFloatingRegistrationRequest(Common::UUID user_id) - : ServiceFramework("IFloatingRegistrationRequest") { + explicit IFloatingRegistrationRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IFloatingRegistrationRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -107,7 +114,8 @@ public: class IAdministrator final : public ServiceFramework<IAdministrator> { public: - explicit IAdministrator(Common::UUID user_id) : ServiceFramework("IAdministrator") { + explicit IAdministrator(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAdministrator"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CheckAvailability"}, @@ -164,8 +172,8 @@ public: class IAuthorizationRequest final : public ServiceFramework<IAuthorizationRequest> { public: - explicit IAuthorizationRequest(Common::UUID user_id) - : ServiceFramework("IAuthorizationRequest") { + explicit IAuthorizationRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAuthorizationRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -183,7 +191,8 @@ public: class IOAuthProcedure final : public ServiceFramework<IOAuthProcedure> { public: - explicit IOAuthProcedure(Common::UUID user_id) : ServiceFramework("IOAuthProcedure") { + explicit IOAuthProcedure(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedure"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -201,8 +210,8 @@ public: // 3.0.0+ class IOAuthProcedureForExternalNsa final : public ServiceFramework<IOAuthProcedureForExternalNsa> { public: - explicit IOAuthProcedureForExternalNsa(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForExternalNsa") { + explicit IOAuthProcedureForExternalNsa(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForExternalNsa"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -224,8 +233,8 @@ public: class IOAuthProcedureForNintendoAccountLinkage final : public ServiceFramework<IOAuthProcedureForNintendoAccountLinkage> { public: - explicit IOAuthProcedureForNintendoAccountLinkage(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForNintendoAccountLinkage") { + explicit IOAuthProcedureForNintendoAccountLinkage(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForNintendoAccountLinkage"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -245,7 +254,8 @@ public: class INotifier final : public ServiceFramework<INotifier> { public: - explicit INotifier(Common::UUID user_id) : ServiceFramework("INotifier") { + explicit INotifier(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "INotifier"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -258,9 +268,9 @@ public: class IProfileCommon : public ServiceFramework<IProfileCommon> { public: - explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id, - ProfileManager& profile_manager) - : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) { + explicit IProfileCommon(Core::System& system_, const char* name, bool editor_commands, + Common::UUID user_id_, ProfileManager& profile_manager_) + : ServiceFramework{system_, name}, profile_manager{profile_manager_}, user_id{user_id_} { static const FunctionInfo functions[] = { {0, &IProfileCommon::Get, "Get"}, {1, &IProfileCommon::GetBase, "GetBase"}, @@ -365,7 +375,7 @@ protected: if (user_data.size() < sizeof(ProfileData)) { LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_INVALID_BUFFER_SIZE); + rb.Push(ERR_INVALID_BUFFER); return; } @@ -398,7 +408,7 @@ protected: if (user_data.size() < sizeof(ProfileData)) { LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_INVALID_BUFFER_SIZE); + rb.Push(ERR_INVALID_BUFFER); return; } @@ -426,19 +436,21 @@ protected: class IProfile final : public IProfileCommon { public: - IProfile(Common::UUID user_id, ProfileManager& profile_manager) - : IProfileCommon("IProfile", false, user_id, profile_manager) {} + explicit IProfile(Core::System& system_, Common::UUID user_id_, + ProfileManager& profile_manager_) + : IProfileCommon{system_, "IProfile", false, user_id_, profile_manager_} {} }; class IProfileEditor final : public IProfileCommon { public: - IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager) - : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {} + explicit IProfileEditor(Core::System& system_, Common::UUID user_id_, + ProfileManager& profile_manager_) + : IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {} }; class IAsyncContext final : public ServiceFramework<IAsyncContext> { public: - explicit IAsyncContext(Common::UUID user_id) : ServiceFramework("IAsyncContext") { + explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -454,7 +466,8 @@ public: class ISessionObject final : public ServiceFramework<ISessionObject> { public: - explicit ISessionObject(Common::UUID user_id) : ServiceFramework("ISessionObject") { + explicit ISessionObject(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "ISessionObject"} { // clang-format off static const FunctionInfo functions[] = { {999, nullptr, "Dummy"}, @@ -467,7 +480,8 @@ public: class IGuestLoginRequest final : public ServiceFramework<IGuestLoginRequest> { public: - explicit IGuestLoginRequest(Common::UUID) : ServiceFramework("IGuestLoginRequest") { + explicit IGuestLoginRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IGuestLoginRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -486,8 +500,8 @@ public: class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { public: - explicit IManagerForApplication(Common::UUID user_id) - : ServiceFramework("IManagerForApplication"), user_id(user_id) { + explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_) + : ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} { // clang-format off static const FunctionInfo functions[] = { {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, @@ -496,7 +510,7 @@ public: {3, nullptr, "LoadIdTokenCache"}, {130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"}, {150, nullptr, "CreateAuthorizationRequest"}, - {160, nullptr, "StoreOpenContext"}, + {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"}, {170, nullptr, "LoadNetworkServiceLicenseKindAsync"}, }; // clang-format on @@ -520,15 +534,21 @@ private: rb.PushRaw<u64>(user_id.GetNintendoID()); } - Common::UUID user_id; + void StoreOpenContext(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Common::UUID user_id{Common::INVALID_UUID}; }; // 6.0.0+ class IAsyncNetworkServiceLicenseKindContext final : public ServiceFramework<IAsyncNetworkServiceLicenseKindContext> { public: - explicit IAsyncNetworkServiceLicenseKindContext(Common::UUID user_id) - : ServiceFramework("IAsyncNetworkServiceLicenseKindContext") { + explicit IAsyncNetworkServiceLicenseKindContext(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAsyncNetworkServiceLicenseKindContext"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -547,8 +567,8 @@ public: class IOAuthProcedureForUserRegistration final : public ServiceFramework<IOAuthProcedureForUserRegistration> { public: - explicit IOAuthProcedureForUserRegistration(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForUserRegistration") { + explicit IOAuthProcedureForUserRegistration(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForUserRegistration"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -571,7 +591,7 @@ public: class DAUTH_O final : public ServiceFramework<DAUTH_O> { public: - explicit DAUTH_O(Common::UUID) : ServiceFramework("dauth:o") { + explicit DAUTH_O(Core::System& system_, Common::UUID) : ServiceFramework{system_, "dauth:o"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData @@ -590,7 +610,8 @@ public: // 6.0.0+ class IAsyncResult final : public ServiceFramework<IAsyncResult> { public: - explicit IAsyncResult(Common::UUID user_id) : ServiceFramework("IAsyncResult") { + explicit IAsyncResult(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAsyncResult"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetResult"}, @@ -649,7 +670,7 @@ void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfile>(user_id, *profile_manager); + rb.PushIpcInterface<IProfile>(system, user_id, *profile_manager); } void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { @@ -724,7 +745,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IManagerForApplication>(profile_manager->GetLastOpenedUser()); + rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser()); } void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) { @@ -735,8 +756,10 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx bool is_locked = false; if (res != Loader::ResultStatus::Success) { - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; - auto nacp_unique = pm.GetControlMetadata().first; + const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(), + system.GetFileSystemController(), + system.GetContentProvider()}; + const auto nacp_unique = pm.GetControlMetadata().first; if (nacp_unique != nullptr) { is_locked = nacp_unique->GetUserAccountSwitchLock(); @@ -760,7 +783,7 @@ void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager); + rb.PushIpcInterface<IProfileEditor>(system, user_id, *profile_manager); } void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) { @@ -774,6 +797,17 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + // This is similar to GetBaasAccountManagerForApplication + // This command is used concurrently with ListOpenContextStoredUsers + // TODO: Find the differences between this and GetBaasAccountManagerForApplication + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser()); +} + void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); @@ -783,6 +817,55 @@ void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ct rb.Push(RESULT_SUCCESS); } +void Module::Interface::StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto uuid = rp.PopRaw<Common::UUID>(); + + LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}", uuid.Format()); + + // TODO(ogniK): Check if application ID is zero on acc initialize. As we don't have a reliable + // way of confirming things like the TID, we're going to assume a non zero value for the time + // being. + constexpr u64 tid{1}; + StoreSaveDataThumbnail(ctx, uuid, tid); +} + +void Module::Interface::StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto uuid = rp.PopRaw<Common::UUID>(); + const auto tid = rp.Pop<u64_le>(); + + LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}, tid={:016X}", uuid.Format(), tid); + StoreSaveDataThumbnail(ctx, uuid, tid); +} + +void Module::Interface::StoreSaveDataThumbnail(Kernel::HLERequestContext& ctx, + const Common::UUID& uuid, const u64 tid) { + IPC::ResponseBuilder rb{ctx, 2}; + + if (tid == 0) { + LOG_ERROR(Service_ACC, "TitleID is not valid!"); + rb.Push(ERR_INVALID_APPLICATION_ID); + return; + } + + if (!uuid) { + LOG_ERROR(Service_ACC, "User ID is not valid!"); + rb.Push(ERR_INVALID_USER_ID); + return; + } + const auto thumbnail_size = ctx.GetReadBufferSize(); + if (thumbnail_size != THUMBNAIL_SIZE) { + LOG_ERROR(Service_ACC, "Buffer size is empty! size={:X} expecting {:X}", thumbnail_size, + THUMBNAIL_SIZE); + rb.Push(ERR_INVALID_BUFFER_SIZE); + return; + } + + // TODO(ogniK): Construct save data thumbnail + rb.Push(RESULT_SUCCESS); +} + void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); // A u8 is passed into this function which we can safely ignore. It's to determine if we have @@ -807,11 +890,11 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex rb.PushRaw<u128>(profile_manager->GetUser(0)->uuid); } -Module::Interface::Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, Core::System& system, - const char* name) - : ServiceFramework(name), module(std::move(module)), - profile_manager(std::move(profile_manager)), system(system) {} +Module::Interface::Interface(std::shared_ptr<Module> module_, + std::shared_ptr<ProfileManager> profile_manager_, + Core::System& system_, const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)}, profile_manager{std::move( + profile_manager_)} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index d4c6395c6..0e3ad8ec6 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -4,6 +4,7 @@ #pragma once +#include "common/uuid.h" #include "core/hle/service/glue/manager.h" #include "core/hle/service/service.h" @@ -15,8 +16,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, Core::System& system, + explicit Interface(std::shared_ptr<Module> module_, + std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_, const char* name); ~Interface() override; @@ -34,10 +35,15 @@ public: void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); void GetProfileEditor(Kernel::HLERequestContext& ctx); void ListQualifiedUsers(Kernel::HLERequestContext& ctx); + void LoadOpenContext(Kernel::HLERequestContext& ctx); void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx); + void StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx); + void StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx); private: ResultCode InitializeApplicationInfoBase(); + void StoreSaveDataThumbnail(Kernel::HLERequestContext& ctx, const Common::UUID& uuid, + const u64 tid); enum class ApplicationType : u32_le { GameCard = 0, @@ -59,7 +65,6 @@ public: protected: std::shared_ptr<Module> module; std::shared_ptr<ProfileManager> profile_manager; - Core::System& system; }; }; diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index d2bb8c2c8..49b22583e 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -29,7 +29,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {104, nullptr, "GetProfileUpdateNotifier"}, {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+ {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+ - {110, nullptr, "StoreSaveDataThumbnail"}, + {110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"}, {112, nullptr, "LoadSaveDataThumbnail"}, {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+ diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index cb44e06b7..8d66d180d 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -26,10 +26,10 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, {102, nullptr, "AuthenticateApplicationAsync"}, {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+ - {110, nullptr, "StoreSaveDataThumbnail"}, + {110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"}, {120, nullptr, "CreateGuestLoginRequest"}, - {130, nullptr, "LoadOpenContext"}, // 5.0.0+ + {130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+ {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+ {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+ {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+ diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index a4aa5316a..951081cd0 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -29,7 +29,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {104, nullptr, "GetProfileUpdateNotifier"}, {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+ {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+ - {110, nullptr, "StoreSaveDataThumbnail"}, + {110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"}, {112, nullptr, "LoadSaveDataThumbnail"}, {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+ diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 9b829e957..50b2c58e2 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -41,12 +41,18 @@ constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/ ProfileManager::ProfileManager() { ParseUserSaveFile(); - if (user_count == 0) + // Create an user if none are present + if (user_count == 0) { CreateNewUser(UUID::Generate(), "yuzu"); + } auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1); - if (UserExistsIndex(current)) + + // If user index don't exist. Load the first user and change the active user + if (!UserExistsIndex(current)) { current = 0; + Settings::values.current_user = 0; + } OpenUser(*GetUser(current)); } @@ -227,17 +233,17 @@ void ProfileManager::CloseUser(UUID uuid) { /// Gets all valid user ids on the system UserIDArray ProfileManager::GetAllUsers() const { - UserIDArray output; - std::transform(profiles.begin(), profiles.end(), output.begin(), - [](const ProfileInfo& p) { return p.user_uuid; }); + UserIDArray output{}; + std::ranges::transform(profiles, output.begin(), + [](const ProfileInfo& p) { return p.user_uuid; }); return output; } /// Get all the open users on the system and zero out the rest of the data. This is specifically /// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out UserIDArray ProfileManager::GetOpenUsers() const { - UserIDArray output; - std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) { + UserIDArray output{}; + std::ranges::transform(profiles, output.begin(), [](const ProfileInfo& p) { if (p.is_open) return p.user_uuid; return UUID{Common::INVALID_UUID}; diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 5310637a6..71b9d5518 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -23,12 +23,12 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>; /// Contains extra data related to a user. /// TODO: RE this structure struct ProfileData { - INSERT_PADDING_WORDS(1); - u32 icon_id{}; - u8 bg_color_id{}; - INSERT_PADDING_BYTES(0x7); - INSERT_PADDING_BYTES(0x10); - INSERT_PADDING_BYTES(0x60); + INSERT_PADDING_WORDS_NOINIT(1); + u32 icon_id; + u8 bg_color_id; + INSERT_PADDING_BYTES_NOINIT(0x7); + INSERT_PADDING_BYTES_NOINIT(0x10); + INSERT_PADDING_BYTES_NOINIT(0x60); }; static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size"); @@ -43,9 +43,9 @@ struct ProfileInfo { }; struct ProfileBase { - Common::UUID user_uuid{Common::INVALID_UUID}; - u64_le timestamp{}; - ProfileUsername username{}; + Common::UUID user_uuid; + u64_le timestamp; + ProfileUsername username; // Zero out all the fields to make the profile slot considered "Empty" void Invalidate() { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 7d92b25a3..8e1fe9438 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -13,11 +13,12 @@ #include "core/file_sys/registered_cache.h" #include "core/file_sys/savedata_factory.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/transfer_memory.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" @@ -64,7 +65,7 @@ struct LaunchParameterAccountPreselectedUser { static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88); IWindowController::IWindowController(Core::System& system_) - : ServiceFramework("IWindowController"), system{system_} { + : ServiceFramework{system_, "IWindowController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateWindow"}, @@ -99,7 +100,8 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) rb.Push(RESULT_SUCCESS); } -IAudioController::IAudioController() : ServiceFramework("IAudioController") { +IAudioController::IAudioController(Core::System& system_) + : ServiceFramework{system_, "IAudioController"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"}, @@ -180,7 +182,8 @@ void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { +IDisplayController::IDisplayController(Core::System& system_) + : ServiceFramework{system_, "IDisplayController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLastForegroundCaptureImage"}, @@ -219,7 +222,8 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController" IDisplayController::~IDisplayController() = default; -IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { +IDebugFunctions::IDebugFunctions(Core::System& system_) + : ServiceFramework{system_, "IDebugFunctions"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "NotifyMessageToHomeMenuForDebug"}, @@ -246,9 +250,8 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { IDebugFunctions::~IDebugFunctions() = default; -ISelfController::ISelfController(Core::System& system, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger) - : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) { +ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_) + : ServiceFramework{system_, "ISelfController"}, nvflinger{nvflinger_} { // clang-format off static const FunctionInfo functions[] = { {0, &ISelfController::Exit, "Exit"}, @@ -301,17 +304,18 @@ ISelfController::ISelfController(Core::System& system, RegisterHandlers(functions); auto& kernel = system.Kernel(); - launchable_event = - Kernel::WritableEvent::CreateEventPair(kernel, "ISelfController:LaunchableEvent"); + launchable_event = Kernel::KEvent::Create(kernel, "ISelfController:LaunchableEvent"); + launchable_event->Initialize(); // This event is created by AM on the first time GetAccumulatedSuspendedTickChangedEvent() is // called. Yuzu can just create it unconditionally, since it doesn't need to support multiple // ISelfControllers. The event is signaled on creation, and on transition from suspended -> not // suspended if the event has previously been created by a call to // GetAccumulatedSuspendedTickChangedEvent. - accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair( - kernel, "ISelfController:AccumulatedSuspendedTickChangedEvent"); - accumulated_suspended_tick_changed_event.writable->Signal(); + accumulated_suspended_tick_changed_event = + Kernel::KEvent::Create(kernel, "ISelfController:AccumulatedSuspendedTickChangedEvent"); + accumulated_suspended_tick_changed_event->Initialize(); + accumulated_suspended_tick_changed_event->GetWritableEvent()->Signal(); } ISelfController::~ISelfController() = default; @@ -370,11 +374,11 @@ void ISelfController::LeaveFatalSection(Kernel::HLERequestContext& ctx) { void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); - launchable_event.writable->Signal(); + launchable_event->GetWritableEvent()->Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(launchable_event.readable); + rb.PushCopyObjects(launchable_event->GetReadableEvent()); } void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) { @@ -458,8 +462,8 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) // TODO(Subv): Find out how AM determines the display to use, for now just // create the layer in the Default display. - const auto display_id = nvflinger->OpenDisplay("Default"); - const auto layer_id = nvflinger->CreateLayer(*display_id); + const auto display_id = nvflinger.OpenDisplay("Default"); + const auto layer_id = nvflinger.CreateLayer(*display_id); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -476,8 +480,8 @@ void ISelfController::CreateManagedDisplaySeparableLayer(Kernel::HLERequestConte // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse // side effects. // TODO: Support multiple layers - const auto display_id = nvflinger->OpenDisplay("Default"); - const auto layer_id = nvflinger->CreateLayer(*display_id); + const auto display_id = nvflinger.OpenDisplay("Default"); + const auto layer_id = nvflinger.CreateLayer(*display_id); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -553,41 +557,42 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); + rb.PushCopyObjects(accumulated_suspended_tick_changed_event->GetReadableEvent()); } AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) { - on_new_message = - Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OnMessageRecieved"); + on_new_message = Kernel::KEvent::Create(kernel, "AMMessageQueue:OnMessageReceived"); + on_new_message->Initialize(); on_operation_mode_changed = - Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OperationModeChanged"); + Kernel::KEvent::Create(kernel, "AMMessageQueue:OperationModeChanged"); + on_operation_mode_changed->Initialize(); } AppletMessageQueue::~AppletMessageQueue() = default; -const std::shared_ptr<Kernel::ReadableEvent>& AppletMessageQueue::GetMesssageRecieveEvent() const { - return on_new_message.readable; +const std::shared_ptr<Kernel::KReadableEvent>& AppletMessageQueue::GetMessageReceiveEvent() const { + return on_new_message->GetReadableEvent(); } -const std::shared_ptr<Kernel::ReadableEvent>& AppletMessageQueue::GetOperationModeChangedEvent() +const std::shared_ptr<Kernel::KReadableEvent>& AppletMessageQueue::GetOperationModeChangedEvent() const { - return on_operation_mode_changed.readable; + return on_operation_mode_changed->GetReadableEvent(); } void AppletMessageQueue::PushMessage(AppletMessage msg) { messages.push(msg); - on_new_message.writable->Signal(); + on_new_message->GetWritableEvent()->Signal(); } AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() { if (messages.empty()) { - on_new_message.writable->Clear(); + on_new_message->GetWritableEvent()->Clear(); return AppletMessage::NoMessage; } auto msg = messages.front(); messages.pop(); if (messages.empty()) { - on_new_message.writable->Clear(); + on_new_message->GetWritableEvent()->Clear(); } return msg; } @@ -599,16 +604,16 @@ std::size_t AppletMessageQueue::GetMessageCount() const { void AppletMessageQueue::OperationModeChanged() { PushMessage(AppletMessage::OperationModeChanged); PushMessage(AppletMessage::PerformanceModeChanged); - on_operation_mode_changed.writable->Signal(); + on_operation_mode_changed->GetWritableEvent()->Signal(); } void AppletMessageQueue::RequestExit() { PushMessage(AppletMessage::ExitRequested); } -ICommonStateGetter::ICommonStateGetter(Core::System& system, - std::shared_ptr<AppletMessageQueue> msg_queue) - : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) { +ICommonStateGetter::ICommonStateGetter(Core::System& system_, + std::shared_ptr<AppletMessageQueue> msg_queue_) + : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -633,7 +638,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system, {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, {51, &ICommonStateGetter::SetVrModeEnabled, "SetVrModeEnabled"}, {52, &ICommonStateGetter::SetLcdBacklighOffEnabled, "SetLcdBacklighOffEnabled"}, - {53, nullptr, "BeginVrModeEx"}, + {53, &ICommonStateGetter::BeginVrModeEx, "BeginVrModeEx"}, {54, &ICommonStateGetter::EndVrModeEx, "EndVrModeEx"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, {60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"}, @@ -673,7 +678,7 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(msg_queue->GetMesssageRecieveEvent()); + rb.PushCopyObjects(msg_queue->GetMessageReceiveEvent()); } void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { @@ -730,6 +735,13 @@ void ICommonStateGetter::SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx rb.Push(RESULT_SUCCESS); } +void ICommonStateGetter::BeginVrModeEx(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void ICommonStateGetter::EndVrModeEx(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -751,7 +763,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * @@ -796,8 +808,9 @@ private: std::vector<u8> buffer; }; -IStorage::IStorage(std::vector<u8>&& buffer) - : ServiceFramework("IStorage"), impl{std::make_shared<StorageDataImpl>(std::move(buffer))} { +IStorage::IStorage(Core::System& system_, std::vector<u8>&& buffer) + : ServiceFramework{system_, "IStorage"}, impl{std::make_shared<StorageDataImpl>( + std::move(buffer))} { Register(); } @@ -820,11 +833,11 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IStorageAccessor>(*this); + rb.PushIpcInterface<IStorageAccessor>(system, *this); } void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { - const bool use_docked_mode{Settings::values.use_docked_mode}; + const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); IPC::ResponseBuilder rb{ctx, 3}; @@ -842,8 +855,8 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: - explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet) - : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) { + explicit ILibraryAppletAccessor(Core::System& system_, std::shared_ptr<Applets::Applet> applet_) + : ServiceFramework{system_, "ILibraryAppletAccessor"}, applet{std::move(applet_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, @@ -853,7 +866,7 @@ public: {25, nullptr, "Terminate"}, {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, - {60, nullptr, "PresetLibraryAppletGpuTimeSliceZero"}, + {60, &ILibraryAppletAccessor::PresetLibraryAppletGpuTimeSliceZero, "PresetLibraryAppletGpuTimeSliceZero"}, {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, {102, nullptr, "PushExtraStorage"}, @@ -897,6 +910,13 @@ private: rb.Push(applet->GetStatus()); } + void PresetLibraryAppletGpuTimeSliceZero(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + void Start(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); @@ -998,8 +1018,8 @@ private: std::shared_ptr<Applets::Applet> applet; }; -IStorageAccessor::IStorageAccessor(IStorage& storage) - : ServiceFramework("IStorageAccessor"), backing(storage) { +IStorageAccessor::IStorageAccessor(Core::System& system_, IStorage& backing_) + : ServiceFramework{system_, "IStorageAccessor"}, backing{backing_} { // clang-format off static const FunctionInfo functions[] = { {0, &IStorageAccessor::GetSize, "GetSize"}, @@ -1027,20 +1047,21 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { const u64 offset{rp.Pop<u64>()}; const std::vector<u8> data{ctx.ReadBuffer()}; + const std::size_t size{std::min(data.size(), backing.GetSize() - offset)}; - LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); + LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); - if (data.size() > backing.GetSize() - offset) { + if (offset > backing.GetSize()) { LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", - backing.GetSize(), data.size(), offset); + backing.GetSize(), size, offset); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_SIZE_OUT_OF_BOUNDS); return; } - std::memcpy(backing.GetData().data() + offset, data.data(), data.size()); + std::memcpy(backing.GetData().data() + offset, data.data(), size); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1050,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 offset{rp.Pop<u64>()}; - const std::size_t size{ctx.GetWriteBufferSize()}; + const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)}; LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); - if (size > backing.GetSize() - offset) { + if (offset > backing.GetSize()) { LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", backing.GetSize(), size, offset); @@ -1070,7 +1091,7 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { } ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) - : ServiceFramework("ILibraryAppletCreator"), system{system_} { + : ServiceFramework{system_, "ILibraryAppletCreator"} { static const FunctionInfo functions[] = { {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, @@ -1089,14 +1110,14 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) const auto applet_id = rp.PopRaw<Applets::AppletId>(); const auto applet_mode = rp.PopRaw<u32>(); - LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", - static_cast<u32>(applet_id), applet_mode); + LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id, + applet_mode); const auto& applet_manager{system.GetAppletManager()}; const auto applet = applet_manager.GetApplet(applet_id); if (applet == nullptr) { - LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); @@ -1106,7 +1127,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet); + rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet); } void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { @@ -1118,7 +1139,7 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorage>(std::move(buffer)); + rb.PushIpcInterface<IStorage>(system, std::move(buffer)); } void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { @@ -1145,11 +1166,11 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IStorage>(std::move(memory)); + rb.PushIpcInterface<IStorage>(system, std::move(memory)); } IApplicationFunctions::IApplicationFunctions(Core::System& system_) - : ServiceFramework("IApplicationFunctions"), system{system_} { + : ServiceFramework{system_, "IApplicationFunctions"} { // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, @@ -1175,7 +1196,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, {50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"}, {60, nullptr, "SetMediaPlaybackStateForApplication"}, - {65, nullptr, "IsGamePlayRecordingSupported"}, + {65, &IApplicationFunctions::IsGamePlayRecordingSupported, "IsGamePlayRecordingSupported"}, {66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"}, {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"}, {68, nullptr, "RequestFlushGamePlayingMovieForDebug"}, @@ -1189,18 +1210,20 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {102, &IApplicationFunctions::SetApplicationCopyrightVisibility, "SetApplicationCopyrightVisibility"}, {110, &IApplicationFunctions::QueryApplicationPlayStatistics, "QueryApplicationPlayStatistics"}, {111, &IApplicationFunctions::QueryApplicationPlayStatisticsByUid, "QueryApplicationPlayStatisticsByUid"}, - {120, nullptr, "ExecuteProgram"}, - {121, nullptr, "ClearUserChannel"}, - {122, nullptr, "UnpopToUserChannel"}, - {123, nullptr, "GetPreviousProgramIndex"}, + {120, &IApplicationFunctions::ExecuteProgram, "ExecuteProgram"}, + {121, &IApplicationFunctions::ClearUserChannel, "ClearUserChannel"}, + {122, &IApplicationFunctions::UnpopToUserChannel, "UnpopToUserChannel"}, + {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"}, {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"}, - {141, nullptr, "TryPopFromFriendInvitationStorageChannel"}, + {141, &IApplicationFunctions::TryPopFromFriendInvitationStorageChannel, "TryPopFromFriendInvitationStorageChannel"}, {150, nullptr, "GetNotificationStorageChannelEvent"}, {151, nullptr, "TryPopFromNotificationStorageChannel"}, - {160, nullptr, "GetHealthWarningDisappearedSystemEvent"}, + {160, &IApplicationFunctions::GetHealthWarningDisappearedSystemEvent, "GetHealthWarningDisappearedSystemEvent"}, {170, nullptr, "SetHdcpAuthenticationActivated"}, + {180, nullptr, "GetLaunchRequiredVersion"}, + {181, nullptr, "UpgradeLaunchRequiredVersion"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, nullptr, "PrepareForJit"}, @@ -1210,11 +1233,15 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) RegisterHandlers(functions); auto& kernel = system.Kernel(); - gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); - - friend_invitation_storage_channel_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IApplicationFunctions:FriendInvitationStorageChannelEvent"); + gpu_error_detected_event = + Kernel::KEvent::Create(kernel, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); + gpu_error_detected_event->Initialize(); + friend_invitation_storage_channel_event = + Kernel::KEvent::Create(kernel, "IApplicationFunctions:FriendInvitationStorageChannelEvent"); + friend_invitation_storage_channel_event->Initialize(); + health_warning_disappeared_system_event = + Kernel::KEvent::Create(kernel, "IApplicationFunctions:HealthWarningDisappearedSystemEvent"); + health_warning_disappeared_system_event->Initialize(); } IApplicationFunctions::~IApplicationFunctions() = default; @@ -1285,7 +1312,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto kind = rp.PopEnum<LaunchParameterKind>(); - LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind)); + LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { @@ -1299,7 +1326,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { if (data.has_value()) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IStorage>(std::move(*data)); + rb.PushIpcInterface<IStorage>(system, std::move(*data)); launch_popped_application_specific = true; return; } @@ -1322,7 +1349,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); std::memcpy(buffer.data(), ¶ms, buffer.size()); - rb.PushIpcInterface<IStorage>(std::move(buffer)); + rb.PushIpcInterface<IStorage>(system, std::move(buffer)); launch_popped_account_preselect = true; return; } @@ -1379,13 +1406,16 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) { const auto res = [this] { const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; auto res = pm.GetControlMetadata(); if (res.first != nullptr) { return res; } - FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id), + system.GetFileSystemController(), + system.GetContentProvider()}; return pm_update.GetControlMetadata(); }(); @@ -1413,13 +1443,16 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { const auto res = [this] { const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; auto res = pm.GetControlMetadata(); if (res.first != nullptr) { return res; } - FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id), + system.GetFileSystemController(), + system.GetContentProvider()}; return pm_update.GetControlMetadata(); }(); @@ -1455,6 +1488,16 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { rb.Push(*res_code); } +void IApplicationFunctions::IsGamePlayRecordingSupported(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + constexpr bool gameplay_recording_supported = false; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(gameplay_recording_supported); +} + void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -1526,8 +1569,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto [type, user_id] = rp.PopRaw<Parameters>(); - LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), - user_id[1], user_id[0]); + LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1], + user_id[0]); const auto size = system.GetFileSystemController().ReadSaveDataSize( type, system.CurrentProcess()->GetTitleID(), user_id); @@ -1554,12 +1597,48 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque rb.Push<u32>(0); } +void IApplicationFunctions::ExecuteProgram(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto unk_1 = rp.Pop<u32>(); + [[maybe_unused]] const auto unk_2 = rp.Pop<u32>(); + const auto program_index = rp.Pop<u64>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + system.ExecuteProgram(program_index); +} + +void IApplicationFunctions::ClearUserChannel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IApplicationFunctions::UnpopToUserChannel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(previous_program_index); +} + void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(gpu_error_detected_event.readable); + rb.PushCopyObjects(gpu_error_detected_event->GetReadableEvent()); } void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx) { @@ -1567,25 +1646,41 @@ void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERe IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(friend_invitation_storage_channel_event.readable); + rb.PushCopyObjects(friend_invitation_storage_channel_event->GetReadableEvent()); +} + +void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel( + Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NO_DATA_IN_CHANNEL); +} + +void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(health_warning_disappeared_system_event->GetReadableEvent()); } -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system) { auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel()); // Needed on game boot message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); - std::make_shared<IdleSys>()->InstallAsService(service_manager); - std::make_shared<OMM>()->InstallAsService(service_manager); - std::make_shared<SPSM>()->InstallAsService(service_manager); - std::make_shared<TCAP>()->InstallAsService(service_manager); + std::make_shared<IdleSys>(system)->InstallAsService(service_manager); + std::make_shared<OMM>(system)->InstallAsService(service_manager); + std::make_shared<SPSM>(system)->InstallAsService(service_manager); + std::make_shared<TCAP>(system)->InstallAsService(service_manager); } -IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel) - : ServiceFramework("IHomeMenuFunctions"), kernel(kernel) { +IHomeMenuFunctions::IHomeMenuFunctions(Core::System& system_) + : ServiceFramework{system_, "IHomeMenuFunctions"} { // clang-format off static const FunctionInfo functions[] = { {10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"}, @@ -1603,8 +1698,9 @@ IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel) RegisterHandlers(functions); - pop_from_general_channel_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IHomeMenuFunctions:PopFromGeneralChannelEvent"); + pop_from_general_channel_event = + Kernel::KEvent::Create(system.Kernel(), "IHomeMenuFunctions:PopFromGeneralChannelEvent"); + pop_from_general_channel_event->Initialize(); } IHomeMenuFunctions::~IHomeMenuFunctions() = default; @@ -1621,10 +1717,11 @@ void IHomeMenuFunctions::GetPopFromGeneralChannelEvent(Kernel::HLERequestContext IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(pop_from_general_channel_event.readable); + rb.PushCopyObjects(pop_from_general_channel_event->GetReadableEvent()); } -IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") { +IGlobalStateController::IGlobalStateController(Core::System& system_) + : ServiceFramework{system_, "IGlobalStateController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestToEnterSleep"}, @@ -1647,7 +1744,8 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat IGlobalStateController::~IGlobalStateController() = default; -IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") { +IApplicationCreator::IApplicationCreator(Core::System& system_) + : ServiceFramework{system_, "IApplicationCreator"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateApplication"}, @@ -1662,8 +1760,8 @@ IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreat IApplicationCreator::~IApplicationCreator() = default; -IProcessWindingController::IProcessWindingController() - : ServiceFramework("IProcessWindingController") { +IProcessWindingController::IProcessWindingController(Core::System& system_) + : ServiceFramework{system_, "IProcessWindingController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLaunchReason"}, diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6e69796ec..6911f0d6e 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -7,11 +7,12 @@ #include <chrono> #include <memory> #include <queue> -#include "core/hle/kernel/writable_event.h" + #include "core/hle/service/service.h" namespace Kernel { class KernelCore; +class KEvent; class TransferMemory; } // namespace Kernel @@ -55,8 +56,8 @@ public: explicit AppletMessageQueue(Kernel::KernelCore& kernel); ~AppletMessageQueue(); - const std::shared_ptr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; - const std::shared_ptr<Kernel::ReadableEvent>& GetOperationModeChangedEvent() const; + const std::shared_ptr<Kernel::KReadableEvent>& GetMessageReceiveEvent() const; + const std::shared_ptr<Kernel::KReadableEvent>& GetOperationModeChangedEvent() const; void PushMessage(AppletMessage msg); AppletMessage PopMessage(); std::size_t GetMessageCount() const; @@ -65,8 +66,8 @@ public: private: std::queue<AppletMessage> messages; - Kernel::EventPair on_new_message; - Kernel::EventPair on_operation_mode_changed; + std::shared_ptr<Kernel::KEvent> on_new_message; + std::shared_ptr<Kernel::KEvent> on_operation_mode_changed; }; class IWindowController final : public ServiceFramework<IWindowController> { @@ -77,13 +78,11 @@ public: private: void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); void AcquireForegroundRights(Kernel::HLERequestContext& ctx); - - Core::System& system; }; class IAudioController final : public ServiceFramework<IAudioController> { public: - IAudioController(); + explicit IAudioController(Core::System& system_); ~IAudioController() override; private: @@ -109,20 +108,19 @@ private: class IDisplayController final : public ServiceFramework<IDisplayController> { public: - IDisplayController(); + explicit IDisplayController(Core::System& system_); ~IDisplayController() override; }; class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { public: - IDebugFunctions(); + explicit IDebugFunctions(Core::System& system_); ~IDebugFunctions() override; }; class ISelfController final : public ServiceFramework<ISelfController> { public: - explicit ISelfController(Core::System& system_, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger_); + explicit ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_); ~ISelfController() override; private: @@ -155,10 +153,9 @@ private: Disable = 2, }; - Core::System& system; - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; - Kernel::EventPair launchable_event; - Kernel::EventPair accumulated_suspended_tick_changed_event; + NVFlinger::NVFlinger& nvflinger; + std::shared_ptr<Kernel::KEvent> launchable_event; + std::shared_ptr<Kernel::KEvent> accumulated_suspended_tick_changed_event; u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; @@ -168,8 +165,8 @@ private: class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { public: - explicit ICommonStateGetter(Core::System& system, - std::shared_ptr<AppletMessageQueue> msg_queue); + explicit ICommonStateGetter(Core::System& system_, + std::shared_ptr<AppletMessageQueue> msg_queue_); ~ICommonStateGetter() override; private: @@ -193,11 +190,11 @@ private: void IsVrModeEnabled(Kernel::HLERequestContext& ctx); void SetVrModeEnabled(Kernel::HLERequestContext& ctx); void SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx); + void BeginVrModeEx(Kernel::HLERequestContext& ctx); void EndVrModeEx(Kernel::HLERequestContext& ctx); void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); void SetCpuBoostMode(Kernel::HLERequestContext& ctx); - Core::System& system; std::shared_ptr<AppletMessageQueue> msg_queue; bool vr_mode_state{}; }; @@ -212,7 +209,7 @@ public: class IStorage final : public ServiceFramework<IStorage> { public: - explicit IStorage(std::vector<u8>&& buffer); + explicit IStorage(Core::System& system_, std::vector<u8>&& buffer); ~IStorage() override; std::vector<u8>& GetData() { @@ -236,7 +233,7 @@ private: class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { public: - explicit IStorageAccessor(IStorage& backing); + explicit IStorageAccessor(Core::System& system_, IStorage& backing_); ~IStorageAccessor() override; private: @@ -256,8 +253,6 @@ private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); - - Core::System& system; }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { @@ -272,6 +267,7 @@ private: void SetTerminateResult(Kernel::HLERequestContext& ctx); void GetDisplayVersion(Kernel::HLERequestContext& ctx); void GetDesiredLanguage(Kernel::HLERequestContext& ctx); + void IsGamePlayRecordingSupported(Kernel::HLERequestContext& ctx); void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx); void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx); void NotifyRunning(Kernel::HLERequestContext& ctx); @@ -288,49 +284,55 @@ private: void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); + void ExecuteProgram(Kernel::HLERequestContext& ctx); + void ClearUserChannel(Kernel::HLERequestContext& ctx); + void UnpopToUserChannel(Kernel::HLERequestContext& ctx); + void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); + void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx); + void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx); bool launch_popped_application_specific = false; bool launch_popped_account_preselect = false; - Kernel::EventPair gpu_error_detected_event; - Kernel::EventPair friend_invitation_storage_channel_event; - Core::System& system; + s32 previous_program_index{-1}; + std::shared_ptr<Kernel::KEvent> gpu_error_detected_event; + std::shared_ptr<Kernel::KEvent> friend_invitation_storage_channel_event; + std::shared_ptr<Kernel::KEvent> health_warning_disappeared_system_event; }; class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { public: - explicit IHomeMenuFunctions(Kernel::KernelCore& kernel); + explicit IHomeMenuFunctions(Core::System& system_); ~IHomeMenuFunctions() override; private: void RequestToGetForeground(Kernel::HLERequestContext& ctx); void GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx); - Kernel::EventPair pop_from_general_channel_event; - Kernel::KernelCore& kernel; + std::shared_ptr<Kernel::KEvent> pop_from_general_channel_event; }; class IGlobalStateController final : public ServiceFramework<IGlobalStateController> { public: - IGlobalStateController(); + explicit IGlobalStateController(Core::System& system_); ~IGlobalStateController() override; }; class IApplicationCreator final : public ServiceFramework<IApplicationCreator> { public: - IApplicationCreator(); + explicit IApplicationCreator(Core::System& system_); ~IApplicationCreator() override; }; class IProcessWindingController final : public ServiceFramework<IProcessWindingController> { public: - IProcessWindingController(); + explicit IProcessWindingController(Core::System& system_); ~IProcessWindingController() override; }; /// Registers all AM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system); } // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 9df286d17..5421e0da0 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -3,8 +3,8 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -13,11 +13,11 @@ namespace Service::AM { class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> { public: - explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, - Core::System& system) - : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit ILibraryAppletProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr<AppletMessageQueue> msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "ILibraryAppletProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -66,7 +66,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAudioController>(); + rb.PushIpcInterface<IAudioController>(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -74,7 +74,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDisplayController>(); + rb.PushIpcInterface<IDisplayController>(system); } void GetProcessWindingController(Kernel::HLERequestContext& ctx) { @@ -82,7 +82,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProcessWindingController>(); + rb.PushIpcInterface<IProcessWindingController>(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -90,7 +90,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDebugFunctions>(); + rb.PushIpcInterface<IDebugFunctions>(system); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { @@ -109,17 +109,17 @@ private: rb.PushIpcInterface<IApplicationFunctions>(system); } - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; - Core::System& system; }; class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> { public: - explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) - : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit ISystemAppletProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr<AppletMessageQueue> msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "ISystemAppletProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -170,7 +170,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAudioController>(); + rb.PushIpcInterface<IAudioController>(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -178,7 +178,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDisplayController>(); + rb.PushIpcInterface<IDisplayController>(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -186,7 +186,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDebugFunctions>(); + rb.PushIpcInterface<IDebugFunctions>(system); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { @@ -202,7 +202,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHomeMenuFunctions>(system.Kernel()); + rb.PushIpcInterface<IHomeMenuFunctions>(system); } void GetGlobalStateController(Kernel::HLERequestContext& ctx) { @@ -210,7 +210,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGlobalStateController>(); + rb.PushIpcInterface<IGlobalStateController>(system); } void GetApplicationCreator(Kernel::HLERequestContext& ctx) { @@ -218,11 +218,11 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationCreator>(); + rb.PushIpcInterface<IApplicationCreator>(system); } - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + + NVFlinger::NVFlinger& nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; - Core::System& system; }; void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { @@ -249,10 +249,10 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system); } -AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) - : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { +AppletAE::AppletAE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "appletAE"}, nvflinger{nvflinger_}, msg_queue{ + std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"}, diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 2e3e45915..adb207349 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -23,8 +23,8 @@ class AppletMessageQueue; class AppletAE final : public ServiceFramework<AppletAE> { public: - explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system); + explicit AppletAE(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_); ~AppletAE() override; const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const; @@ -34,9 +34,8 @@ private: void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx); void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx); - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; - Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index a2ffaa440..f9eba8f52 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -12,10 +12,11 @@ namespace Service::AM { class IApplicationProxy final : public ServiceFramework<IApplicationProxy> { public: - explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) - : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit IApplicationProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr<AppletMessageQueue> msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "IApplicationProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -39,7 +40,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAudioController>(); + rb.PushIpcInterface<IAudioController>(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -47,7 +48,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDisplayController>(); + rb.PushIpcInterface<IDisplayController>(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -55,7 +56,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDebugFunctions>(); + rb.PushIpcInterface<IDebugFunctions>(system); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -98,9 +99,8 @@ private: rb.PushIpcInterface<IApplicationFunctions>(system); } - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; - Core::System& system; }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { @@ -111,10 +111,10 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system); } -AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system) - : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { +AppletOE::AppletOE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "appletOE"}, nvflinger{nvflinger_}, msg_queue{ + std::move(msg_queue_)} { static const FunctionInfo functions[] = { {0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, }; diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index 758da792d..6c1aa255a 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -23,8 +23,8 @@ class AppletMessageQueue; class AppletOE final : public ServiceFramework<AppletOE> { public: - explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, - std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system); + explicit AppletOE(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_); ~AppletOE() override; const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const; @@ -32,9 +32,8 @@ public: private: void OpenApplicationProxy(Kernel::HLERequestContext& ctx); - std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; - Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index c3261f3e6..e2f3b7563 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -3,18 +3,22 @@ // Refer to the license.txt file included. #include <cstring> + #include "common/assert.h" #include "core/core.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/error.h" #include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" -#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/server_session.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/controller.h" #include "core/hle/service/am/applets/error.h" #include "core/hle/service/am/applets/general_backend.h" #include "core/hle/service/am/applets/profile_select.h" @@ -25,11 +29,13 @@ namespace Service::AM::Applets { AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) { state_changed_event = - Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:StateChangedEvent"); - pop_out_data_event = - Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:PopDataOutEvent"); - pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair( - kernel, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); + Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:StateChangedEvent"); + state_changed_event->Initialize(); + pop_out_data_event = Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:PopDataOutEvent"); + pop_out_data_event->Initialize(); + pop_interactive_out_data_event = + Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); + pop_interactive_out_data_event->Initialize(); } AppletDataBroker::~AppletDataBroker() = default; @@ -56,7 +62,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { auto out = std::move(out_channel.front()); out_channel.pop_front(); - pop_out_data_event.writable->Clear(); + pop_out_data_event->GetWritableEvent()->Clear(); return out; } @@ -75,7 +81,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { auto out = std::move(out_interactive_channel.front()); out_interactive_channel.pop_front(); - pop_interactive_out_data_event.writable->Clear(); + pop_interactive_out_data_event->GetWritableEvent()->Clear(); return out; } @@ -94,7 +100,7 @@ void AppletDataBroker::PushNormalDataFromGame(std::shared_ptr<IStorage>&& storag void AppletDataBroker::PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage) { out_channel.emplace_back(std::move(storage)); - pop_out_data_event.writable->Signal(); + pop_out_data_event->GetWritableEvent()->Signal(); } void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage) { @@ -103,23 +109,23 @@ void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& s void AppletDataBroker::PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage) { out_interactive_channel.emplace_back(std::move(storage)); - pop_interactive_out_data_event.writable->Signal(); + pop_interactive_out_data_event->GetWritableEvent()->Signal(); } void AppletDataBroker::SignalStateChanged() const { - state_changed_event.writable->Signal(); + state_changed_event->GetWritableEvent()->Signal(); } -std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetNormalDataEvent() const { - return pop_out_data_event.readable; +std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetNormalDataEvent() const { + return pop_out_data_event->GetReadableEvent(); } -std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const { - return pop_interactive_out_data_event.readable; +std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const { + return pop_interactive_out_data_event->GetReadableEvent(); } -std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent() const { - return state_changed_event.readable; +std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetStateChangedEvent() const { + return state_changed_event->GetReadableEvent(); } Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {} @@ -140,14 +146,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, - PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce) - : parental_controls{std::move(parental_controls)}, error{std::move(error)}, - photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)}, - software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)}, - e_commerce{std::move(e_commerce)} {} +AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, + ParentalControlsApplet parental_controls_applet, + PhotoViewer photo_viewer_, ProfileSelect profile_select_, + SoftwareKeyboard software_keyboard_, WebBrowser web_browser_) + : controller{std::move(controller_applet)}, error{std::move(error_applet)}, + parental_controls{std::move(parental_controls_applet)}, + photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)}, + software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {} AppletFrontendSet::~AppletFrontendSet() = default; @@ -164,20 +170,33 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { } void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { - if (set.parental_controls != nullptr) - frontend.parental_controls = std::move(set.parental_controls); - if (set.error != nullptr) + if (set.controller != nullptr) { + frontend.controller = std::move(set.controller); + } + + if (set.error != nullptr) { frontend.error = std::move(set.error); - if (set.photo_viewer != nullptr) + } + + if (set.parental_controls != nullptr) { + frontend.parental_controls = std::move(set.parental_controls); + } + + if (set.photo_viewer != nullptr) { frontend.photo_viewer = std::move(set.photo_viewer); - if (set.profile_select != nullptr) + } + + if (set.profile_select != nullptr) { frontend.profile_select = std::move(set.profile_select); - if (set.software_keyboard != nullptr) + } + + if (set.software_keyboard != nullptr) { frontend.software_keyboard = std::move(set.software_keyboard); - if (set.web_browser != nullptr) + } + + if (set.web_browser != nullptr) { frontend.web_browser = std::move(set.web_browser); - if (set.e_commerce != nullptr) - frontend.e_commerce = std::move(set.e_commerce); + } } void AppletManager::SetDefaultAppletFrontendSet() { @@ -186,15 +205,20 @@ void AppletManager::SetDefaultAppletFrontendSet() { } void AppletManager::SetDefaultAppletsIfMissing() { - if (frontend.parental_controls == nullptr) { - frontend.parental_controls = - std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); + if (frontend.controller == nullptr) { + frontend.controller = + std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager()); } if (frontend.error == nullptr) { frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); } + if (frontend.parental_controls == nullptr) { + frontend.parental_controls = + std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); + } + if (frontend.photo_viewer == nullptr) { frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>(); } @@ -211,10 +235,6 @@ void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); } - - if (frontend.e_commerce == nullptr) { - frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>(); - } } void AppletManager::ClearAll() { @@ -225,19 +245,22 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { switch (id) { case AppletId::Auth: return std::make_shared<Auth>(system, *frontend.parental_controls); + case AppletId::Controller: + return std::make_shared<Controller>(system, *frontend.controller); case AppletId::Error: return std::make_shared<Error>(system, *frontend.error); case AppletId::ProfileSelect: return std::make_shared<ProfileSelect>(system, *frontend.profile_select); case AppletId::SoftwareKeyboard: return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard); + case AppletId::Web: + case AppletId::Shop: + case AppletId::OfflineWeb: + case AppletId::LoginShare: + case AppletId::WebAuth: + return std::make_shared<WebBrowser>(system, *frontend.web_browser); case AppletId::PhotoViewer: return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer); - case AppletId::LibAppletShop: - return std::make_shared<WebBrowser>(system, *frontend.web_browser, - frontend.e_commerce.get()); - case AppletId::LibAppletOff: - return std::make_shared<WebBrowser>(system, *frontend.web_browser); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index e75be86a2..b9a006317 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -6,9 +6,9 @@ #include <memory> #include <queue> + #include "common/swap.h" #include "core/hle/kernel/object.h" -#include "core/hle/kernel/writable_event.h" union ResultCode; @@ -17,6 +17,7 @@ class System; } namespace Core::Frontend { +class ControllerApplet; class ECommerceApplet; class ErrorApplet; class ParentalControlsApplet; @@ -28,7 +29,9 @@ class WebBrowserApplet; namespace Kernel { class KernelCore; -} +class KEvent; +class KReadableEvent; +} // namespace Kernel namespace Service::AM { @@ -49,13 +52,13 @@ enum class AppletId : u32 { ProfileSelect = 0x10, SoftwareKeyboard = 0x11, MiiEdit = 0x12, - LibAppletWeb = 0x13, - LibAppletShop = 0x14, + Web = 0x13, + Shop = 0x14, PhotoViewer = 0x15, Settings = 0x16, - LibAppletOff = 0x17, - LibAppletWhitelisted = 0x18, - LibAppletAuth = 0x19, + OfflineWeb = 0x17, + LoginShare = 0x18, + WebAuth = 0x19, MyPage = 0x1A, }; @@ -86,9 +89,9 @@ public: void SignalStateChanged() const; - std::shared_ptr<Kernel::ReadableEvent> GetNormalDataEvent() const; - std::shared_ptr<Kernel::ReadableEvent> GetInteractiveDataEvent() const; - std::shared_ptr<Kernel::ReadableEvent> GetStateChangedEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetNormalDataEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetInteractiveDataEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetStateChangedEvent() const; private: // Queues are named from applet's perspective @@ -105,13 +108,13 @@ private: // PopInteractiveDataToGame and PushInteractiveDataFromApplet std::deque<std::shared_ptr<IStorage>> out_interactive_channel; - Kernel::EventPair state_changed_event; + std::shared_ptr<Kernel::KEvent> state_changed_event; // Signaled on PushNormalDataFromApplet - Kernel::EventPair pop_out_data_event; + std::shared_ptr<Kernel::KEvent> pop_out_data_event; // Signaled on PushInteractiveDataFromApplet - Kernel::EventPair pop_interactive_out_data_event; + std::shared_ptr<Kernel::KEvent> pop_interactive_out_data_event; }; class Applet { @@ -155,19 +158,19 @@ protected: }; struct AppletFrontendSet { - using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; + using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>; using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>; + using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>; using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>; using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>; using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>; - using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>; AppletFrontendSet(); - AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, - PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce); + AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, + ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, + ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, + WebBrowser web_browser_); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -176,13 +179,13 @@ struct AppletFrontendSet { AppletFrontendSet(AppletFrontendSet&&) noexcept; AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; - ParentalControlsApplet parental_controls; + ControllerApplet controller; ErrorApplet error; + ParentalControlsApplet parental_controls; PhotoViewer photo_viewer; ProfileSelect profile_select; SoftwareKeyboard software_keyboard; WebBrowser web_browser; - ECommerceApplet e_commerce; }; class AppletManager { diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp new file mode 100644 index 000000000..c2bfe698f --- /dev/null +++ b/src/core/hle/service/am/applets/controller.cpp @@ -0,0 +1,252 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstring> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" + +namespace Service::AM::Applets { + +// This error code (0x183ACA) is thrown when the applet fails to initialize. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101}; +// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; + +static Core::Frontend::ControllerParameters ConvertToFrontendParameters( + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, + std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) { + HID::Controller_NPad::NpadStyleSet npad_style_set; + npad_style_set.raw = private_arg.style_set; + + return { + .min_players = std::max(s8{1}, header.player_count_min), + .max_players = header.player_count_max, + .keep_controllers_connected = header.enable_take_over_connection, + .enable_single_mode = header.enable_single_mode, + .enable_border_color = header.enable_identification_color, + .border_colors = std::move(identification_colors), + .enable_explain_text = enable_text, + .explain_text = std::move(text), + .allow_pro_controller = npad_style_set.fullkey == 1, + .allow_handheld = npad_style_set.handheld == 1, + .allow_dual_joycons = npad_style_set.joycon_dual == 1, + .allow_left_joycon = npad_style_set.joycon_left == 1, + .allow_right_joycon = npad_style_set.joycon_right == 1, + }; +} + +Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} + +Controller::~Controller() = default; + +void Controller::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_HID, "Initializing Controller Applet."); + + LOG_DEBUG(Service_HID, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + controller_applet_version = ControllerAppletVersion{common_args.library_version}; + + const auto private_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(private_arg_storage != nullptr); + + const auto& private_arg = private_arg_storage->GetData(); + ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); + + std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size()); + ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), + "Unknown ControllerSupportArgPrivate revision={} with size={}", + controller_applet_version, controller_private_arg.arg_private_size); + + // Some games such as Cave Story+ set invalid values for the ControllerSupportMode. + // Defer to arg_size to set the ControllerSupportMode. + if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) { + switch (controller_private_arg.arg_size) { + case sizeof(ControllerSupportArgOld): + case sizeof(ControllerSupportArgNew): + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + case sizeof(ControllerUpdateFirmwareArg): + controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate; + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}", + controller_private_arg.mode, controller_private_arg.arg_size); + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + } + } + + // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller. + // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem. + if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) { + if (controller_private_arg.flag_1 && + controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) { + controller_private_arg.caller = ControllerSupportCaller::System; + } else { + controller_private_arg.caller = ControllerSupportCaller::Application; + } + } + + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: + case ControllerSupportMode::ShowControllerStrapGuide: { + const auto user_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(user_arg_storage != nullptr); + + const auto& user_arg = user_arg_storage->GetData(); + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size()); + break; + case ControllerAppletVersion::Version7: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size()); + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", + controller_applet_version, controller_private_arg.arg_size); + ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + } + break; + } + case ControllerSupportMode::ShowControllerFirmwareUpdate: { + const auto update_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(update_arg_storage != nullptr); + + const auto& update_arg = update_arg_storage->GetData(); + ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg)); + + std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size()); + break; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); + break; + } + } +} + +bool Controller::TransactionComplete() const { + return complete; +} + +ResultCode Controller::GetStatus() const { + return status; +} + +void Controller::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void Controller::Execute() { + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto parameters = [this] { + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_old.header, + controller_user_arg_old.enable_explain_text, + std::vector<IdentificationColor>( + controller_user_arg_old.identification_colors.begin(), + controller_user_arg_old.identification_colors.end()), + std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(), + controller_user_arg_old.explain_text.end())); + case ControllerAppletVersion::Version7: + default: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_new.header, + controller_user_arg_new.enable_explain_text, + std::vector<IdentificationColor>( + controller_user_arg_new.identification_colors.begin(), + controller_user_arg_new.identification_colors.end()), + std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(), + controller_user_arg_new.explain_text.end())); + } + }(); + + is_single_mode = parameters.enable_single_mode; + + LOG_DEBUG(Service_HID, + "Controller Parameters: min_players={}, max_players={}, " + "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, " + "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, " + "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", + parameters.min_players, parameters.max_players, + parameters.keep_controllers_connected, parameters.enable_single_mode, + parameters.enable_border_color, parameters.enable_explain_text, + parameters.allow_pro_controller, parameters.allow_handheld, + parameters.allow_dual_joycons, parameters.allow_left_joycon, + parameters.allow_right_joycon); + + frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented", + controller_private_arg.mode); + ConfigurationComplete(); + break; + default: { + ConfigurationComplete(); + break; + } + } +} + +void Controller::ConfigurationComplete() { + ControllerSupportResultInfo result_info{}; + + const auto& players = Settings::values.players.GetValue(); + + // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. + // Otherwise, only count connected players from P1-P8. + result_info.player_count = + is_single_mode + ? 1 + : static_cast<s8>(std::count_if(players.begin(), players.end() - 2, + [](const auto& player) { return player.connected; })); + + result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance( + players.begin(), std::find_if(players.begin(), players.end(), + [](const auto& player) { return player.connected; }))); + + result_info.result = 0; + + LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}", + result_info.player_count, result_info.selected_id, result_info.result); + + complete = true; + out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo)); + std::memcpy(out_data.data(), &result_info, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h new file mode 100644 index 000000000..d4c9da7b1 --- /dev/null +++ b/src/core/hle/service/am/applets/controller.h @@ -0,0 +1,136 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +using IdentificationColor = std::array<u8, 4>; +using ExplainText = std::array<char, 0x81>; + +enum class ControllerAppletVersion : u32_le { + Version3 = 0x3, // 1.0.0 - 2.3.0 + Version4 = 0x4, // 3.0.0 - 5.1.0 + Version5 = 0x5, // 6.0.0 - 7.0.1 + Version7 = 0x7, // 8.0.0+ +}; + +enum class ControllerSupportMode : u8 { + ShowControllerSupport, + ShowControllerStrapGuide, + ShowControllerFirmwareUpdate, + + MaxControllerSupportMode, +}; + +enum class ControllerSupportCaller : u8 { + Application, + System, + + MaxControllerSupportCaller, +}; + +struct ControllerSupportArgPrivate { + u32 arg_private_size{}; + u32 arg_size{}; + bool flag_0{}; + bool flag_1{}; + ControllerSupportMode mode{}; + ControllerSupportCaller caller{}; + u32 style_set{}; + u32 joy_hold_type{}; +}; +static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, + "ControllerSupportArgPrivate has incorrect size."); + +struct ControllerSupportArgHeader { + s8 player_count_min{}; + s8 player_count_max{}; + bool enable_take_over_connection{}; + bool enable_left_justify{}; + bool enable_permit_joy_dual{}; + bool enable_single_mode{}; + bool enable_identification_color{}; +}; +static_assert(sizeof(ControllerSupportArgHeader) == 0x7, + "ControllerSupportArgHeader has incorrect size."); + +// LibraryAppletVersion 0x3, 0x4, 0x5 +struct ControllerSupportArgOld { + ControllerSupportArgHeader header{}; + std::array<IdentificationColor, 4> identification_colors{}; + bool enable_explain_text{}; + std::array<ExplainText, 4> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgOld) == 0x21C, + "ControllerSupportArgOld has incorrect size."); + +// LibraryAppletVersion 0x7 +struct ControllerSupportArgNew { + ControllerSupportArgHeader header{}; + std::array<IdentificationColor, 8> identification_colors{}; + bool enable_explain_text{}; + std::array<ExplainText, 8> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgNew) == 0x430, + "ControllerSupportArgNew has incorrect size."); + +struct ControllerUpdateFirmwareArg { + bool enable_force_update{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4, + "ControllerUpdateFirmwareArg has incorrect size."); + +struct ControllerSupportResultInfo { + s8 player_count{}; + INSERT_PADDING_BYTES(3); + u32 selected_id{}; + u32 result{}; +}; +static_assert(sizeof(ControllerSupportResultInfo) == 0xC, + "ControllerSupportResultInfo has incorrect size."); + +class Controller final : public Applet { +public: + explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_); + ~Controller() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ConfigurationComplete(); + +private: + const Core::Frontend::ControllerApplet& frontend; + Core::System& system; + + ControllerAppletVersion controller_applet_version; + ControllerSupportArgPrivate controller_private_arg; + ControllerSupportArgOld controller_user_arg_old; + ControllerSupportArgNew controller_user_arg_new; + ControllerUpdateFirmwareArg controller_update_arg; + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; + bool is_single_mode{false}; + std::vector<u8> out_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index f12fd7f89..0c8b632e8 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -20,9 +20,9 @@ namespace Service::AM::Applets { struct ShowError { u8 mode; bool jump; - INSERT_UNION_PADDING_BYTES(4); + INSERT_PADDING_BYTES_NOINIT(4); bool use_64bit_error_code; - INSERT_UNION_PADDING_BYTES(1); + INSERT_PADDING_BYTES_NOINIT(1); u64 error_code_64; u32 error_code_32; }; @@ -32,7 +32,7 @@ static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size."); struct ShowErrorRecord { u8 mode; bool jump; - INSERT_UNION_PADDING_BYTES(6); + INSERT_PADDING_BYTES_NOINIT(6); u64 error_code_64; u64 posix_time; }; @@ -41,7 +41,7 @@ static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect si struct SystemErrorArg { u8 mode; bool jump; - INSERT_UNION_PADDING_BYTES(6); + INSERT_PADDING_BYTES_NOINIT(6); u64 error_code_64; std::array<char, 8> language_code; std::array<char, 0x800> main_text; @@ -52,7 +52,7 @@ static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect si struct ApplicationErrorArg { u8 mode; bool jump; - INSERT_UNION_PADDING_BYTES(6); + INSERT_PADDING_BYTES_NOINIT(6); u32 error_code; std::array<char, 8> language_code; std::array<char, 0x800> main_text; @@ -87,7 +87,7 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} Error::~Error() = default; @@ -125,7 +125,7 @@ void Error::Initialize() { error_code = Decode64BitError(args->error_record.error_code_64); break; default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode)); + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); } } @@ -179,14 +179,14 @@ void Error::Execute() { error_code, std::chrono::seconds{args->error_record.posix_time}, callback); break; default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode)); + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); DisplayCompleted(); } } void Error::DisplayCompleted() { complete = true; - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{})); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{})); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index 104501ac5..4d1df5cbe 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -38,7 +38,7 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) } Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} Auth::~Auth() = default; @@ -90,7 +90,7 @@ void Auth::Execute() { const auto unimplemented_log = [this] { UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, " "arg1={:02X}, arg2={:02X}", - static_cast<u32>(type), arg0, arg1, arg2); + type, arg0, arg1, arg2); }; switch (type) { @@ -135,8 +135,8 @@ void Auth::Execute() { } } -void Auth::AuthFinished(bool successful) { - this->successful = successful; +void Auth::AuthFinished(bool is_successful) { + successful = is_successful; struct Return { ResultCode result_code; @@ -148,12 +148,12 @@ void Auth::AuthFinished(bool successful) { std::vector<u8> out(sizeof(Return)); std::memcpy(out.data(), &return_, sizeof(Return)); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out))); broker.SignalStateChanged(); } PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -193,17 +193,17 @@ void PhotoViewer::Execute() { frontend.ShowAllPhotos(callback); break; default: - UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", static_cast<u8>(mode)); + UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode); } } void PhotoViewer::ViewFinished() { - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{})); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{})); broker.SignalStateChanged(); } StubApplet::StubApplet(Core::System& system_, AppletId id_) - : Applet{system_.Kernel()}, id(id_), system{system_} {} + : Applet{system_.Kernel()}, id{id_}, system{system_} {} StubApplet::~StubApplet() = default; @@ -234,8 +234,9 @@ void StubApplet::ExecuteInteractive() { LOG_WARNING(Service_AM, "called (STUBBED)"); LogCurrentStorage(broker, "ExecuteInteractive"); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000))); - broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared<IStorage>(system, std::vector<u8>(0x1000))); broker.SignalStateChanged(); } @@ -243,8 +244,9 @@ void StubApplet::Execute() { LOG_WARNING(Service_AM, "called (STUBBED)"); LogCurrentStorage(broker, "Execute"); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000))); - broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared<IStorage>(system, std::vector<u8>(0x1000))); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index cfa2df369..ba76ae3d3 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -29,10 +29,11 @@ public: void ExecuteInteractive() override; void Execute() override; - void AuthFinished(bool successful = true); + void AuthFinished(bool is_successful = true); private: Core::Frontend::ParentalControlsApplet& frontend; + Core::System& system; bool complete = false; bool successful = false; diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 70cc23552..77fba16c7 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -17,7 +17,7 @@ constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; ProfileSelect::ProfileSelect(Core::System& system_, const Core::Frontend::ProfileSelectApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} ProfileSelect::~ProfileSelect() = default; @@ -50,7 +50,7 @@ void ProfileSelect::ExecuteInteractive() { void ProfileSelect::Execute() { if (complete) { - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data))); return; } @@ -71,7 +71,7 @@ void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) { final_data = std::vector<u8>(sizeof(UserSelectionOutput)); std::memcpy(final_data.data(), &output, final_data.size()); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data))); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 16364ead7..648d33a24 100644 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -53,6 +53,7 @@ private: bool complete = false; ResultCode status = RESULT_SUCCESS; std::vector<u8> final_data; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index bdeb0737a..79b209c6b 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -53,7 +53,7 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, const Core::Frontend::SoftwareKeyboardApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} SoftwareKeyboard::~SoftwareKeyboard() = default; @@ -121,8 +121,12 @@ void SoftwareKeyboard::ExecuteInteractive() { std::memcpy(&request, data.data(), sizeof(Request)); switch (request) { + case Request::Finalize: + complete = true; + broker.SignalStateChanged(); + break; case Request::Calc: { - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{1})); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{1})); broker.SignalStateChanged(); break; } @@ -135,7 +139,7 @@ void SoftwareKeyboard::ExecuteInteractive() { void SoftwareKeyboard::Execute() { if (complete) { - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data))); broker.SignalStateChanged(); return; } @@ -179,15 +183,17 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { final_data = output_main; if (complete) { - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main))); + broker.PushNormalDataFromApplet( + std::make_shared<IStorage>(system, std::move(output_main))); broker.SignalStateChanged(); } else { - broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::move(output_sub))); + broker.PushInteractiveDataFromApplet( + std::make_shared<IStorage>(system, std::move(output_sub))); } } else { output_main[0] = 1; complete = true; - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main))); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(output_main))); broker.SignalStateChanged(); } } diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 5a3824b5a..1d260fef8 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -80,6 +80,7 @@ private: bool complete = false; bool is_inline = false; std::vector<u8> final_data; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index efe595c4f..2ab420789 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -1,558 +1,478 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <array> -#include <cstring> -#include <vector> - #include "common/assert.h" -#include "common/common_funcs.h" #include "common/common_paths.h" #include "common/file_util.h" -#include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/mode.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/system_archive.h" -#include "core/file_sys/vfs_types.h" -#include "core/frontend/applets/general_frontend.h" +#include "core/file_sys/vfs_vector.h" #include "core/frontend/applets/web_browser.h" #include "core/hle/kernel/process.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/web_browser.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/loader.h" +#include "core/hle/service/ns/pl_u.h" namespace Service::AM::Applets { -enum class WebArgTLVType : u16 { - InitialURL = 0x1, - ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name. - CallbackURL = 0x3, - CallbackableURL = 0x4, - ApplicationID = 0x5, - DocumentPath = 0x6, - DocumentKind = 0x7, - SystemDataID = 0x8, - ShareStartPage = 0x9, - Whitelist = 0xA, - News = 0xB, - UserID = 0xE, - AlbumEntry0 = 0xF, - ScreenShotEnabled = 0x10, - EcClientCertEnabled = 0x11, - Unk12 = 0x12, - PlayReportEnabled = 0x13, - Unk14 = 0x14, - Unk15 = 0x15, - BootDisplayKind = 0x17, - BackgroundKind = 0x18, - FooterEnabled = 0x19, - PointerEnabled = 0x1A, - LeftStickMode = 0x1B, - KeyRepeatFrame1 = 0x1C, - KeyRepeatFrame2 = 0x1D, - BootAsMediaPlayerInv = 0x1E, - DisplayUrlKind = 0x1F, - BootAsMediaPlayer = 0x21, - ShopJumpEnabled = 0x22, - MediaAutoPlayEnabled = 0x23, - LobbyParameter = 0x24, - ApplicationAlbumEntry = 0x26, - JsExtensionEnabled = 0x27, - AdditionalCommentText = 0x28, - TouchEnabledOnContents = 0x29, - UserAgentAdditionalString = 0x2A, - AdditionalMediaData0 = 0x2B, - MediaPlayerAutoCloseEnabled = 0x2C, - PageCacheEnabled = 0x2D, - WebAudioEnabled = 0x2E, - Unk2F = 0x2F, - YouTubeVideoWhitelist = 0x31, - FooterFixedKind = 0x32, - PageFadeEnabled = 0x33, - MediaCreatorApplicationRatingAge = 0x34, - BootLoadingIconEnabled = 0x35, - PageScrollIndicationEnabled = 0x36, - MediaPlayerSpeedControlEnabled = 0x37, - AlbumEntry1 = 0x38, - AlbumEntry2 = 0x39, - AlbumEntry3 = 0x3A, - AdditionalMediaData1 = 0x3B, - AdditionalMediaData2 = 0x3C, - AdditionalMediaData3 = 0x3D, - BootFooterButton = 0x3E, - OverrideWebAudioVolume = 0x3F, - OverrideMediaAudioVolume = 0x40, - BootMode = 0x41, - WebSessionEnabled = 0x42, -}; - -enum class ShimKind : u32 { - Shop = 1, - Login = 2, - Offline = 3, - Share = 4, - Web = 5, - Wifi = 6, - Lobby = 7, -}; - -enum class ShopWebTarget { - ApplicationInfo, - AddOnContentList, - SubscriptionList, - ConsumableItemList, - Home, - Settings, -}; - namespace { -constexpr std::size_t SHIM_KIND_COUNT = 0x8; - -struct WebArgHeader { - u16 count; - INSERT_PADDING_BYTES(2); - ShimKind kind; -}; -static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); - -struct WebArgTLV { - WebArgTLVType type; - u16 size; - u32 offset; -}; -static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size."); - -struct WebCommonReturnValue { - u32 result_code; - INSERT_PADDING_BYTES(0x4); - std::array<char, 0x1000> last_url; - u64 last_url_size; -}; -static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); - -struct WebWifiPageArg { - INSERT_PADDING_BYTES(4); - std::array<char, 0x100> connection_test_url; - std::array<char, 0x400> initial_url; - std::array<u8, 0x10> nifm_network_uuid; - u32 nifm_requirement; -}; -static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size."); - -struct WebWifiReturnValue { - INSERT_PADDING_BYTES(4); - u32 result; -}; -static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size."); - -enum class OfflineWebSource : u32 { - OfflineHtmlPage = 0x1, - ApplicationLegalInformation = 0x2, - SystemDataPage = 0x3, -}; - -std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) { - if (arg.size() < sizeof(WebArgHeader)) - return {}; - - WebArgHeader header{}; - std::memcpy(&header, arg.data(), sizeof(WebArgHeader)); - - std::map<WebArgTLVType, std::vector<u8>> out; - u64 offset = sizeof(WebArgHeader); - for (std::size_t i = 0; i < header.count; ++i) { - if (arg.size() < (offset + sizeof(WebArgTLV))) - return out; +template <typename T> +void ParseRawValue(T& value, const std::vector<u8>& data) { + static_assert(std::is_trivially_copyable_v<T>, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); + std::memcpy(&value, data.data(), data.size()); +} - WebArgTLV tlv{}; - std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV)); - offset += sizeof(WebArgTLV); +template <typename T> +T ParseRawValue(const std::vector<u8>& data) { + T value; + ParseRawValue(value, data); + return value; +} - offset += tlv.offset; - if (arg.size() < (offset + tlv.size)) - return out; +std::string ParseStringValue(const std::vector<u8>& data) { + return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()), + data.size()); +} - std::vector<u8> data(tlv.size); - std::memcpy(data.data(), arg.data() + offset, tlv.size); - offset += tlv.size; +std::string GetMainURL(const std::string& url) { + const auto index = url.find('?'); - out.insert_or_assign(tlv.type, data); + if (index == std::string::npos) { + return url; } - return out; + return url.substr(0, index); } -FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id, - FileSys::ContentRecordType type) { - const auto& installed{system.GetContentProvider()}; - const auto res = installed.GetEntry(title_id, type); +WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) { + std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); - if (res != nullptr) { - return res->GetRomFS(); + if (web_arg.size() == sizeof(WebArgHeader)) { + return {}; } - if (type == FileSys::ContentRecordType::Data) { - return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + WebArgInputTLVMap input_tlv_map; + + u64 current_offset = sizeof(WebArgHeader); + + for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { + if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { + return input_tlv_map; + } + + WebArgInputTLV input_tlv; + std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); + + current_offset += sizeof(WebArgInputTLV); + + if (web_arg.size() < current_offset + input_tlv.arg_data_size) { + return input_tlv_map; + } + + std::vector<u8> data(input_tlv.arg_data_size); + std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); + + current_offset += input_tlv.arg_data_size; + + input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); } - return nullptr; + return input_tlv_map; } -} // Anonymous namespace +FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, + FileSys::ContentRecordType nca_type) { + if (nca_type == FileSys::ContentRecordType::Data) { + const auto nca = + system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type); + + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the System NAND!", + nca_type, title_id); + return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + } -WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, - Core::Frontend::ECommerceApplet* frontend_e_commerce_) - : Applet{system_.Kernel()}, frontend(frontend_), - frontend_e_commerce(frontend_e_commerce_), system{system_} {} + return nca->GetRomFS(); + } else { + const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type); -WebBrowser::~WebBrowser() = default; + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the ContentProvider!", + nca_type, title_id); + return nullptr; + } -void WebBrowser::Initialize() { - Applet::Initialize(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; - complete = false; - temporary_dir.clear(); - filename.clear(); - status = RESULT_SUCCESS; + return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); + } +} - const auto web_arg_storage = broker.PopNormalDataToApplet(); - ASSERT(web_arg_storage != nullptr); - const auto& web_arg = web_arg_storage->GetData(); +void ExtractSharedFonts(Core::System& system) { + static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{ + "FontStandard.ttf", + "FontChineseSimplified.ttf", + "FontExtendedChineseSimplified.ttf", + "FontChineseTraditional.ttf", + "FontKorean.ttf", + "FontNintendoExtended.ttf", + "FontNintendoExtended2.ttf", + }; - ASSERT(web_arg.size() >= 0x8); - std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind)); + for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { + const auto fonts_dir = Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); - args = GetWebArguments(web_arg); + const auto font_file_path = + Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]), + Common::FS::DirectorySeparator::PlatformDefault); - InitializeInternal(); -} + if (Common::FS::Exists(font_file_path)) { + continue; + } -bool WebBrowser::TransactionComplete() const { - return complete; -} + const auto font = NS::SHARED_FONTS[i]; + const auto font_title_id = static_cast<u64>(font.first); -ResultCode WebBrowser::GetStatus() const { - return status; -} + const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry( + font_title_id, FileSys::ContentRecordType::Data); -void WebBrowser::ExecuteInteractive() { - UNIMPLEMENTED_MSG("Unexpected interactive data recieved!"); -} + FileSys::VirtualFile romfs; -void WebBrowser::Execute() { - if (complete) { - return; - } + if (!nca) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id); + } else { + romfs = nca->GetRomFS(); + } - if (status != RESULT_SUCCESS) { - complete = true; + if (!romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!", + font_title_id); + continue; + } - // This is a workaround in order not to softlock yuzu when an error happens during the - // webapplet init. In order to avoid an svcBreak, the status is set to RESULT_SUCCESS - Finalize(); - status = RESULT_SUCCESS; + const auto extracted_romfs = FileSys::ExtractRomFS(romfs); - return; - } + if (!extracted_romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!", + font_title_id); + continue; + } - ExecuteInternal(); -} + const auto font_file = extracted_romfs->GetFile(font.second); -void WebBrowser::UnpackRomFS() { - if (unpacked) - return; + if (!font_file) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!", + font_title_id, font.second); + continue; + } - ASSERT(offline_romfs != nullptr); - const auto dir = - FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - const auto& vfs{system.GetFilesystem()}; - const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); - FileSys::VfsRawCopyD(dir, temp_dir); + std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32)); + font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize()); - unpacked = true; -} + std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), + Common::swap32); -void WebBrowser::Finalize() { - complete = true; + std::vector<u8> decrypted_data(font_file->GetSize() - 8); - WebCommonReturnValue out{}; - out.result_code = 0; - out.last_url_size = 0; + NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data); - std::vector<u8> data(sizeof(WebCommonReturnValue)); - std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue)); + FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>( + std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); - broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data))); - broker.SignalStateChanged(); + const auto temp_dir = + system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite); + + const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); - if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) { - Common::FS::DeleteDirRecursively(temporary_dir); + FileSys::VfsRawCopy(decrypted_font, out_file); } } -void WebBrowser::InitializeInternal() { - using WebAppletInitializer = void (WebBrowser::*)(); +} // namespace - constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{ - nullptr, &WebBrowser::InitializeShop, - nullptr, &WebBrowser::InitializeOffline, - nullptr, nullptr, - nullptr, nullptr, - }; +WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} - const auto index = static_cast<u32>(kind); +WebBrowser::~WebBrowser() = default; - if (index > functions.size() || functions[index] == nullptr) { - LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); - return; - } +void WebBrowser::Initialize() { + Applet::Initialize(); - const auto function = functions[index]; - (this->*function)(); -} + LOG_INFO(Service_AM, "Initializing Web Browser Applet."); -void WebBrowser::ExecuteInternal() { - using WebAppletExecutor = void (WebBrowser::*)(); + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); - constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{ - nullptr, &WebBrowser::ExecuteShop, - nullptr, &WebBrowser::ExecuteOffline, - nullptr, nullptr, - nullptr, nullptr, - }; + web_applet_version = WebAppletVersion{common_args.library_version}; - const auto index = static_cast<u32>(kind); + const auto web_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(web_arg_storage != nullptr); - if (index > functions.size() || functions[index] == nullptr) { - LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); - return; - } + const auto& web_arg = web_arg_storage->GetData(); + ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; }); - const auto function = functions[index]; - (this->*function)(); -} + web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header); -void WebBrowser::InitializeShop() { - if (frontend_e_commerce == nullptr) { - LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!"); - status = RESULT_UNKNOWN; - return; - } + LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", + web_arg_header.total_tlv_entries, web_arg_header.shim_kind); - const auto user_id_data = args.find(WebArgTLVType::UserID); + ExtractSharedFonts(system); - user_id = std::nullopt; - if (user_id_data != args.end()) { - user_id = u128{}; - std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128)); + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + InitializeShop(); + break; + case ShimKind::Login: + InitializeLogin(); + break; + case ShimKind::Offline: + InitializeOffline(); + break; + case ShimKind::Share: + InitializeShare(); + break; + case ShimKind::Web: + InitializeWeb(); + break; + case ShimKind::Wifi: + InitializeWifi(); + break; + case ShimKind::Lobby: + InitializeLobby(); + break; + default: + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + break; } +} - const auto url = args.find(WebArgTLVType::ShopArgumentsURL); +bool WebBrowser::TransactionComplete() const { + return complete; +} - if (url == args.end()) { - LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!"); - status = RESULT_UNKNOWN; - return; - } +ResultCode WebBrowser::GetStatus() const { + return status; +} - std::vector<std::string> split_query; - Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(url->second.data()), url->second.size()), - '?', split_query); - - // 2 -> Main URL '?' Query Parameters - // Less is missing info, More is malformed - if (split_query.size() != 2) { - LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed"); - status = RESULT_UNKNOWN; - return; - } +void WebBrowser::ExecuteInteractive() { + UNIMPLEMENTED_MSG("WebSession is not implemented"); +} - std::vector<std::string> queries; - Common::SplitString(split_query[1], '&', queries); +void WebBrowser::Execute() { + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + ExecuteShop(); + break; + case ShimKind::Login: + ExecuteLogin(); + break; + case ShimKind::Offline: + ExecuteOffline(); + break; + case ShimKind::Share: + ExecuteShare(); + break; + case ShimKind::Web: + ExecuteWeb(); + break; + case ShimKind::Wifi: + ExecuteWifi(); + break; + case ShimKind::Lobby: + ExecuteLobby(); + break; + default: + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + WebBrowserExit(WebExitReason::EndButtonPressed); + break; + } +} - const auto split_single_query = - [](const std::string& in) -> std::pair<std::string, std::string> { - const auto index = in.find('='); - if (index == std::string::npos || index == in.size() - 1) { - return {in, ""}; - } +void WebBrowser::ExtractOfflineRomFS() { + LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir); - return {in.substr(0, index), in.substr(index + 1)}; - }; + const auto extracted_romfs_dir = + FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - std::transform(queries.begin(), queries.end(), - std::inserter(shop_query, std::next(shop_query.begin())), split_single_query); + const auto temp_dir = + system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite); - const auto scene = shop_query.find("scene"); + FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); +} - if (scene == shop_query.end()) { - LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!"); - status = RESULT_UNKNOWN; - return; +void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { + if ((web_arg_header.shim_kind == ShimKind::Share && + web_applet_version >= WebAppletVersion::Version196608) || + (web_arg_header.shim_kind == ShimKind::Web && + web_applet_version >= WebAppletVersion::Version524288)) { + // TODO: Push Output TLVs instead of a WebCommonReturnValue } - const std::map<std::string, ShopWebTarget, std::less<>> target_map{ - {"product_detail", ShopWebTarget::ApplicationInfo}, - {"aocs", ShopWebTarget::AddOnContentList}, - {"subscriptions", ShopWebTarget::SubscriptionList}, - {"consumption", ShopWebTarget::ConsumableItemList}, - {"settings", ShopWebTarget::Settings}, - {"top", ShopWebTarget::Home}, - }; + WebCommonReturnValue web_common_return_value; - const auto target = target_map.find(scene->second); - if (target == target_map.end()) { - LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second); - status = RESULT_UNKNOWN; - return; - } + web_common_return_value.exit_reason = exit_reason; + std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size()); + web_common_return_value.last_url_size = last_url.size(); - shop_web_target = target->second; + LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}", + exit_reason, last_url, last_url.size()); - const auto title_id_data = shop_query.find("dst_app_id"); - if (title_id_data != shop_query.end()) { - title_id = std::stoull(title_id_data->second, nullptr, 0x10); - } + complete = true; + std::vector<u8> out_data(sizeof(WebCommonReturnValue)); + std::memcpy(out_data.data(), &web_common_return_value, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); + broker.SignalStateChanged(); +} - const auto mode_data = shop_query.find("mode"); - if (mode_data != shop_query.end()) { - shop_full_display = mode_data->second == "full"; - } +bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const { + return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end(); } -void WebBrowser::InitializeOffline() { - if (args.find(WebArgTLVType::DocumentPath) == args.end() || - args.find(WebArgTLVType::DocumentKind) == args.end() || - args.find(WebArgTLVType::ApplicationID) == args.end()) { - status = RESULT_UNKNOWN; - LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!"); +std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) { + const auto map_it = web_arg_input_tlv_map.find(input_tlv_type); + + if (map_it == web_arg_input_tlv_map.end()) { + return std::nullopt; } - const auto url_data = args[WebArgTLVType::DocumentPath]; - filename = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(url_data.data()), url_data.size()); + return map_it->second; +} - OfflineWebSource source; - ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4); - std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource)); +void WebBrowser::InitializeShop() {} - constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{ - "manual", - "legal", - "system", - }; +void WebBrowser::InitializeLogin() {} + +void WebBrowser::InitializeOffline() { + const auto document_path = + ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value()); + + const auto document_kind = + ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value()); + + std::string additional_paths; - temporary_dir = - Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + - "web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1], - Common::FS::DirectorySeparator::PlatformDefault); - Common::FS::DeleteDirRecursively(temporary_dir); - - u64 title_id = 0; // 0 corresponds to current process - ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8); - std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64)); - FileSys::ContentRecordType type = FileSys::ContentRecordType::Data; - - switch (source) { - case OfflineWebSource::OfflineHtmlPage: - // While there is an AppID TLV field, in official SW this is always ignored. - title_id = 0; - type = FileSys::ContentRecordType::HtmlDocument; + switch (document_kind) { + case DocumentKind::OfflineHtmlPage: + default: + title_id = system.CurrentProcess()->GetTitleID(); + nca_type = FileSys::ContentRecordType::HtmlDocument; + additional_paths = "html-document"; break; - case OfflineWebSource::ApplicationLegalInformation: - type = FileSys::ContentRecordType::LegalInformation; + case DocumentKind::ApplicationLegalInformation: + title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value()); + nca_type = FileSys::ContentRecordType::LegalInformation; break; - case OfflineWebSource::SystemDataPage: - type = FileSys::ContentRecordType::Data; + case DocumentKind::SystemDataPage: + title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value()); + nca_type = FileSys::ContentRecordType::Data; break; } - if (title_id == 0) { - title_id = system.CurrentProcess()->GetTitleID(); - } + static constexpr std::array<const char*, 3> RESOURCE_TYPES{ + "manual", + "legal_information", + "system_data", + }; - offline_romfs = GetApplicationRomFS(system, title_id, type); - if (offline_romfs == nullptr) { - status = RESULT_UNKNOWN; - LOG_ERROR(Service_AM, "Failed to find offline data for request!"); - } + offline_cache_dir = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_{}/{:016X}", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), + RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id), + Common::FS::DirectorySeparator::PlatformDefault); - std::string path_additional_directory; - if (source == OfflineWebSource::OfflineHtmlPage) { - path_additional_directory = std::string(DIR_SEP).append("html-document"); - } + offline_document = Common::FS::SanitizePath( + fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path), + Common::FS::DirectorySeparator::PlatformDefault); +} + +void WebBrowser::InitializeShare() {} - filename = - Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, - Common::FS::DirectorySeparator::PlatformDefault); +void WebBrowser::InitializeWeb() { + external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value()); } +void WebBrowser::InitializeWifi() {} + +void WebBrowser::InitializeLobby() {} + void WebBrowser::ExecuteShop() { - const auto callback = [this]() { Finalize(); }; + LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} - const auto check_optional_parameter = [this](const auto& p) { - if (!p.has_value()) { - LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!"); - status = RESULT_UNKNOWN; - return false; - } +void WebBrowser::ExecuteLogin() { + LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} - return true; - }; +void WebBrowser::ExecuteOffline() { + const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document), + Common::FS::DirectorySeparator::PlatformDefault); - switch (shop_web_target) { - case ShopWebTarget::ApplicationInfo: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id, - shop_full_display, shop_extra_parameter); - break; - case ShopWebTarget::AddOnContentList: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display); - break; - case ShopWebTarget::ConsumableItemList: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id); - break; - case ShopWebTarget::Home: - if (!check_optional_parameter(user_id)) - return; - if (!check_optional_parameter(shop_full_display)) - return; - frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display); - break; - case ShopWebTarget::Settings: - if (!check_optional_parameter(user_id)) - return; - if (!check_optional_parameter(shop_full_display)) - return; - frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display); - break; - case ShopWebTarget::SubscriptionList: - if (!check_optional_parameter(title_id)) + if (!Common::FS::Exists(main_url)) { + offline_romfs = GetOfflineRomFS(system, title_id, nca_type); + + if (offline_romfs == nullptr) { + LOG_ERROR(Service_AM, + "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id, + nca_type); + WebBrowserExit(WebExitReason::WindowClosed); return; - frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id); - break; - default: - UNREACHABLE(); + } } + + LOG_INFO(Service_AM, "Opening offline document at {}", offline_document); + + frontend.OpenLocalWebPage( + offline_document, [this] { ExtractOfflineRomFS(); }, + [this](WebExitReason exit_reason, std::string last_url) { + WebBrowserExit(exit_reason, last_url); + }); } -void WebBrowser::ExecuteOffline() { - frontend.OpenPageLocal( - filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); +void WebBrowser::ExecuteShare() { + LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteWeb() { + LOG_INFO(Service_AM, "Opening external URL at {}", external_url); + + frontend.OpenExternalWebPage(external_url, + [this](WebExitReason exit_reason, std::string last_url) { + WebBrowserExit(exit_reason, last_url); + }); } +void WebBrowser::ExecuteWifi() { + LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteLobby() { + LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 8d4027411..04c274754 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -1,28 +1,31 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <map> +#include <optional> + +#include "common/common_funcs.h" +#include "common/common_types.h" #include "core/file_sys/vfs_types.h" -#include "core/hle/service/am/am.h" +#include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/web_types.h" namespace Core { class System; } -namespace Service::AM::Applets { +namespace FileSys { +enum class ContentRecordType : u8; +} -enum class ShimKind : u32; -enum class ShopWebTarget; -enum class WebArgTLVType : u16; +namespace Service::AM::Applets { class WebBrowser final : public Applet { public: - WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, - Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr); + WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_); ~WebBrowser() override; @@ -33,49 +36,50 @@ public: void ExecuteInteractive() override; void Execute() override; - // Callback to be fired when the frontend needs the manual RomFS unpacked to temporary - // directory. This is a blocking call and may take a while as some manuals can be up to 100MB in - // size. Attempting to access files at filename before invocation is likely to not work. - void UnpackRomFS(); + void ExtractOfflineRomFS(); - // Callback to be fired when the frontend is finished browsing. This will delete the temporary - // manual RomFS extracted files, so ensure this is only called at actual finalization. - void Finalize(); + void WebBrowserExit(WebExitReason exit_reason, std::string last_url = ""); private: - void InitializeInternal(); - void ExecuteInternal(); + bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const; - // Specific initializers for the types of web applets + std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type); + + // Initializers for the various types of browser applets void InitializeShop(); + void InitializeLogin(); void InitializeOffline(); + void InitializeShare(); + void InitializeWeb(); + void InitializeWifi(); + void InitializeLobby(); - // Specific executors for the types of web applets + // Executors for the various types of browser applets void ExecuteShop(); + void ExecuteLogin(); void ExecuteOffline(); + void ExecuteShare(); + void ExecuteWeb(); + void ExecuteWifi(); + void ExecuteLobby(); - Core::Frontend::WebBrowserApplet& frontend; - - // Extra frontends for specialized functions - Core::Frontend::ECommerceApplet* frontend_e_commerce; + const Core::Frontend::WebBrowserApplet& frontend; - bool complete = false; - bool unpacked = false; - ResultCode status = RESULT_SUCCESS; + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; - ShimKind kind; - std::map<WebArgTLVType, std::vector<u8>> args; + WebAppletVersion web_applet_version; + WebExitReason web_exit_reason; + WebArgHeader web_arg_header; + WebArgInputTLVMap web_arg_input_tlv_map; + u64 title_id; + FileSys::ContentRecordType nca_type; + std::string offline_cache_dir; + std::string offline_document; FileSys::VirtualFile offline_romfs; - std::string temporary_dir; - std::string filename; - - ShopWebTarget shop_web_target; - std::map<std::string, std::string, std::less<>> shop_query; - std::optional<u64> title_id = 0; - std::optional<u128> user_id; - std::optional<bool> shop_full_display; - std::string shop_extra_parameter; + + std::string external_url; Core::System& system; }; diff --git a/src/core/hle/service/am/applets/web_types.h b/src/core/hle/service/am/applets/web_types.h new file mode 100644 index 000000000..419c2bf79 --- /dev/null +++ b/src/core/hle/service/am/applets/web_types.h @@ -0,0 +1,178 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <unordered_map> +#include <vector> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +enum class WebAppletVersion : u32_le { + Version0 = 0x0, // Only used by WifiWebAuthApplet + Version131072 = 0x20000, // 1.0.0 - 2.3.0 + Version196608 = 0x30000, // 3.0.0 - 4.1.0 + Version327680 = 0x50000, // 5.0.0 - 5.1.0 + Version393216 = 0x60000, // 6.0.0 - 7.0.1 + Version524288 = 0x80000, // 8.0.0+ +}; + +enum class ShimKind : u32 { + Shop = 1, + Login = 2, + Offline = 3, + Share = 4, + Web = 5, + Wifi = 6, + Lobby = 7, +}; + +enum class WebExitReason : u32 { + EndButtonPressed = 0, + BackButtonPressed = 1, + ExitRequested = 2, + CallbackURL = 3, + WindowClosed = 4, + ErrorDialog = 7, +}; + +enum class WebArgInputTLVType : u16 { + InitialURL = 0x1, + CallbackURL = 0x3, + CallbackableURL = 0x4, + ApplicationID = 0x5, + DocumentPath = 0x6, + DocumentKind = 0x7, + SystemDataID = 0x8, + ShareStartPage = 0x9, + Whitelist = 0xA, + News = 0xB, + UserID = 0xE, + AlbumEntry0 = 0xF, + ScreenShotEnabled = 0x10, + EcClientCertEnabled = 0x11, + PlayReportEnabled = 0x13, + BootDisplayKind = 0x17, + BackgroundKind = 0x18, + FooterEnabled = 0x19, + PointerEnabled = 0x1A, + LeftStickMode = 0x1B, + KeyRepeatFrame1 = 0x1C, + KeyRepeatFrame2 = 0x1D, + BootAsMediaPlayerInverted = 0x1E, + DisplayURLKind = 0x1F, + BootAsMediaPlayer = 0x21, + ShopJumpEnabled = 0x22, + MediaAutoPlayEnabled = 0x23, + LobbyParameter = 0x24, + ApplicationAlbumEntry = 0x26, + JsExtensionEnabled = 0x27, + AdditionalCommentText = 0x28, + TouchEnabledOnContents = 0x29, + UserAgentAdditionalString = 0x2A, + AdditionalMediaData0 = 0x2B, + MediaPlayerAutoCloseEnabled = 0x2C, + PageCacheEnabled = 0x2D, + WebAudioEnabled = 0x2E, + YouTubeVideoWhitelist = 0x31, + FooterFixedKind = 0x32, + PageFadeEnabled = 0x33, + MediaCreatorApplicationRatingAge = 0x34, + BootLoadingIconEnabled = 0x35, + PageScrollIndicatorEnabled = 0x36, + MediaPlayerSpeedControlEnabled = 0x37, + AlbumEntry1 = 0x38, + AlbumEntry2 = 0x39, + AlbumEntry3 = 0x3A, + AdditionalMediaData1 = 0x3B, + AdditionalMediaData2 = 0x3C, + AdditionalMediaData3 = 0x3D, + BootFooterButton = 0x3E, + OverrideWebAudioVolume = 0x3F, + OverrideMediaAudioVolume = 0x40, + BootMode = 0x41, + WebSessionEnabled = 0x42, + MediaPlayerOfflineEnabled = 0x43, +}; + +enum class WebArgOutputTLVType : u16 { + ShareExitReason = 0x1, + LastURL = 0x2, + LastURLSize = 0x3, + SharePostResult = 0x4, + PostServiceName = 0x5, + PostServiceNameSize = 0x6, + PostID = 0x7, + PostIDSize = 0x8, + MediaPlayerAutoClosedByCompletion = 0x9, +}; + +enum class DocumentKind : u32 { + OfflineHtmlPage = 1, + ApplicationLegalInformation = 2, + SystemDataPage = 3, +}; + +enum class ShareStartPage : u32 { + Default, + Settings, +}; + +enum class BootDisplayKind : u32 { + Default, + White, + Black, +}; + +enum class BackgroundKind : u32 { + Default, +}; + +enum class LeftStickMode : u32 { + Pointer, + Cursor, +}; + +enum class WebSessionBootMode : u32 { + AllForeground, + AllForegroundInitiallyHidden, +}; + +struct WebArgHeader { + u16 total_tlv_entries{}; + INSERT_PADDING_BYTES(2); + ShimKind shim_kind{}; +}; +static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); + +struct WebArgInputTLV { + WebArgInputTLVType input_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size."); + +struct WebArgOutputTLV { + WebArgOutputTLVType output_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size."); + +struct WebCommonReturnValue { + WebExitReason exit_reason{}; + INSERT_PADDING_WORDS(1); + std::array<char, 0x1000> last_url{}; + u64 last_url_size{}; +}; +static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); + +using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp index d256d57c8..6196773d5 100644 --- a/src/core/hle/service/am/idle.cpp +++ b/src/core/hle/service/am/idle.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -IdleSys::IdleSys() : ServiceFramework{"idle:sys"} { +IdleSys::IdleSys(Core::System& system_) : ServiceFramework{system_, "idle:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAutoPowerDownEvent"}, diff --git a/src/core/hle/service/am/idle.h b/src/core/hle/service/am/idle.h index c44e856b1..e290c30b1 100644 --- a/src/core/hle/service/am/idle.h +++ b/src/core/hle/service/am/idle.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class IdleSys final : public ServiceFramework<IdleSys> { public: - explicit IdleSys(); + explicit IdleSys(Core::System& system_); ~IdleSys() override; }; diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp index 37389ccda..55de67e1d 100644 --- a/src/core/hle/service/am/omm.cpp +++ b/src/core/hle/service/am/omm.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -OMM::OMM() : ServiceFramework{"omm"} { +OMM::OMM(Core::System& system_) : ServiceFramework{system_, "omm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetOperationMode"}, diff --git a/src/core/hle/service/am/omm.h b/src/core/hle/service/am/omm.h index 59dc91b72..3766150fe 100644 --- a/src/core/hle/service/am/omm.h +++ b/src/core/hle/service/am/omm.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class OMM final : public ServiceFramework<OMM> { public: - explicit OMM(); + explicit OMM(Core::System& system_); ~OMM() override; }; diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp index f27729ce7..95218d9ee 100644 --- a/src/core/hle/service/am/spsm.cpp +++ b/src/core/hle/service/am/spsm.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -SPSM::SPSM() : ServiceFramework{"spsm"} { +SPSM::SPSM(Core::System& system_) : ServiceFramework{system_, "spsm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, diff --git a/src/core/hle/service/am/spsm.h b/src/core/hle/service/am/spsm.h index 3a0b979fa..04bbf9e68 100644 --- a/src/core/hle/service/am/spsm.h +++ b/src/core/hle/service/am/spsm.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class SPSM final : public ServiceFramework<SPSM> { public: - explicit SPSM(); + explicit SPSM(Core::System& system_); ~SPSM() override; }; diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp index a75cbdda8..4d0971c03 100644 --- a/src/core/hle/service/am/tcap.cpp +++ b/src/core/hle/service/am/tcap.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -TCAP::TCAP() : ServiceFramework{"tcap"} { +TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetContinuousHighSkinTemperatureEvent"}, diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h index 2021b55d1..e9578f16e 100644 --- a/src/core/hle/service/am/tcap.h +++ b/src/core/hle/service/am/tcap.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class TCAP final : public ServiceFramework<TCAP> { public: - explicit TCAP(); + explicit TCAP(Core::System& system_); ~TCAP() override; }; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 8e79f707b..8d657c0bf 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -5,28 +5,28 @@ #include <algorithm> #include <numeric> #include <vector> + #include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/aoc/aoc_u.h" #include "core/loader/loader.h" #include "core/settings.h" namespace Service::AOC { -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; -constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000; - static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { - return (title_id & DLC_BASE_TITLE_ID_MASK) == base; + return FileSys::GetBaseTitleID(title_id) == base; } static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) { @@ -47,8 +47,65 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) { return add_on_content; } -AOC_U::AOC_U(Core::System& system) - : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs(system)), system(system) { +class IPurchaseEventManager final : public ServiceFramework<IPurchaseEventManager> { +public: + explicit IPurchaseEventManager(Core::System& system_) + : ServiceFramework{system_, "IPurchaseEventManager"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, + {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, + {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, + {3, nullptr, "PopPurchasedProductInfo"}, + {4, nullptr, "PopPurchasedProductInfoWithUid"}, + }; + // clang-format on + + RegisterHandlers(functions); + + purchased_event = + Kernel::KEvent::Create(system.Kernel(), "IPurchaseEventManager:PurchasedEvent"); + purchased_event->Initialize(); + } + +private: + void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown_1 = rp.Pop<u64>(); + [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer(); + + LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetDeliveryTarget(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown_1 = rp.Pop<u64>(); + [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer(); + + LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(purchased_event->GetReadableEvent()); + } + + std::shared_ptr<Kernel::KEvent> purchased_event; +}; + +AOC_U::AOC_U(Core::System& system_) + : ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CountAddOnContentByApplicationId"}, @@ -61,16 +118,16 @@ AOC_U::AOC_U(Core::System& system) {7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"}, {8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"}, {9, nullptr, "GetAddOnContentLostErrorCode"}, - {100, nullptr, "CreateEcPurchasedEventManager"}, - {101, nullptr, "CreatePermanentEcPurchasedEventManager"}, + {100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"}, + {101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"}, }; // clang-format on RegisterHandlers(functions); auto& kernel = system.Kernel(); - aoc_change_event = - Kernel::WritableEvent::CreateEventPair(kernel, "GetAddOnContentListChanged:Event"); + aoc_change_event = Kernel::KEvent::Create(kernel, "GetAddOnContentListChanged:Event"); + aoc_change_event->Initialize(); } AOC_U::~AOC_U() = default; @@ -122,11 +179,11 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) { for (u64 content_id : add_on_content) { - if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) { + if (FileSys::GetBaseTitleID(content_id) != current) { continue; } - out.push_back(static_cast<u32>(content_id & 0x7FF)); + out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id))); } } @@ -163,11 +220,12 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto res = pm.GetControlMetadata(); if (res.first == nullptr) { - rb.Push(title_id + DLC_BASE_TO_AOC_ID); + rb.Push(FileSys::GetAOCBaseTitleID(title_id)); return; } @@ -196,7 +254,23 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(aoc_change_event.readable); + rb.PushCopyObjects(aoc_change_event->GetReadableEvent()); +} + +void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IPurchaseEventManager>(system); +} + +void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IPurchaseEventManager>(system); } void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 848b2f416..1aa23529e 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -6,8 +6,12 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { -class WritableEvent; +class KEvent; } namespace Service::AOC { @@ -23,10 +27,11 @@ private: void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx); void PrepareAddOnContent(Kernel::HLERequestContext& ctx); void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx); + void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx); + void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx); std::vector<u64> add_on_content; - Kernel::EventPair aoc_change_event; - Core::System& system; + std::shared_ptr<Kernel::KEvent> aoc_change_event; }; /// Registers all AOC services with the specified service manager. diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index 85bbf5988..97d6619dd 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/interface.h" @@ -13,13 +14,14 @@ Module::~Module() = default; void InstallInterfaces(Core::System& system) { auto module_ = std::make_shared<Module>(); - std::make_shared<APM>(module_, system.GetAPMController(), "apm") + std::make_shared<APM>(system, module_, system.GetAPMController(), "apm") ->InstallAsService(system.ServiceManager()); - std::make_shared<APM>(module_, system.GetAPMController(), "apm:p") + std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:p") ->InstallAsService(system.ServiceManager()); - std::make_shared<APM>(module_, system.GetAPMController(), "apm:am") + std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:am") + ->InstallAsService(system.ServiceManager()); + std::make_shared<APM_Sys>(system, system.GetAPMController()) ->InstallAsService(system.ServiceManager()); - std::make_shared<APM_Sys>(system.GetAPMController())->InstallAsService(system.ServiceManager()); } } // namespace Service::APM diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index cf4c2bb11..691fe6c16 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -4,7 +4,9 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} namespace Service::APM { diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp index 25a886238..03636642b 100644 --- a/src/core/hle/service/apm/controller.cpp +++ b/src/core/hle/service/apm/controller.cpp @@ -48,8 +48,7 @@ void Controller::SetPerformanceConfiguration(PerformanceMode mode, [config](const auto& entry) { return entry.first == config; }); if (iter == config_to_speed.cend()) { - LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", - static_cast<u32>(config)); + LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config); return; } @@ -69,7 +68,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { } PerformanceMode Controller::GetCurrentPerformanceMode() const { - return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld; + return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked + : PerformanceMode::Handheld; } PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index 06f0f8edd..0bff97a37 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -12,7 +12,8 @@ namespace Service::APM { class ISession final : public ServiceFramework<ISession> { public: - ISession(Controller& controller) : ServiceFramework("ISession"), controller(controller) { + explicit ISession(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "ISession"}, controller{controller_} { static const FunctionInfo functions[] = { {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, @@ -27,8 +28,7 @@ private: const auto mode = rp.PopEnum<PerformanceMode>(); const auto config = rp.PopEnum<PerformanceConfiguration>(); - LOG_DEBUG(Service_APM, "called mode={} config={}", static_cast<u32>(mode), - static_cast<u32>(config)); + LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config); controller.SetPerformanceConfiguration(mode, config); @@ -40,7 +40,7 @@ private: IPC::RequestParser rp{ctx}; const auto mode = rp.PopEnum<PerformanceMode>(); - LOG_DEBUG(Service_APM, "called mode={}", static_cast<u32>(mode)); + LOG_DEBUG(Service_APM, "called mode={}", mode); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -50,12 +50,13 @@ private: Controller& controller; }; -APM::APM(std::shared_ptr<Module> apm, Controller& controller, const char* name) - : ServiceFramework(name), apm(std::move(apm)), controller(controller) { +APM::APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_, + const char* name) + : ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} { static const FunctionInfo functions[] = { {0, &APM::OpenSession, "OpenSession"}, {1, &APM::GetPerformanceMode, "GetPerformanceMode"}, - {6, nullptr, "IsCpuOverclockEnabled"}, + {6, &APM::IsCpuOverclockEnabled, "IsCpuOverclockEnabled"}, }; RegisterHandlers(functions); } @@ -67,7 +68,7 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISession>(controller); + rb.PushIpcInterface<ISession>(system, controller); } void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { @@ -77,7 +78,16 @@ void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { rb.PushEnum(controller.GetCurrentPerformanceMode()); } -APM_Sys::APM_Sys(Controller& controller) : ServiceFramework{"apm:sys"}, controller(controller) { +void APM::IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_APM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); +} + +APM_Sys::APM_Sys(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "apm:sys"}, controller{controller_} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestPerformanceMode"}, @@ -101,14 +111,14 @@ void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISession>(controller); + rb.PushIpcInterface<ISession>(system, controller); } void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto mode = rp.PopEnum<CpuBoostMode>(); - LOG_DEBUG(Service_APM, "called, mode={:08X}", static_cast<u32>(mode)); + LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); controller.SetFromCpuBoostMode(mode); diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h index de1b89437..063ad5308 100644 --- a/src/core/hle/service/apm/interface.h +++ b/src/core/hle/service/apm/interface.h @@ -13,12 +13,14 @@ class Module; class APM final : public ServiceFramework<APM> { public: - explicit APM(std::shared_ptr<Module> apm, Controller& controller, const char* name); + explicit APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_, + const char* name); ~APM() override; private: void OpenSession(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); + void IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx); std::shared_ptr<Module> apm; Controller& controller; @@ -26,7 +28,7 @@ private: class APM_Sys final : public ServiceFramework<APM_Sys> { public: - explicit APM_Sys(Controller& controller); + explicit APM_Sys(Core::System& system_, Controller& controller); ~APM_Sys() override; void SetCpuBoostMode(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 6ddb547fb..84890be72 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -8,7 +8,7 @@ namespace Service::Audio { -AudCtl::AudCtl() : ServiceFramework{"audctl"} { +AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetTargetVolume"}, diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h index c7fafc02e..15f6c77a0 100644 --- a/src/core/hle/service/audio/audctl.h +++ b/src/core/hle/service/audio/audctl.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudCtl final : public ServiceFramework<AudCtl> { public: - explicit AudCtl(); + explicit AudCtl(Core::System& system_); ~AudCtl() override; private: diff --git a/src/core/hle/service/audio/auddbg.cpp b/src/core/hle/service/audio/auddbg.cpp index 8fff3e4b4..6264e4bda 100644 --- a/src/core/hle/service/audio/auddbg.cpp +++ b/src/core/hle/service/audio/auddbg.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudDbg::AudDbg(const char* name) : ServiceFramework{name} { +AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendForDebug"}, diff --git a/src/core/hle/service/audio/auddbg.h b/src/core/hle/service/audio/auddbg.h index 6689f4759..d1653eedd 100644 --- a/src/core/hle/service/audio/auddbg.h +++ b/src/core/hle/service/audio/auddbg.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudDbg final : public ServiceFramework<AudDbg> { public: - explicit AudDbg(const char* name); + explicit AudDbg(Core::System& system_, const char* name); ~AudDbg() override; }; diff --git a/src/core/hle/service/audio/audin_a.cpp b/src/core/hle/service/audio/audin_a.cpp index ddd12f35e..79c3aa920 100644 --- a/src/core/hle/service/audio/audin_a.cpp +++ b/src/core/hle/service/audio/audin_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudInA::AudInA() : ServiceFramework{"audin:a"} { +AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioIns"}, diff --git a/src/core/hle/service/audio/audin_a.h b/src/core/hle/service/audio/audin_a.h index e7623bc29..15120a4b6 100644 --- a/src/core/hle/service/audio/audin_a.h +++ b/src/core/hle/service/audio/audin_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudInA final : public ServiceFramework<AudInA> { public: - explicit AudInA(); + explicit AudInA(Core::System& system_); ~AudInA() override; }; diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 3e2299426..26a6deddf 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -11,7 +11,7 @@ namespace Service::Audio { class IAudioIn final : public ServiceFramework<IAudioIn> { public: - IAudioIn() : ServiceFramework("IAudioIn") { + explicit IAudioIn(Core::System& system_) : ServiceFramework{system_, "IAudioIn"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAudioInState"}, @@ -36,7 +36,7 @@ public: } }; -AudInU::AudInU() : ServiceFramework("audin:u") { +AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudInU::ListAudioIns, "ListAudioIns"}, @@ -96,7 +96,7 @@ void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushRaw<AudInOutParams>(params); - rb.PushIpcInterface<IAudioIn>(); + rb.PushIpcInterface<IAudioIn>(system); } void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index a599f4a64..0d75ae5ac 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Audio { class AudInU final : public ServiceFramework<AudInU> { public: - explicit AudInU(); + explicit AudInU(Core::System& system_); ~AudInU() override; private: diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 1781bec83..b3f24f9bb 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -20,22 +20,22 @@ namespace Service::Audio { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - std::make_shared<AudCtl>()->InstallAsService(service_manager); - std::make_shared<AudOutA>()->InstallAsService(service_manager); + std::make_shared<AudCtl>(system)->InstallAsService(service_manager); + std::make_shared<AudOutA>(system)->InstallAsService(service_manager); std::make_shared<AudOutU>(system)->InstallAsService(service_manager); - std::make_shared<AudInA>()->InstallAsService(service_manager); - std::make_shared<AudInU>()->InstallAsService(service_manager); - std::make_shared<AudRecA>()->InstallAsService(service_manager); - std::make_shared<AudRecU>()->InstallAsService(service_manager); - std::make_shared<AudRenA>()->InstallAsService(service_manager); + std::make_shared<AudInA>(system)->InstallAsService(service_manager); + std::make_shared<AudInU>(system)->InstallAsService(service_manager); + std::make_shared<AudRecA>(system)->InstallAsService(service_manager); + std::make_shared<AudRecU>(system)->InstallAsService(service_manager); + std::make_shared<AudRenA>(system)->InstallAsService(service_manager); std::make_shared<AudRenU>(system)->InstallAsService(service_manager); - std::make_shared<CodecCtl>()->InstallAsService(service_manager); - std::make_shared<HwOpus>()->InstallAsService(service_manager); + std::make_shared<CodecCtl>(system)->InstallAsService(service_manager); + std::make_shared<HwOpus>(system)->InstallAsService(service_manager); - std::make_shared<AudDbg>("audin:d")->InstallAsService(service_manager); - std::make_shared<AudDbg>("audout:d")->InstallAsService(service_manager); - std::make_shared<AudDbg>("audrec:d")->InstallAsService(service_manager); - std::make_shared<AudDbg>("audren:d")->InstallAsService(service_manager); + std::make_shared<AudDbg>(system, "audin:d")->InstallAsService(service_manager); + std::make_shared<AudDbg>(system, "audout:d")->InstallAsService(service_manager); + std::make_shared<AudDbg>(system, "audrec:d")->InstallAsService(service_manager); + std::make_shared<AudDbg>(system, "audren:d")->InstallAsService(service_manager); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_a.cpp b/src/core/hle/service/audio/audout_a.cpp index 85febbca3..19825fd5d 100644 --- a/src/core/hle/service/audio/audout_a.cpp +++ b/src/core/hle/service/audio/audout_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudOutA::AudOutA() : ServiceFramework{"audout:a"} { +AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioOuts"}, diff --git a/src/core/hle/service/audio/audout_a.h b/src/core/hle/service/audio/audout_a.h index d65b66e8e..2043dfb77 100644 --- a/src/core/hle/service/audio/audout_a.h +++ b/src/core/hle/service/audio/audout_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudOutA final : public ServiceFramework<AudOutA> { public: - explicit AudOutA(); + explicit AudOutA(Core::System& system_); ~AudOutA() override; }; diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 9b4910e53..5ed9cb20e 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -14,9 +14,10 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/audio/audout_u.h" #include "core/hle/service/audio/errors.h" #include "core/memory.h" @@ -29,7 +30,7 @@ constexpr int DefaultSampleRate{48000}; struct AudoutParams { s32_le sample_rate; u16_le channel_count; - INSERT_PADDING_BYTES(2); + INSERT_PADDING_BYTES_NOINIT(2); }; static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); @@ -40,11 +41,11 @@ enum class AudioState : u32 { class IAudioOut final : public ServiceFramework<IAudioOut> { public: - IAudioOut(Core::System& system, AudoutParams audio_params, AudioCore::AudioOut& audio_core, - std::string&& device_name, std::string&& unique_name) - : ServiceFramework("IAudioOut"), audio_core(audio_core), - device_name(std::move(device_name)), - audio_params(audio_params), main_memory{system.Memory()} { + IAudioOut(Core::System& system_, AudoutParams audio_params_, AudioCore::AudioOut& audio_core_, + std::string&& device_name_, std::string&& unique_name) + : ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_}, + device_name{std::move(device_name_)}, audio_params{audio_params_}, main_memory{ + system.Memory()} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, @@ -58,7 +59,7 @@ public: {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, nullptr, "GetAudioOutPlayedSampleCount"}, - {11, nullptr, "FlushAudioOutBuffers"}, + {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"}, {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"}, }; @@ -66,12 +67,14 @@ public: RegisterHandlers(functions); // This is the event handle used to check if the audio buffer was released - buffer_event = - Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased"); + buffer_event = Kernel::KEvent::Create(system.Kernel(), "IAudioOutBufferReleased"); + buffer_event->Initialize(); stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, - audio_params.channel_count, std::move(unique_name), - [this] { buffer_event.writable->Signal(); }); + audio_params.channel_count, std::move(unique_name), [this] { + const auto guard = LockService(); + buffer_event->GetWritableEvent()->Signal(); + }); } private: @@ -123,7 +126,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(buffer_event.readable); + rb.PushCopyObjects(buffer_event->GetReadableEvent()); } void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { @@ -183,6 +186,14 @@ private: rb.Push(static_cast<u32>(stream->GetQueueSize())); } + void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(stream->Flush()); + } + void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const float volume = rp.Pop<float>(); @@ -209,11 +220,11 @@ private: [[maybe_unused]] AudoutParams audio_params{}; /// This is the event handle used to check if the audio buffer was released - Kernel::EventPair buffer_event; + std::shared_ptr<Kernel::KEvent> buffer_event; Core::Memory::Memory& main_memory; }; -AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} { +AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index c9f532ccd..f7ae2f2bf 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -34,8 +34,6 @@ private: std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; std::unique_ptr<AudioCore::AudioOut> audio_core; - - Core::System& system; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_a.cpp b/src/core/hle/service/audio/audrec_a.cpp index ce1bfb48d..c5ab7cad4 100644 --- a/src/core/hle/service/audio/audrec_a.cpp +++ b/src/core/hle/service/audio/audrec_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudRecA::AudRecA() : ServiceFramework{"audrec:a"} { +AudRecA::AudRecA(Core::System& system_) : ServiceFramework{system_, "audrec:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendFinalOutputRecorders"}, diff --git a/src/core/hle/service/audio/audrec_a.h b/src/core/hle/service/audio/audrec_a.h index 384d24c69..2cce90b1d 100644 --- a/src/core/hle/service/audio/audrec_a.h +++ b/src/core/hle/service/audio/audrec_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudRecA final : public ServiceFramework<AudRecA> { public: - explicit AudRecA(); + explicit AudRecA(Core::System& system_); ~AudRecA() override; }; diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp index 1a5aed9ed..eb5c63c62 100644 --- a/src/core/hle/service/audio/audrec_u.cpp +++ b/src/core/hle/service/audio/audrec_u.cpp @@ -8,7 +8,8 @@ namespace Service::Audio { class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> { public: - IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") { + explicit IFinalOutputRecorder(Core::System& system_) + : ServiceFramework{system_, "IFinalOutputRecorder"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetFinalOutputRecorderState"}, @@ -29,7 +30,7 @@ public: } }; -AudRecU::AudRecU() : ServiceFramework("audrec:u") { +AudRecU::AudRecU(Core::System& system_) : ServiceFramework{system_, "audrec:u"} { static const FunctionInfo functions[] = { {0, nullptr, "OpenFinalOutputRecorder"}, }; diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h index ca3d638e8..f79d49e5c 100644 --- a/src/core/hle/service/audio/audrec_u.h +++ b/src/core/hle/service/audio/audrec_u.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Audio { class AudRecU final : public ServiceFramework<AudRecU> { public: - explicit AudRecU(); + explicit AudRecU(Core::System& system_); ~AudRecU() override; }; diff --git a/src/core/hle/service/audio/audren_a.cpp b/src/core/hle/service/audio/audren_a.cpp index edb66d985..5e9f866f0 100644 --- a/src/core/hle/service/audio/audren_a.cpp +++ b/src/core/hle/service/audio/audren_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudRenA::AudRenA() : ServiceFramework{"audren:a"} { +AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioRenderers"}, diff --git a/src/core/hle/service/audio/audren_a.h b/src/core/hle/service/audio/audren_a.h index 81fef0ffe..5d0a626ad 100644 --- a/src/core/hle/service/audio/audren_a.h +++ b/src/core/hle/service/audio/audren_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudRenA final : public ServiceFramework<AudRenA> { public: - explicit AudRenA(); + explicit AudRenA(Core::System& system_); ~AudRenA() override; }; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index d8359abaa..b2b2ffc5a 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -16,9 +16,10 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/errors.h" @@ -26,9 +27,9 @@ namespace Service::Audio { class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { public: - explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params, + explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params, const std::size_t instance_number) - : ServiceFramework("IAudioRenderer") { + : ServiceFramework{system, "IAudioRenderer"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, @@ -47,18 +48,18 @@ public: // clang-format on RegisterHandlers(functions); - system_event = - Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent"); - renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), system.Memory(), - audren_params, system_event.writable, - instance_number); + system_event = Kernel::KEvent::Create(system.Kernel(), "IAudioRenderer:SystemEvent"); + system_event->Initialize(); + renderer = std::make_unique<AudioCore::AudioRenderer>( + system.CoreTiming(), system.Memory(), audren_params, + [this]() { + const auto guard = LockService(); + system_event->GetWritableEvent()->Signal(); + }, + instance_number); } private: - void UpdateAudioCallback() { - system_event.writable->Signal(); - } - void GetSampleRate(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); @@ -94,14 +95,15 @@ private: void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); - auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer()); + std::vector<u8> output_params(ctx.GetWriteBufferSize()); + auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); - if (result.Succeeded()) { - ctx.WriteBuffer(result.Unwrap()); + if (result.IsSuccess()) { + ctx.WriteBuffer(output_params); } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); } void Start(Kernel::HLERequestContext& ctx) { @@ -125,7 +127,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(system_event.readable); + rb.PushCopyObjects(system_event->GetReadableEvent()); } void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { @@ -159,15 +161,15 @@ private: rb.Push(ERR_NOT_SUPPORTED); } - Kernel::EventPair system_event; + std::shared_ptr<Kernel::KEvent> system_event; std::unique_ptr<AudioCore::AudioRenderer> renderer; u32 rendering_time_limit_percent = 100; }; class IAudioDevice final : public ServiceFramework<IAudioDevice> { public: - explicit IAudioDevice(Core::System& system, u32_le revision_num) - : ServiceFramework("IAudioDevice"), revision{revision_num} { + explicit IAudioDevice(Core::System& system_, u32_le revision_num) + : ServiceFramework{system_, "IAudioDevice"}, revision{revision_num} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, @@ -186,17 +188,19 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - buffer_event = - Kernel::WritableEvent::CreateEventPair(kernel, "IAudioOutBufferReleasedEvent"); + buffer_event = Kernel::KEvent::Create(kernel, "IAudioOutBufferReleasedEvent"); + buffer_event->Initialize(); // Should be similar to audio_output_device_switch_event - audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IAudioDevice:AudioInputDeviceSwitchedEvent"); + audio_input_device_switch_event = + Kernel::KEvent::Create(kernel, "IAudioDevice:AudioInputDeviceSwitchedEvent"); + audio_input_device_switch_event->Initialize(); // Should only be signalled when an audio output device has been changed, example: speaker // to headset - audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IAudioDevice:AudioOutputDeviceSwitchedEvent"); + audio_output_device_switch_event = + Kernel::KEvent::Create(kernel, "IAudioDevice:AudioOutputDeviceSwitchedEvent"); + audio_output_device_switch_event->Initialize(); } private: @@ -285,11 +289,11 @@ private: void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); - buffer_event.writable->Signal(); + buffer_event->GetWritableEvent()->Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(buffer_event.readable); + rb.PushCopyObjects(buffer_event->GetReadableEvent()); } void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { @@ -306,7 +310,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(audio_input_device_switch_event.readable); + rb.PushCopyObjects(audio_input_device_switch_event->GetReadableEvent()); } void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { @@ -314,17 +318,17 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(audio_output_device_switch_event.readable); + rb.PushCopyObjects(audio_output_device_switch_event->GetReadableEvent()); } u32_le revision = 0; - Kernel::EventPair buffer_event; - Kernel::EventPair audio_input_device_switch_event; - Kernel::EventPair audio_output_device_switch_event; + std::shared_ptr<Kernel::KEvent> buffer_event; + std::shared_ptr<Kernel::KEvent> audio_input_device_switch_event; + std::shared_ptr<Kernel::KEvent> audio_output_device_switch_event; }; // namespace Audio -AudRenU::AudRenU(Core::System& system_) : ServiceFramework("audren:u"), system{system_} { +AudRenU::AudRenU(Core::System& system_) : ServiceFramework{system_, "audren:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, @@ -346,7 +350,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { OpenAudioRendererImpl(ctx); } -static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { +static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) { // +1 represents the final mix. return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + 1; @@ -375,7 +379,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { constexpr u64 upsampler_manager_size = 0x48; // Calculates the part of the size that relates to mix buffers. - const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) { // As of 8.0.0 this is the maximum on voice channels. constexpr u64 max_voice_channels = 6; @@ -397,7 +401,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the portion of the size related to the mix data (and the sorting thereof). - const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) { // The size of the mixing info data structure. constexpr u64 mix_info_size = 0x940; @@ -447,7 +451,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to voice channel info. - const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) { constexpr u64 voice_info_size = 0x220; constexpr u64 voice_resource_size = 0xD0; @@ -461,7 +465,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to memory pools. - const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); const u64 memory_pool_info_size = 0x20; return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); @@ -469,7 +473,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { // Calculates the part of the size related to the splitter context. const auto calculate_splitter_context_size = - [](const AudioCore::AudioRendererParameter& params) -> u64 { + [](const AudioCommon::AudioRendererParameter& params) -> u64 { if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { return 0; } @@ -488,27 +492,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to the upsampler info. - const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) { - constexpr u64 upsampler_info_size = 0x280; - // Yes, using the buffer size over info alignment size is intentional here. - return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count), - buffer_alignment_size); - }; + const auto calculate_upsampler_info_size = + [](const AudioCommon::AudioRendererParameter& params) { + constexpr u64 upsampler_info_size = 0x280; + // Yes, using the buffer size over info alignment size is intentional here. + return Common::AlignUp(upsampler_info_size * + (u64{params.submix_count} + params.sink_count), + buffer_alignment_size); + }; // Calculates the part of the size related to effect info. - const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) { constexpr u64 effect_info_size = 0x2B0; return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); }; // Calculates the part of the size related to audio sink info. - const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 sink_info_size = 0x170; return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); }; // Calculates the part of the size related to voice state info. - const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) { const u64 voice_state_size = 0x100; const u64 additional_size = buffer_alignment_size - 1; return Common::AlignUp(voice_state_size * params.voice_count + additional_size, @@ -516,7 +522,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to performance statistics. - const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) { + const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) { // Extra size value appended to the end of the calculation. constexpr u64 appended = 128; @@ -543,79 +549,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size that relates to the audio command buffer. - const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) { - constexpr u64 alignment = (buffer_alignment_size - 1) * 2; + const auto calculate_command_buffer_size = + [](const AudioCommon::AudioRendererParameter& params) { + constexpr u64 alignment = (buffer_alignment_size - 1) * 2; - if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { - constexpr u64 command_buffer_size = 0x18000; + if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { + constexpr u64 command_buffer_size = 0x18000; - return command_buffer_size + alignment; - } + return command_buffer_size + alignment; + } - // When the variadic command buffer is supported, this means - // the command generator for the audio renderer can issue commands - // that are (as one would expect), variable in size. So what we need to do - // is determine the maximum possible size for a few command data structures - // then multiply them by the amount of present commands indicated by the given - // respective audio parameters. + // When the variadic command buffer is supported, this means + // the command generator for the audio renderer can issue commands + // that are (as one would expect), variable in size. So what we need to do + // is determine the maximum possible size for a few command data structures + // then multiply them by the amount of present commands indicated by the given + // respective audio parameters. - constexpr u64 max_biquad_filters = 2; - constexpr u64 max_mix_buffers = 24; + constexpr u64 max_biquad_filters = 2; + constexpr u64 max_mix_buffers = 24; - constexpr u64 biquad_filter_command_size = 0x2C; + constexpr u64 biquad_filter_command_size = 0x2C; - constexpr u64 depop_mix_command_size = 0x24; - constexpr u64 depop_setup_command_size = 0x50; + constexpr u64 depop_mix_command_size = 0x24; + constexpr u64 depop_setup_command_size = 0x50; - constexpr u64 effect_command_max_size = 0x540; + constexpr u64 effect_command_max_size = 0x540; - constexpr u64 mix_command_size = 0x1C; - constexpr u64 mix_ramp_command_size = 0x24; - constexpr u64 mix_ramp_grouped_command_size = 0x13C; + constexpr u64 mix_command_size = 0x1C; + constexpr u64 mix_ramp_command_size = 0x24; + constexpr u64 mix_ramp_grouped_command_size = 0x13C; - constexpr u64 perf_command_size = 0x28; + constexpr u64 perf_command_size = 0x28; - constexpr u64 sink_command_size = 0x130; + constexpr u64 sink_command_size = 0x130; - constexpr u64 submix_command_max_size = - depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; + constexpr u64 submix_command_max_size = + depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; - constexpr u64 volume_command_size = 0x1C; - constexpr u64 volume_ramp_command_size = 0x20; + constexpr u64 volume_command_size = 0x1C; + constexpr u64 volume_ramp_command_size = 0x20; - constexpr u64 voice_biquad_filter_command_size = - biquad_filter_command_size * max_biquad_filters; - constexpr u64 voice_data_command_size = 0x9C; - const u64 voice_command_max_size = - (params.splitter_count * depop_setup_command_size) + - (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size + - mix_ramp_grouped_command_size); + constexpr u64 voice_biquad_filter_command_size = + biquad_filter_command_size * max_biquad_filters; + constexpr u64 voice_data_command_size = 0x9C; + const u64 voice_command_max_size = + (params.splitter_count * depop_setup_command_size) + + (voice_data_command_size + voice_biquad_filter_command_size + + volume_ramp_command_size + mix_ramp_grouped_command_size); - // Now calculate the individual elements that comprise the size and add them together. - const u64 effect_commands_size = params.effect_count * effect_command_max_size; + // Now calculate the individual elements that comprise the size and add them together. + const u64 effect_commands_size = params.effect_count * effect_command_max_size; - const u64 final_mix_commands_size = - depop_mix_command_size + volume_command_size * max_mix_buffers; + const u64 final_mix_commands_size = + depop_mix_command_size + volume_command_size * max_mix_buffers; - const u64 perf_commands_size = - perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); + const u64 perf_commands_size = + perf_command_size * + (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); - const u64 sink_commands_size = params.sink_count * sink_command_size; + const u64 sink_commands_size = params.sink_count * sink_command_size; - const u64 splitter_commands_size = - params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; + const u64 splitter_commands_size = + params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; - const u64 submix_commands_size = params.submix_count * submix_command_max_size; + const u64 submix_commands_size = params.submix_count * submix_command_max_size; - const u64 voice_commands_size = params.voice_count * voice_command_max_size; + const u64 voice_commands_size = params.voice_count * voice_command_max_size; - return effect_commands_size + final_mix_commands_size + perf_commands_size + - sink_commands_size + splitter_commands_size + submix_commands_size + - voice_commands_size + alignment; - }; + return effect_commands_size + final_mix_commands_size + perf_commands_size + + sink_commands_size + splitter_commands_size + submix_commands_size + + voice_commands_size + alignment; + }; IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); + const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); u64 size = 0; size += calculate_mix_buffer_sizes(params); @@ -681,7 +689,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); + const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 4e0ccc792..d693dc406 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -31,7 +31,6 @@ private: void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); std::size_t audren_instance_count = 0; - Core::System& system; }; // Describes a particular audio feature that may be supported in a particular revision. diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp index c6864146d..94afec1b6 100644 --- a/src/core/hle/service/audio/codecctl.cpp +++ b/src/core/hle/service/audio/codecctl.cpp @@ -2,14 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/codecctl.h" namespace Service::Audio { -CodecCtl::CodecCtl() : ServiceFramework("codecctl") { +CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} { static const FunctionInfo functions[] = { {0, nullptr, "InitializeCodecController"}, {1, nullptr, "FinalizeCodecController"}, diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h index 2fe75b6e2..58e53259e 100644 --- a/src/core/hle/service/audio/codecctl.h +++ b/src/core/hle/service/audio/codecctl.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Audio { class CodecCtl final : public ServiceFramework<CodecCtl> { public: - explicit CodecCtl(); + explicit CodecCtl(Core::System& system_); ~CodecCtl() override; }; diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index f1d81602c..ea3414fd2 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -160,8 +160,9 @@ private: class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { public: - explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state) - : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { + explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state) + : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ + std::move(decoder_state)} { // clang-format off static const FunctionInfo functions[] = { {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, @@ -287,10 +288,10 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IHardwareOpusDecoderManager>( - OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); } -HwOpus::HwOpus() : ServiceFramework("hwopus") { +HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 602ede8ba..4f921f18e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class HwOpus final : public ServiceFramework<HwOpus> { public: - explicit HwOpus(); + explicit HwOpus(Core::System& system_); ~HwOpus() override; private: diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp index def3410cc..92d25dbe4 100644 --- a/src/core/hle/service/bcat/backend/backend.cpp +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -5,6 +5,9 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/lock.h" #include "core/hle/service/bcat/backend/backend.h" @@ -12,12 +15,13 @@ namespace Service::BCAT { ProgressServiceBackend::ProgressServiceBackend(Kernel::KernelCore& kernel, std::string_view event_name) { - event = Kernel::WritableEvent::CreateEventPair( - kernel, std::string("ProgressServiceBackend:UpdateEvent:").append(event_name)); + event = Kernel::KEvent::Create(kernel, + "ProgressServiceBackend:UpdateEvent:" + std::string(event_name)); + event->Initialize(); } -std::shared_ptr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const { - return event.readable; +std::shared_ptr<Kernel::KReadableEvent> ProgressServiceBackend::GetEvent() const { + return event->GetReadableEvent(); } DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() { @@ -84,10 +88,10 @@ void ProgressServiceBackend::FinishDownload(ResultCode result) { void ProgressServiceBackend::SignalUpdate() const { if (need_hle_lock) { - std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); - event.writable->Signal(); + std::lock_guard lock(HLE::g_hle_lock); + event->GetWritableEvent()->Signal(); } else { - event.writable->Signal(); + event->GetWritableEvent()->Signal(); } } diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h index 48bbbe66f..db585b069 100644 --- a/src/core/hle/service/bcat/backend/backend.h +++ b/src/core/hle/service/bcat/backend/backend.h @@ -11,8 +11,6 @@ #include "common/common_types.h" #include "core/file_sys/vfs_types.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/result.h" namespace Core { @@ -21,7 +19,9 @@ class System; namespace Kernel { class KernelCore; -} +class KEvent; +class KReadableEvent; +} // namespace Kernel namespace Service::BCAT { @@ -98,13 +98,13 @@ public: private: explicit ProgressServiceBackend(Kernel::KernelCore& kernel, std::string_view event_name); - std::shared_ptr<Kernel::ReadableEvent> GetEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetEvent() const; DeliveryCacheProgressImpl& GetImpl(); void SignalUpdate() const; DeliveryCacheProgressImpl impl{}; - Kernel::EventPair event; + std::shared_ptr<Kernel::KEvent> event; bool need_hle_lock = false; }; diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index ca021a99f..e43f3f47f 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -196,7 +196,9 @@ private: const std::string& content_type_name) { if (client == nullptr) { client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT); - client->set_timeout_sec(timeout_seconds); + client->set_connection_timeout(timeout_seconds); + client->set_read_timeout(timeout_seconds); + client->set_write_timeout(timeout_seconds); } httplib::Headers headers{ @@ -255,7 +257,7 @@ private: return out; } - std::unique_ptr<httplib::Client> client; + std::unique_ptr<httplib::SSLClient> client; std::string path; u64 title_id; u64 build_id; @@ -443,13 +445,25 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, std::map<std::string, EventStatus>& games) { httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)}; - client.set_timeout_sec(static_cast<int>(TIMEOUT_SECONDS)); + client.set_connection_timeout(static_cast<int>(TIMEOUT_SECONDS)); + client.set_read_timeout(static_cast<int>(TIMEOUT_SECONDS)); + client.set_write_timeout(static_cast<int>(TIMEOUT_SECONDS)); httplib::Headers headers{ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, }; + if (!client.is_valid()) { + LOG_ERROR(Service_BCAT, "Client is invalid, going offline!"); + return StatusResult::Offline; + } + + if (!client.is_socket_open()) { + LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!"); + return StatusResult::Offline; + } + const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); if (response == nullptr) return StatusResult::Offline; @@ -469,7 +483,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, global = json["global"].get<std::string>(); if (json["games"].is_array()) { - for (const auto object : json["games"]) { + for (const auto& object : json["games"]) { if (object.is_object() && object.find("name") != object.end()) { EventStatus detail{}; if (object["header"].is_string()) { diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index db0e06ca1..503109fdd 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -8,11 +8,12 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/bcat/bcat.h" #include "core/hle/service/bcat/module.h" @@ -87,9 +88,11 @@ struct DeliveryCacheDirectoryEntry { class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> { public: - IDeliveryCacheProgressService(std::shared_ptr<Kernel::ReadableEvent> event, - const DeliveryCacheProgressImpl& impl) - : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) { + explicit IDeliveryCacheProgressService(Core::System& system_, + std::shared_ptr<Kernel::KReadableEvent> event_, + const DeliveryCacheProgressImpl& impl_) + : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{std::move(event_)}, + impl{impl_} { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, @@ -118,14 +121,14 @@ private: rb.Push(RESULT_SUCCESS); } - std::shared_ptr<Kernel::ReadableEvent> event; + std::shared_ptr<Kernel::KReadableEvent> event; const DeliveryCacheProgressImpl& impl; }; class IBcatService final : public ServiceFramework<IBcatService> { public: explicit IBcatService(Core::System& system_, Backend& backend_) - : ServiceFramework("IBcatService"), system{system_}, backend{backend_}, + : ServiceFramework{system_, "IBcatService"}, backend{backend_}, progress{{ ProgressServiceBackend{system_.Kernel(), "Normal"}, ProgressServiceBackend{system_.Kernel(), "Directory"}, @@ -170,7 +173,7 @@ private: std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) { auto& backend{progress.at(static_cast<std::size_t>(type))}; - return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(), + return std::make_shared<IDeliveryCacheProgressService>(system, backend.GetEvent(), backend.GetImpl()); } @@ -260,7 +263,6 @@ private: rb.Push(RESULT_SUCCESS); } - Core::System& system; Backend& backend; std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress; @@ -276,8 +278,8 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> { public: - IDeliveryCacheFileService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) { + explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheFileService::Open, "Open"}, @@ -393,8 +395,8 @@ private: class IDeliveryCacheDirectoryService final : public ServiceFramework<IDeliveryCacheDirectoryService> { public: - IDeliveryCacheDirectoryService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) { + explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheDirectoryService::Open, "Open"}, @@ -491,8 +493,8 @@ private: class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> { public: - IDeliveryCacheStorageService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) { + explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, @@ -517,7 +519,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDeliveryCacheFileService>(root); + rb.PushIpcInterface<IDeliveryCacheFileService>(system, root); } void CreateDirectoryService(Kernel::HLERequestContext& ctx) { @@ -525,7 +527,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root); + rb.PushIpcInterface<IDeliveryCacheDirectoryService>(system, root); } void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { @@ -550,10 +552,10 @@ private: void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_BCAT, "called"); + const auto title_id = system.CurrentProcess()->GetTitleID(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDeliveryCacheStorageService>( - fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID())); + rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id)); } void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( @@ -565,7 +567,7 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id)); + rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id)); } std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system, @@ -581,10 +583,9 @@ std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, FileSystem::FileSystemController& fsc_, const char* name) - : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)}, + : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)}, backend{CreateBackendFromSettings(system_, - [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })}, - system{system_} {} + [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index e4ba23ba0..738731c06 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -37,9 +37,6 @@ public: std::shared_ptr<Module> module; std::unique_ptr<Backend> backend; - - private: - Core::System& system; }; }; diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp index fac6b2f9c..e4630320e 100644 --- a/src/core/hle/service/bpc/bpc.cpp +++ b/src/core/hle/service/bpc/bpc.cpp @@ -12,7 +12,7 @@ namespace Service::BPC { class BPC final : public ServiceFramework<BPC> { public: - explicit BPC() : ServiceFramework{"bpc"} { + explicit BPC(Core::System& system_) : ServiceFramework{system_, "bpc"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ShutdownSystem"}, @@ -40,7 +40,7 @@ public: class BPC_R final : public ServiceFramework<BPC_R> { public: - explicit BPC_R() : ServiceFramework{"bpc:r"} { + explicit BPC_R(Core::System& system_) : ServiceFramework{system_, "bpc:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetRtcTime"}, @@ -55,9 +55,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<BPC>()->InstallAsService(sm); - std::make_shared<BPC_R>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<BPC>(system)->InstallAsService(sm); + std::make_shared<BPC_R>(system)->InstallAsService(sm); } } // namespace Service::BPC diff --git a/src/core/hle/service/bpc/bpc.h b/src/core/hle/service/bpc/bpc.h index eaa37be8d..6ec25aa9b 100644 --- a/src/core/hle/service/bpc/bpc.h +++ b/src/core/hle/service/bpc/bpc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::BPC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::BPC diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index f311afa2f..17a2ac899 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -3,11 +3,12 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -16,7 +17,7 @@ namespace Service::BtDrv { class Bt final : public ServiceFramework<Bt> { public: - explicit Bt(Core::System& system) : ServiceFramework{"bt"} { + explicit Bt(Core::System& system_) : ServiceFramework{system_, "bt"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LeClientReadCharacteristic"}, @@ -34,7 +35,8 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - register_event = Kernel::WritableEvent::CreateEventPair(kernel, "BT:RegisterEvent"); + register_event = Kernel::KEvent::Create(kernel, "BT:RegisterEvent"); + register_event->Initialize(); } private: @@ -43,15 +45,15 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(register_event.readable); + rb.PushCopyObjects(register_event->GetReadableEvent()); } - Kernel::EventPair register_event; + std::shared_ptr<Kernel::KEvent> register_event; }; class BtDrv final : public ServiceFramework<BtDrv> { public: - explicit BtDrv() : ServiceFramework{"btdrv"} { + explicit BtDrv(Core::System& system_) : ServiceFramework{system_, "btdrv"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "InitializeBluetoothDriver"}, @@ -165,7 +167,7 @@ public: }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared<BtDrv>()->InstallAsService(sm); + std::make_shared<BtDrv>(system)->InstallAsService(sm); std::make_shared<Bt>(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index 0d251c6d0..9cf2ee92a 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -5,11 +5,12 @@ #include <memory> #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/btm/btm.h" #include "core/hle/service/service.h" @@ -17,7 +18,7 @@ namespace Service::BTM { class IBtmUserCore final : public ServiceFramework<IBtmUserCore> { public: - explicit IBtmUserCore(Core::System& system) : ServiceFramework{"IBtmUserCore"} { + explicit IBtmUserCore(Core::System& system_) : ServiceFramework{system_, "IBtmUserCore"} { // clang-format off static const FunctionInfo functions[] = { {0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"}, @@ -57,12 +58,14 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - scan_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ScanEvent"); - connection_event = - Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConnectionEvent"); - service_discovery = - Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:Discovery"); - config_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConfigEvent"); + scan_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ScanEvent"); + scan_event->Initialize(); + connection_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ConnectionEvent"); + connection_event->Initialize(); + service_discovery = Kernel::KEvent::Create(kernel, "IBtmUserCore:Discovery"); + service_discovery->Initialize(); + config_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ConfigEvent"); + config_event->Initialize(); } private: @@ -71,7 +74,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(scan_event.readable); + rb.PushCopyObjects(scan_event->GetReadableEvent()); } void AcquireBleConnectionEvent(Kernel::HLERequestContext& ctx) { @@ -79,7 +82,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(connection_event.readable); + rb.PushCopyObjects(connection_event->GetReadableEvent()); } void AcquireBleServiceDiscoveryEvent(Kernel::HLERequestContext& ctx) { @@ -87,7 +90,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(service_discovery.readable); + rb.PushCopyObjects(service_discovery->GetReadableEvent()); } void AcquireBleMtuConfigEvent(Kernel::HLERequestContext& ctx) { @@ -95,18 +98,18 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(config_event.readable); + rb.PushCopyObjects(config_event->GetReadableEvent()); } - Kernel::EventPair scan_event; - Kernel::EventPair connection_event; - Kernel::EventPair service_discovery; - Kernel::EventPair config_event; + std::shared_ptr<Kernel::KEvent> scan_event; + std::shared_ptr<Kernel::KEvent> connection_event; + std::shared_ptr<Kernel::KEvent> service_discovery; + std::shared_ptr<Kernel::KEvent> config_event; }; class BTM_USR final : public ServiceFramework<BTM_USR> { public: - explicit BTM_USR(Core::System& system) : ServiceFramework{"btm:u"}, system(system) { + explicit BTM_USR(Core::System& system_) : ServiceFramework{system_, "btm:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &BTM_USR::GetCore, "GetCore"}, @@ -123,13 +126,11 @@ private: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IBtmUserCore>(system); } - - Core::System& system; }; class BTM final : public ServiceFramework<BTM> { public: - explicit BTM() : ServiceFramework{"btm"} { + explicit BTM(Core::System& system_) : ServiceFramework{system_, "btm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, @@ -206,7 +207,7 @@ public: class BTM_DBG final : public ServiceFramework<BTM_DBG> { public: - explicit BTM_DBG() : ServiceFramework{"btm:dbg"} { + explicit BTM_DBG(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AcquireDiscoveryEvent"}, @@ -231,7 +232,7 @@ public: class IBtmSystemCore final : public ServiceFramework<IBtmSystemCore> { public: - explicit IBtmSystemCore() : ServiceFramework{"IBtmSystemCore"} { + explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "StartGamepadPairing"}, @@ -253,7 +254,7 @@ public: class BTM_SYS final : public ServiceFramework<BTM_SYS> { public: - explicit BTM_SYS() : ServiceFramework{"btm:sys"} { + explicit BTM_SYS(Core::System& system_) : ServiceFramework{system_, "btm:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, &BTM_SYS::GetCore, "GetCore"}, @@ -269,14 +270,14 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IBtmSystemCore>(); + rb.PushIpcInterface<IBtmSystemCore>(system); } }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared<BTM>()->InstallAsService(sm); - std::make_shared<BTM_DBG>()->InstallAsService(sm); - std::make_shared<BTM_SYS>()->InstallAsService(sm); + std::make_shared<BTM>(system)->InstallAsService(sm); + std::make_shared<BTM_DBG>(system)->InstallAsService(sm); + std::make_shared<BTM_SYS>(system)->InstallAsService(sm); std::make_shared<BTM_USR>(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index ba5749b84..5b7fe8e9b 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -13,13 +13,13 @@ namespace Service::Capture { -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<CAPS_A>()->InstallAsService(sm); - std::make_shared<CAPS_C>()->InstallAsService(sm); - std::make_shared<CAPS_U>()->InstallAsService(sm); - std::make_shared<CAPS_SC>()->InstallAsService(sm); - std::make_shared<CAPS_SS>()->InstallAsService(sm); - std::make_shared<CAPS_SU>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<CAPS_A>(system)->InstallAsService(sm); + std::make_shared<CAPS_C>(system)->InstallAsService(sm); + std::make_shared<CAPS_U>(system)->InstallAsService(sm); + std::make_shared<CAPS_SC>(system)->InstallAsService(sm); + std::make_shared<CAPS_SS>(system)->InstallAsService(sm); + std::make_shared<CAPS_SU>(system)->InstallAsService(sm); } } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h index b8c67b6e2..3c4290c88 100644 --- a/src/core/hle/service/caps/caps.h +++ b/src/core/hle/service/caps/caps.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -87,6 +91,6 @@ static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30, "ApplicationAlbumFileEntry has incorrect size."); /// Registers all Capture services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index a0a3b2ae3..1fe4f0e14 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -8,7 +8,8 @@ namespace Service::Capture { class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> { public: - explicit IAlbumAccessorSession() : ServiceFramework{"IAlbumAccessorSession"} { + explicit IAlbumAccessorSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumAccessorSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -26,7 +27,7 @@ public: } }; -CAPS_A::CAPS_A() : ServiceFramework("caps:a") { +CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAlbumFileCount"}, diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h index cb93aad5b..389cc6dbe 100644 --- a/src/core/hle/service/caps/caps_a.h +++ b/src/core/hle/service/caps/caps_a.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Capture { class CAPS_A final : public ServiceFramework<CAPS_A> { public: - explicit CAPS_A(); + explicit CAPS_A(Core::System& system_); ~CAPS_A() override; }; diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp index ab17a187e..45c1c9d30 100644 --- a/src/core/hle/service/caps/caps_c.cpp +++ b/src/core/hle/service/caps/caps_c.cpp @@ -2,13 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/caps/caps_c.h" namespace Service::Capture { class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> { public: - explicit IAlbumControlSession() : ServiceFramework{"IAlbumControlSession"} { + explicit IAlbumControlSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumControlSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -42,12 +45,12 @@ public: } }; -CAPS_C::CAPS_C() : ServiceFramework("caps:c") { +CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "CaptureRawImage"}, {2, nullptr, "CaptureRawImageWithTimeout"}, - {33, nullptr, "Unknown33"}, + {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"}, {1001, nullptr, "RequestTakingScreenShot"}, {1002, nullptr, "RequestTakingScreenShotWithTimeout"}, {1011, nullptr, "NotifyTakingScreenShotRefused"}, @@ -72,4 +75,16 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") { CAPS_C::~CAPS_C() = default; +void CAPS_C::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop<u64>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h index a9d028689..c6d1dfdce 100644 --- a/src/core/hle/service/caps/caps_c.h +++ b/src/core/hle/service/caps/caps_c.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,8 +18,11 @@ namespace Service::Capture { class CAPS_C final : public ServiceFramework<CAPS_C> { public: - explicit CAPS_C(); + explicit CAPS_C(Core::System& system_); ~CAPS_C() override; + +private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); }; } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp index 822ee96c8..d91e18e80 100644 --- a/src/core/hle/service/caps/caps_sc.cpp +++ b/src/core/hle/service/caps/caps_sc.cpp @@ -6,7 +6,7 @@ namespace Service::Capture { -CAPS_SC::CAPS_SC() : ServiceFramework("caps:sc") { +CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "CaptureRawImage"}, diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h index ac3e929ca..e79a33ee5 100644 --- a/src/core/hle/service/caps/caps_sc.h +++ b/src/core/hle/service/caps/caps_sc.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Capture { class CAPS_SC final : public ServiceFramework<CAPS_SC> { public: - explicit CAPS_SC(); + explicit CAPS_SC(Core::System& system_); ~CAPS_SC() override; }; diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 24dc716e7..2b5314691 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -6,7 +6,7 @@ namespace Service::Capture { -CAPS_SS::CAPS_SS() : ServiceFramework("caps:ss") { +CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { // clang-format off static const FunctionInfo functions[] = { {201, nullptr, "SaveScreenShot"}, diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h index 450686e4f..1816f7885 100644 --- a/src/core/hle/service/caps/caps_ss.h +++ b/src/core/hle/service/caps/caps_ss.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Capture { class CAPS_SS final : public ServiceFramework<CAPS_SS> { public: - explicit CAPS_SS(); + explicit CAPS_SS(Core::System& system_); ~CAPS_SS() override; }; diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index fffb2ecf9..eae39eb7b 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -8,7 +8,7 @@ namespace Service::Capture { -CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") { +CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { // clang-format off static const FunctionInfo functions[] = { {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, @@ -25,7 +25,12 @@ CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") { CAPS_SU::~CAPS_SU() = default; void CAPS_SU::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Capture, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop<u64>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 62c9603a9..b366fdb13 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Capture { class CAPS_SU final : public ServiceFramework<CAPS_SU> { public: - explicit CAPS_SU(); + explicit CAPS_SU(Core::System& system_); ~CAPS_SU() override; private: diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp index f36d8de2d..842316a2e 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp @@ -12,8 +12,8 @@ namespace Service::Capture { class IAlbumAccessorApplicationSession final : public ServiceFramework<IAlbumAccessorApplicationSession> { public: - explicit IAlbumAccessorApplicationSession() - : ServiceFramework{"IAlbumAccessorApplicationSession"} { + explicit IAlbumAccessorApplicationSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -28,11 +28,10 @@ public: } }; -CAPS_U::CAPS_U() : ServiceFramework("caps:u") { +CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} { // clang-format off static const FunctionInfo functions[] = { - {31, nullptr, "GetShimLibraryVersion"}, - {32, nullptr, "SetShimLibraryVersion"}, + {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"}, {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"}, {103, nullptr, "DeleteAlbumContentsFileForApplication"}, {104, nullptr, "GetAlbumContentsFileSizeForApplication"}, @@ -42,7 +41,7 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") { {130, nullptr, "PrecheckToCreateContentsForApplication"}, {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, - {142, nullptr, "GetAlbumFileList3AaeAruid"}, + {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"}, {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, {60002, nullptr, "OpenAccessorSessionForApplication"}, }; @@ -53,6 +52,18 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") { CAPS_U::~CAPS_U() = default; +void CAPS_U::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop<u64>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx) { // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total @@ -66,17 +77,24 @@ void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& c // TODO: Update this when we implement the album. // Currently we do not have a method of accessing album entries, set this to 0 for now. - constexpr s32 total_entries{0}; + constexpr u32 total_entries_1{}; + constexpr u32 total_entries_2{}; - LOG_WARNING(Service_Capture, - "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " - "end_posix_time={}, applet_resource_user_id={}, total_entries={}", - pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, - total_entries); + LOG_WARNING( + Service_Capture, + "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " + "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}", + pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, + total_entries_1, total_entries_2); - IPC::ResponseBuilder rb{ctx, 3}; + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(total_entries); + rb.Push(total_entries_1); + rb.Push(total_entries_2); +} + +void CAPS_U::GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx) { + GetAlbumContentsFileListForApplication(ctx); } } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h index 689364de4..e7e0d8775 100644 --- a/src/core/hle/service/caps/caps_u.h +++ b/src/core/hle/service/caps/caps_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,11 +18,13 @@ namespace Service::Capture { class CAPS_U final : public ServiceFramework<CAPS_U> { public: - explicit CAPS_U(); + explicit CAPS_U(Core::System& system_); ~CAPS_U() override; private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); void GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx); + void GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx); }; } // namespace Service::Capture diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp index 4ec8c3093..4924c61c3 100644 --- a/src/core/hle/service/erpt/erpt.cpp +++ b/src/core/hle/service/erpt/erpt.cpp @@ -12,7 +12,7 @@ namespace Service::ERPT { class ErrorReportContext final : public ServiceFramework<ErrorReportContext> { public: - explicit ErrorReportContext() : ServiceFramework{"erpt:c"} { + explicit ErrorReportContext(Core::System& system_) : ServiceFramework{system_, "erpt:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SubmitContext"}, @@ -35,7 +35,7 @@ public: class ErrorReportSession final : public ServiceFramework<ErrorReportSession> { public: - explicit ErrorReportSession() : ServiceFramework{"erpt:r"} { + explicit ErrorReportSession(Core::System& system_) : ServiceFramework{system_, "erpt:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenReport"}, @@ -48,9 +48,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<ErrorReportContext>()->InstallAsService(sm); - std::make_shared<ErrorReportSession>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<ErrorReportContext>(system)->InstallAsService(sm); + std::make_shared<ErrorReportSession>(system)->InstallAsService(sm); } } // namespace Service::ERPT diff --git a/src/core/hle/service/erpt/erpt.h b/src/core/hle/service/erpt/erpt.h index de439ab6d..8cd5c081f 100644 --- a/src/core/hle/service/erpt/erpt.h +++ b/src/core/hle/service/erpt/erpt.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::ERPT { /// Registers all ERPT services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::ERPT diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index c2737a365..26d1e3306 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -14,7 +14,7 @@ constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; class ETicket final : public ServiceFramework<ETicket> { public: - explicit ETicket() : ServiceFramework{"es"} { + explicit ETicket(Core::System& system_) : ServiceFramework{system_, "es"} { // clang-format off static const FunctionInfo functions[] = { {1, &ETicket::ImportTicket, "ImportTicket"}, @@ -305,8 +305,8 @@ private: Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<ETicket>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<ETicket>(system)->InstallAsService(service_manager); } } // namespace Service::ES diff --git a/src/core/hle/service/es/es.h b/src/core/hle/service/es/es.h index afe70465b..2a7b27d12 100644 --- a/src/core/hle/service/es/es.h +++ b/src/core/hle/service/es/es.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::ES { /// Registers all ES services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::ES diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp index 0d6d244f4..2d650b1b7 100644 --- a/src/core/hle/service/eupld/eupld.cpp +++ b/src/core/hle/service/eupld/eupld.cpp @@ -12,7 +12,7 @@ namespace Service::EUPLD { class ErrorUploadContext final : public ServiceFramework<ErrorUploadContext> { public: - explicit ErrorUploadContext() : ServiceFramework{"eupld:c"} { + explicit ErrorUploadContext(Core::System& system_) : ServiceFramework{system_, "eupld:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetUrl"}, @@ -29,7 +29,7 @@ public: class ErrorUploadRequest final : public ServiceFramework<ErrorUploadRequest> { public: - explicit ErrorUploadRequest() : ServiceFramework{"eupld:r"} { + explicit ErrorUploadRequest(Core::System& system_) : ServiceFramework{system_, "eupld:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -45,9 +45,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<ErrorUploadContext>()->InstallAsService(sm); - std::make_shared<ErrorUploadRequest>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<ErrorUploadContext>(system)->InstallAsService(sm); + std::make_shared<ErrorUploadRequest>(system)->InstallAsService(sm); } } // namespace Service::EUPLD diff --git a/src/core/hle/service/eupld/eupld.h b/src/core/hle/service/eupld/eupld.h index 6eef2c15f..539993a9d 100644 --- a/src/core/hle/service/eupld/eupld.h +++ b/src/core/hle/service/eupld/eupld.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::EUPLD { /// Registers all EUPLD services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::EUPLD diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 2546d7595..13147472e 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -20,8 +20,9 @@ namespace Service::Fatal { -Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) {} +Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; @@ -110,8 +111,9 @@ static void GenerateErrorReport(Core::System& system, ResultCode error_code, static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { - LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", - static_cast<u32>(fatal_type), error_code.raw); + LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", fatal_type, + error_code.raw); + switch (fatal_type) { case FatalType::ErrorReportAndScreen: GenerateErrorReport(system, error_code, info); diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index bd9339dfc..2095bf89f 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -16,7 +16,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name); ~Interface() override; void ThrowFatal(Kernel::HLERequestContext& ctx); @@ -25,7 +26,6 @@ public: protected: std::shared_ptr<Module> module; - Core::System& system; }; }; diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index 066ccf6b0..8672b85dc 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -6,8 +6,8 @@ namespace Service::Fatal { -Fatal_P::Fatal_P(std::shared_ptr<Module> module, Core::System& system) - : Module::Interface(std::move(module), system, "fatal:p") {} +Fatal_P::Fatal_P(std::shared_ptr<Module> module_, Core::System& system_) + : Interface(std::move(module_), system_, "fatal:p") {} Fatal_P::~Fatal_P() = default; diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h index c6d953cb5..ffa5b7b98 100644 --- a/src/core/hle/service/fatal/fatal_p.h +++ b/src/core/hle/service/fatal/fatal_p.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_P final : public Module::Interface { public: - explicit Fatal_P(std::shared_ptr<Module> module, Core::System& system); + explicit Fatal_P(std::shared_ptr<Module> module_, Core::System& system_); ~Fatal_P() override; }; diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index 8d72ed485..82993938a 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -6,8 +6,8 @@ namespace Service::Fatal { -Fatal_U::Fatal_U(std::shared_ptr<Module> module, Core::System& system) - : Module::Interface(std::move(module), system, "fatal:u") { +Fatal_U::Fatal_U(std::shared_ptr<Module> module_, Core::System& system_) + : Interface(std::move(module_), system_, "fatal:u") { static const FunctionInfo functions[] = { {0, &Fatal_U::ThrowFatal, "ThrowFatal"}, {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h index 34c5c7f95..0b58c9112 100644 --- a/src/core/hle/service/fatal/fatal_u.h +++ b/src/core/hle/service/fatal/fatal_u.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_U final : public Module::Interface { public: - explicit Fatal_U(std::shared_ptr<Module> module, Core::System& system); + explicit Fatal_U(std::shared_ptr<Module> module_, Core::System& system_); ~Fatal_U() override; }; diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp index e461274c1..9dc1bc52e 100644 --- a/src/core/hle/service/fgm/fgm.cpp +++ b/src/core/hle/service/fgm/fgm.cpp @@ -14,7 +14,7 @@ namespace Service::FGM { class IRequest final : public ServiceFramework<IRequest> { public: - explicit IRequest() : ServiceFramework{"IRequest"} { + explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -30,7 +30,7 @@ public: class FGM final : public ServiceFramework<FGM> { public: - explicit FGM(const char* name) : ServiceFramework{name} { + explicit FGM(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &FGM::Initialize, "Initialize"}, @@ -46,13 +46,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IRequest>(); + rb.PushIpcInterface<IRequest>(system); } }; class FGM_DBG final : public ServiceFramework<FGM_DBG> { public: - explicit FGM_DBG() : ServiceFramework{"fgm:dbg"} { + explicit FGM_DBG(Core::System& system_) : ServiceFramework{system_, "fgm:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -65,11 +65,11 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<FGM>("fgm")->InstallAsService(sm); - std::make_shared<FGM>("fgm:0")->InstallAsService(sm); - std::make_shared<FGM>("fgm:9")->InstallAsService(sm); - std::make_shared<FGM_DBG>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<FGM>(system, "fgm")->InstallAsService(sm); + std::make_shared<FGM>(system, "fgm:0")->InstallAsService(sm); + std::make_shared<FGM>(system, "fgm:9")->InstallAsService(sm); + std::make_shared<FGM_DBG>(system)->InstallAsService(sm); } } // namespace Service::FGM diff --git a/src/core/hle/service/fgm/fgm.h b/src/core/hle/service/fgm/fgm.h index e59691264..75978f2ed 100644 --- a/src/core/hle/service/fgm/fgm.h +++ b/src/core/hle/service/fgm/fgm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::FGM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::FGM diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 2cee1193c..b15c737e1 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -79,7 +79,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons } auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { + if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; } if (!dir->DeleteFile(Common::FS::GetFilename(path))) { @@ -93,8 +93,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr && Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) + if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) { dir = backing; + } auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path)); if (new_dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this @@ -297,10 +298,35 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID()); } +ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS( + u64 title_id, FileSys::ContentRecordType type) const { + LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id); + + if (romfs_factory == nullptr) { + // TODO: Find a better error code for this + return RESULT_UNKNOWN; + } + + return romfs_factory->OpenPatchedRomFS(title_id, type); +} + +ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, FileSys::ContentRecordType type) const { + LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id, + program_index); + + if (romfs_factory == nullptr) { + // TODO: Find a better error code for this + return RESULT_UNKNOWN; + } + + return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type); +} + ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", - title_id, static_cast<u8>(storage_id), static_cast<u8>(type)); + title_id, storage_id, type); if (romfs_factory == nullptr) { // TODO(bunnei): Find a better error code for this @@ -312,8 +338,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const { - LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", - static_cast<u8>(space), save_struct.DebugInfo()); + LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space, + save_struct.DebugInfo()); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -324,8 +350,8 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const { - LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", - static_cast<u8>(space), attribute.DebugInfo()); + LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space, + attribute.DebugInfo()); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -336,7 +362,7 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace( FileSys::SaveDataSpaceId space) const { - LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space)); + LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -357,7 +383,7 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const { ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( FileSys::BisPartitionId id) const { - LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast<u32>(id)); + LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id); if (bis_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -373,13 +399,13 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( FileSys::BisPartitionId id) const { - LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast<u32>(id)); + LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id); if (bis_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; } - auto part = bis_factory->OpenPartitionStorage(id); + auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem()); if (part == nullptr) { return FileSys::ERROR_INVALID_ARGUMENT; } @@ -454,7 +480,9 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy const auto res = system.GetAppLoader().ReadControlData(nacp); if (res != Loader::ResultStatus::Success) { - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; + const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(), + system.GetFileSystemController(), + system.GetContentProvider()}; const auto metadata = pm.GetControlMetadata(); const auto& nacp_unique = metadata.first; @@ -714,7 +742,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } if (save_data_factory == nullptr) { - save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); + save_data_factory = + std::make_unique<FileSys::SaveDataFactory>(system, std::move(nand_directory)); } if (sdmc_factory == nullptr) { @@ -725,10 +754,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } void InstallInterfaces(Core::System& system) { - std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager()); - std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager()); - std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter()) - ->InstallAsService(system.ServiceManager()); + std::make_shared<FSP_LDR>(system)->InstallAsService(system.ServiceManager()); + std::make_shared<FSP_PR>(system)->InstallAsService(system.ServiceManager()); + std::make_shared<FSP_SRV>(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 6dbbf0b2b..7102d3f9a 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -66,6 +66,10 @@ public: void SetPackedUpdate(FileSys::VirtualFile update_raw); ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const; + ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id, + FileSys::ContentRecordType type) const; + ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, FileSys::ContentRecordType type) const; ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const; ResultVal<FileSys::VirtualDir> CreateSaveData( diff --git a/src/core/hle/service/filesystem/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp_ldr.cpp index fb487d5bc..1f6c17ba5 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.cpp +++ b/src/core/hle/service/filesystem/fsp_ldr.cpp @@ -7,7 +7,7 @@ namespace Service::FileSystem { -FSP_LDR::FSP_LDR() : ServiceFramework{"fsp:ldr"} { +FSP_LDR::FSP_LDR(Core::System& system_) : ServiceFramework{system_, "fsp:ldr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenCodeFileSystem"}, diff --git a/src/core/hle/service/filesystem/fsp_ldr.h b/src/core/hle/service/filesystem/fsp_ldr.h index 8210b7729..d6432a0e1 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.h +++ b/src/core/hle/service/filesystem/fsp_ldr.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::FileSystem { class FSP_LDR final : public ServiceFramework<FSP_LDR> { public: - explicit FSP_LDR(); + explicit FSP_LDR(Core::System& system_); ~FSP_LDR() override; }; diff --git a/src/core/hle/service/filesystem/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp_pr.cpp index 378201610..00e4d1662 100644 --- a/src/core/hle/service/filesystem/fsp_pr.cpp +++ b/src/core/hle/service/filesystem/fsp_pr.cpp @@ -7,7 +7,7 @@ namespace Service::FileSystem { -FSP_PR::FSP_PR() : ServiceFramework{"fsp:pr"} { +FSP_PR::FSP_PR(Core::System& system_) : ServiceFramework{system_, "fsp:pr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RegisterProgram"}, diff --git a/src/core/hle/service/filesystem/fsp_pr.h b/src/core/hle/service/filesystem/fsp_pr.h index 556ae5ce9..9e622518c 100644 --- a/src/core/hle/service/filesystem/fsp_pr.h +++ b/src/core/hle/service/filesystem/fsp_pr.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::FileSystem { class FSP_PR final : public ServiceFramework<FSP_PR> { public: - explicit FSP_PR(); + explicit FSP_PR(Core::System& system_); ~FSP_PR() override; }; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 26fd87f58..9cc260515 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -14,6 +14,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/file_sys/directory.h" #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" @@ -56,8 +57,8 @@ enum class FileSystemType : u8 { class IStorage final : public ServiceFramework<IStorage> { public: - explicit IStorage(FileSys::VirtualFile backend_) - : ServiceFramework("IStorage"), backend(std::move(backend_)) { + explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, @@ -114,8 +115,8 @@ private: class IFile final : public ServiceFramework<IFile> { public: - explicit IFile(FileSys::VirtualFile backend_) - : ServiceFramework("IFile"), backend(std::move(backend_)) { + explicit IFile(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"}, @@ -246,8 +247,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec class IDirectory final : public ServiceFramework<IDirectory> { public: - explicit IDirectory(FileSys::VirtualDir backend_) - : ServiceFramework("IDirectory"), backend(std::move(backend_)) { + explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_) + : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IDirectory::Read, "Read"}, {1, &IDirectory::GetEntryCount, "GetEntryCount"}, @@ -302,8 +303,9 @@ private: class IFileSystem final : public ServiceFramework<IFileSystem> { public: - explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size) - : ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) { + explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) + : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move( + size_)} { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, {1, &IFileSystem::DeleteFile, "DeleteFile"}, @@ -411,7 +413,7 @@ public: const auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>()); - LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, static_cast<u32>(mode)); + LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode); auto result = backend.OpenFile(name, mode); if (result.Failed()) { @@ -420,7 +422,7 @@ public: return; } - auto file = std::make_shared<IFile>(result.Unwrap()); + auto file = std::make_shared<IFile>(system, result.Unwrap()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -445,7 +447,7 @@ public: return; } - auto directory = std::make_shared<IDirectory>(result.Unwrap()); + auto directory = std::make_shared<IDirectory>(system, result.Unwrap()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -500,8 +502,9 @@ private: class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> { public: - explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc) - : ServiceFramework("ISaveDataInfoReader"), fsc(fsc) { + explicit ISaveDataInfoReader(Core::System& system_, FileSys::SaveDataSpaceId space, + FileSystemController& fsc_) + : ServiceFramework{system_, "ISaveDataInfoReader"}, fsc{fsc_} { static const FunctionInfo functions[] = { {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"}, }; @@ -550,8 +553,7 @@ private: const auto save_root = fsc.OpenSaveDataSpace(space); if (save_root.Failed() || *save_root == nullptr) { - LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", - static_cast<u8>(space)); + LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space); return; } @@ -650,8 +652,9 @@ private: u64 next_entry_index = 0; }; -FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) - : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) { +FSP_SRV::FSP_SRV(Core::System& system_) + : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()}, + content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenFileSystem"}, @@ -714,7 +717,7 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) {202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"}, {203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"}, {204, nullptr, "OpenDataFileSystemByProgramIndex"}, - {205, nullptr, "OpenDataStorageByProgramIndex"}, + {205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"}, {400, nullptr, "OpenDeviceOperator"}, {500, nullptr, "OpenSdCardDetectionEventNotifier"}, {501, nullptr, "OpenGameCardDetectionEventNotifier"}, @@ -791,8 +794,7 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { const auto type = rp.PopRaw<FileSystemType>(); const auto title_id = rp.PopRaw<u64>(); - LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", - static_cast<u8>(type), title_id); + LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", type, title_id); IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_UNKNOWN); @@ -801,8 +803,9 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto filesystem = std::make_shared<IFileSystem>( - fsc.OpenSDMC().Unwrap(), SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); + auto filesystem = + std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(), + SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -844,8 +847,7 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { return; } - FileSys::StorageId id; - + FileSys::StorageId id{}; switch (parameters.space_id) { case FileSys::SaveDataSpaceId::NandUser: id = FileSys::StorageId::NandUser; @@ -857,10 +859,14 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { case FileSys::SaveDataSpaceId::NandSystem: id = FileSys::StorageId::NandSystem; break; + case FileSys::SaveDataSpaceId::TemporaryStorage: + case FileSys::SaveDataSpaceId::ProperSystem: + case FileSys::SaveDataSpaceId::SafeMode: + UNREACHABLE(); } - auto filesystem = - std::make_shared<IFileSystem>(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id)); + auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()), + SizeGetter::FromStorageId(fsc, id)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -875,11 +881,12 @@ void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto space = rp.PopRaw<FileSys::SaveDataSpaceId>(); - LOG_INFO(Service_FS, "called, space={}", static_cast<u8>(space)); + LOG_INFO(Service_FS, "called, space={}", space); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc)); + rb.PushIpcInterface<ISaveDataInfoReader>( + std::make_shared<ISaveDataInfoReader>(system, space, fsc)); } void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) { @@ -902,7 +909,14 @@ void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute( // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None); - LOG_WARNING(Service_FS, "(STUBBED) called, flags={}", flags); + LOG_WARNING(Service_FS, + "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n" + "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n" + "attribute.type={}, attribute.rank={}, attribute.index={}", + flags, parameters.space_id, parameters.attribute.title_id, + parameters.attribute.user_id[1], parameters.attribute.user_id[0], + parameters.attribute.save_id, parameters.attribute.type, parameters.attribute.rank, + parameters.attribute.index); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -921,7 +935,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { return; } - auto storage = std::make_shared<IStorage>(std::move(romfs.Unwrap())); + auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap())); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -935,7 +949,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { const auto title_id = rp.PopRaw<u64>(); LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", - static_cast<u8>(storage_id), unknown, title_id); + storage_id, unknown, title_id); auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); @@ -945,23 +959,23 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { if (archive != nullptr) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::make_shared<IStorage>(archive)); + rb.PushIpcInterface(std::make_shared<IStorage>(system, archive)); return; } // TODO(DarkLordZach): Find the right error code to use here LOG_ERROR(Service_FS, "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, - static_cast<u8>(storage_id)); + storage_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); return; } - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, fsc, content_provider}; auto storage = std::make_shared<IStorage>( - pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); + system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -971,21 +985,46 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto storage_id = rp.PopRaw<FileSys::StorageId>(); - auto title_id = rp.PopRaw<u64>(); + const auto storage_id = rp.PopRaw<FileSys::StorageId>(); + const auto title_id = rp.PopRaw<u64>(); - LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", - static_cast<u8>(storage_id), title_id); + LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", storage_id, title_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); } +void FSP_SRV::OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto program_index = rp.PopRaw<u8>(); + + LOG_DEBUG(Service_FS, "called, program_index={}", program_index); + + auto romfs = fsc.OpenPatchedRomFSWithProgramIndex( + system.CurrentProcess()->GetTitleID(), program_index, FileSys::ContentRecordType::Program); + + if (romfs.Failed()) { + // TODO: Find the right error code to use here + LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap())); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(storage)); +} + void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; log_mode = rp.PopEnum<LogMode>(); - LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode)); + LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1023,7 +1062,8 @@ void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) { class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> { public: - explicit IMultiCommitManager() : ServiceFramework("IMultiCommitManager") { + explicit IMultiCommitManager(Core::System& system_) + : ServiceFramework{system_, "IMultiCommitManager"} { static const FunctionInfo functions[] = { {1, &IMultiCommitManager::Add, "Add"}, {2, &IMultiCommitManager::Commit, "Commit"}, @@ -1054,7 +1094,7 @@ void FSP_SRV::OpenMultiCommitManager(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>()); + rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>(system)); } } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 4964e874e..8ed933279 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -12,8 +12,9 @@ class Reporter; } namespace FileSys { +class ContentProvider; class FileSystemBackend; -} +} // namespace FileSys namespace Service::FileSystem { @@ -32,7 +33,7 @@ enum class LogMode : u32 { class FSP_SRV final : public ServiceFramework<FSP_SRV> { public: - explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter); + explicit FSP_SRV(Core::System& system_); ~FSP_SRV() override; private: @@ -48,6 +49,7 @@ private: void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx); void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx); @@ -55,6 +57,7 @@ private: void OpenMultiCommitManager(Kernel::HLERequestContext& ctx); FileSystemController& fsc; + const FileSys::ContentProvider& content_provider; FileSys::VirtualFile romfs; u64 current_process_id = 0; diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index b7adaffc7..72a877d68 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -5,9 +5,11 @@ #include <queue> #include "common/logging/log.h" #include "common/uuid.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/friend/errors.h" #include "core/hle/service/friend/friend.h" #include "core/hle/service/friend/interface.h" @@ -16,7 +18,7 @@ namespace Service::Friend { class IFriendService final : public ServiceFramework<IFriendService> { public: - IFriendService() : ServiceFramework("IFriendService") { + explicit IFriendService(Core::System& system_) : ServiceFramework{system_, "IFriendService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetCompletionEvent"}, @@ -170,8 +172,8 @@ private: class INotificationService final : public ServiceFramework<INotificationService> { public: - INotificationService(Common::UUID uuid, Core::System& system) - : ServiceFramework("INotificationService"), uuid(uuid) { + explicit INotificationService(Common::UUID uuid_, Core::System& system_) + : ServiceFramework{system_, "INotificationService"}, uuid{uuid_} { // clang-format off static const FunctionInfo functions[] = { {0, &INotificationService::GetEvent, "GetEvent"}, @@ -182,8 +184,9 @@ public: RegisterHandlers(functions); - notification_event = Kernel::WritableEvent::CreateEventPair( - system.Kernel(), "INotificationService:NotifyEvent"); + notification_event = + Kernel::KEvent::Create(system.Kernel(), "INotificationService:NotifyEvent"); + notification_event->Initialize(); } private: @@ -192,7 +195,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(notification_event.readable); + rb.PushCopyObjects(notification_event->GetReadableEvent()); } void Clear(Kernel::HLERequestContext& ctx) { @@ -228,8 +231,7 @@ private: break; default: // HOS seems not have an error case for an unknown notification - LOG_WARNING(Service_ACC, "Unknown notification {:08X}", - static_cast<u32>(notification.notification_type)); + LOG_WARNING(Service_ACC, "Unknown notification {:08X}", notification.notification_type); break; } @@ -258,7 +260,7 @@ private: }; Common::UUID uuid{Common::INVALID_UUID}; - Kernel::EventPair notification_event; + std::shared_ptr<Kernel::KEvent> notification_event; std::queue<SizedNotificationInfo> notifications; States states{}; }; @@ -266,7 +268,7 @@ private: void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IFriendService>(); + rb.PushIpcInterface<IFriendService>(system); LOG_DEBUG(Service_ACC, "called"); } @@ -281,8 +283,9 @@ void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx rb.PushIpcInterface<INotificationService>(uuid, system); } -Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) {} +Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index 24f3fc969..8be3321db 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -16,7 +16,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name); ~Interface() override; void CreateFriendService(Kernel::HLERequestContext& ctx); @@ -24,7 +25,6 @@ public: protected: std::shared_ptr<Module> module; - Core::System& system; }; }; diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp index 58155f652..7368ccec2 100644 --- a/src/core/hle/service/friend/interface.cpp +++ b/src/core/hle/service/friend/interface.cpp @@ -6,8 +6,8 @@ namespace Service::Friend { -Friend::Friend(std::shared_ptr<Module> module, Core::System& system, const char* name) - : Interface(std::move(module), system, name) { +Friend::Friend(std::shared_ptr<Module> module_, Core::System& system_, const char* name) + : Interface(std::move(module_), system_, name) { static const FunctionInfo functions[] = { {0, &Friend::CreateFriendService, "CreateFriendService"}, {1, &Friend::CreateNotificationService, "CreateNotificationService"}, diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h index 465a35770..43d914b32 100644 --- a/src/core/hle/service/friend/interface.h +++ b/src/core/hle/service/friend/interface.h @@ -10,7 +10,7 @@ namespace Service::Friend { class Friend final : public Module::Interface { public: - explicit Friend(std::shared_ptr<Module> module, Core::System& system, const char* name); + explicit Friend(std::shared_ptr<Module> module_, Core::System& system_, const char* name); ~Friend() override; }; diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index b591ce31b..fc77e7286 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -5,6 +5,7 @@ #include <memory> #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" @@ -32,8 +33,8 @@ std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 proces } } // Anonymous namespace -ARP_R::ARP_R(const Core::System& system, const ARPManager& manager) - : ServiceFramework{"arp:r"}, system(system), manager(manager) { +ARP_R::ARP_R(Core::System& system_, const ARPManager& manager_) + : ServiceFramework{system_, "arp:r"}, manager{manager_} { // clang-format off static const FunctionInfo functions[] = { {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"}, @@ -151,8 +152,9 @@ class IRegistrar final : public ServiceFramework<IRegistrar> { public: explicit IRegistrar( + Core::System& system_, std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer) - : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) { + : ServiceFramework{system_, "IRegistrar"}, issue_process_id{std::move(issuer)} { // clang-format off static const FunctionInfo functions[] = { {0, &IRegistrar::Issue, "Issue"}, @@ -236,8 +238,8 @@ private: std::vector<u8> control; }; -ARP_W::ARP_W(const Core::System& system, ARPManager& manager) - : ServiceFramework{"arp:w"}, system(system), manager(manager) { +ARP_W::ARP_W(Core::System& system_, ARPManager& manager_) + : ServiceFramework{system_, "arp:w"}, manager{manager_} { // clang-format off static const FunctionInfo functions[] = { {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, @@ -254,7 +256,7 @@ void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ARP, "called"); registrar = std::make_shared<IRegistrar>( - [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) { + system, [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) { const auto res = GetTitleIDForProcessID(system, process_id); if (!res.has_value()) { return ERR_NOT_REGISTERED; diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h index d5f8a7e7a..34b412e26 100644 --- a/src/core/hle/service/glue/arp.h +++ b/src/core/hle/service/glue/arp.h @@ -13,7 +13,7 @@ class IRegistrar; class ARP_R final : public ServiceFramework<ARP_R> { public: - explicit ARP_R(const Core::System& system, const ARPManager& manager); + explicit ARP_R(Core::System& system_, const ARPManager& manager_); ~ARP_R() override; private: @@ -22,20 +22,18 @@ private: void GetApplicationControlProperty(Kernel::HLERequestContext& ctx); void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx); - const Core::System& system; const ARPManager& manager; }; class ARP_W final : public ServiceFramework<ARP_W> { public: - explicit ARP_W(const Core::System& system, ARPManager& manager); + explicit ARP_W(Core::System& system_, ARPManager& manager_); ~ARP_W() override; private: void AcquireRegistrar(Kernel::HLERequestContext& ctx); void DeleteProperties(Kernel::HLERequestContext& ctx); - const Core::System& system; ARPManager& manager; std::shared_ptr<IRegistrar> registrar; }; diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp index cd89d088f..a478b68e1 100644 --- a/src/core/hle/service/glue/bgtc.cpp +++ b/src/core/hle/service/glue/bgtc.cpp @@ -6,7 +6,7 @@ namespace Service::Glue { -BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { +BGTC_T::BGTC_T(Core::System& system_) : ServiceFramework{system_, "bgtc:t"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "NotifyTaskStarting"}, @@ -31,7 +31,7 @@ BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { BGTC_T::~BGTC_T() = default; -BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} { +BGTC_SC::BGTC_SC(Core::System& system_) : ServiceFramework{system_, "bgtc:sc"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "GetState"}, diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h index 81844f03e..906116ba6 100644 --- a/src/core/hle/service/glue/bgtc.h +++ b/src/core/hle/service/glue/bgtc.h @@ -6,17 +6,21 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Glue { class BGTC_T final : public ServiceFramework<BGTC_T> { public: - BGTC_T(); + explicit BGTC_T(Core::System& system_); ~BGTC_T() override; }; class BGTC_SC final : public ServiceFramework<BGTC_SC> { public: - BGTC_SC(); + explicit BGTC_SC(Core::System& system_); ~BGTC_SC() override; }; diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp index c728e815c..4eafbe5fa 100644 --- a/src/core/hle/service/glue/glue.cpp +++ b/src/core/hle/service/glue/glue.cpp @@ -18,8 +18,8 @@ void InstallInterfaces(Core::System& system) { ->InstallAsService(system.ServiceManager()); // BackGround Task Controller - std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager()); - std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager()); + std::make_shared<BGTC_T>(system)->InstallAsService(system.ServiceManager()); + std::make_shared<BGTC_SC>(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::Glue diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp index 401e0b208..a502ab47f 100644 --- a/src/core/hle/service/grc/grc.cpp +++ b/src/core/hle/service/grc/grc.cpp @@ -12,7 +12,7 @@ namespace Service::GRC { class GRC final : public ServiceFramework<GRC> { public: - explicit GRC() : ServiceFramework{"grc:c"} { + explicit GRC(Core::System& system) : ServiceFramework{system, "grc:c"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "OpenContinuousRecorder"}, @@ -27,8 +27,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<GRC>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<GRC>(system)->InstallAsService(sm); } } // namespace Service::GRC diff --git a/src/core/hle/service/grc/grc.h b/src/core/hle/service/grc/grc.h index e0d29e70d..9069fe756 100644 --- a/src/core/hle/service/grc/grc.h +++ b/src/core/hle/service/grc/grc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::GRC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::GRC diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h index 8bc69c372..f47a9e61c 100644 --- a/src/core/hle/service/hid/controllers/controller_base.h +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -31,6 +31,10 @@ public: virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) = 0; + // When the controller is requesting a motion update for the shared memory + virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t size) {} + // Called when input devices should be loaded virtual void OnLoadInputDevices() = 0; diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index 0b896d5ad..c4a59147d 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -39,16 +39,25 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, cur_entry.sampling_number2 = cur_entry.sampling_number; cur_entry.key.fill(0); - cur_entry.modifier = 0; if (Settings::values.keyboard_enabled) { for (std::size_t i = 0; i < keyboard_keys.size(); ++i) { - cur_entry.key[i / KEYS_PER_BYTE] |= - (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)); + auto& entry = cur_entry.key[i / KEYS_PER_BYTE]; + entry = static_cast<u8>(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE))); } - for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { - cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i); - } + using namespace Settings::NativeKeyboard; + + // TODO: Assign the correct key to all modifiers + cur_entry.modifier.control.Assign(keyboard_mods[LeftControl]->GetStatus()); + cur_entry.modifier.shift.Assign(keyboard_mods[LeftShift]->GetStatus()); + cur_entry.modifier.left_alt.Assign(keyboard_mods[LeftAlt]->GetStatus()); + cur_entry.modifier.right_alt.Assign(keyboard_mods[RightAlt]->GetStatus()); + cur_entry.modifier.gui.Assign(0); + cur_entry.modifier.caps_lock.Assign(keyboard_mods[CapsLock]->GetStatus()); + cur_entry.modifier.scroll_lock.Assign(keyboard_mods[ScrollLock]->GetStatus()); + cur_entry.modifier.num_lock.Assign(keyboard_mods[NumLock]->GetStatus()); + cur_entry.modifier.katakana.Assign(0); + cur_entry.modifier.hiragana.Assign(0); } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index f3eef5936..b5b281752 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -31,12 +32,28 @@ public: void OnLoadInputDevices() override; private: + struct Modifiers { + union { + u32_le raw{}; + BitField<0, 1, u32> control; + BitField<1, 1, u32> shift; + BitField<2, 1, u32> left_alt; + BitField<3, 1, u32> right_alt; + BitField<4, 1, u32> gui; + BitField<8, 1, u32> caps_lock; + BitField<9, 1, u32> scroll_lock; + BitField<10, 1, u32> num_lock; + BitField<11, 1, u32> katakana; + BitField<12, 1, u32> hiragana; + }; + }; + static_assert(sizeof(Modifiers) == 0x4, "Modifiers is an invalid size"); + struct KeyboardState { s64_le sampling_number; s64_le sampling_number2; - s32_le modifier; - s32_le attribute; + Modifiers modifier; std::array<u8, 32> key; }; static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size"); diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index ac40989c5..2e7457604 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -36,6 +36,7 @@ void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; + cur_entry.attribute.raw = 0; if (Settings::values.mouse_enabled) { const auto [px, py, sx, sy] = mouse_device->GetStatus(); const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width); @@ -46,10 +47,14 @@ void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* cur_entry.delta_y = y - last_entry.y; cur_entry.mouse_wheel_x = sx; cur_entry.mouse_wheel_y = sy; + cur_entry.attribute.is_connected.Assign(1); - for (std::size_t i = 0; i < mouse_button_devices.size(); ++i) { - cur_entry.button |= (mouse_button_devices[i]->GetStatus() << i); - } + using namespace Settings::NativeMouseButton; + cur_entry.button.left.Assign(mouse_button_devices[Left]->GetStatus()); + cur_entry.button.right.Assign(mouse_button_devices[Right]->GetStatus()); + cur_entry.button.middle.Assign(mouse_button_devices[Middle]->GetStatus()); + cur_entry.button.forward.Assign(mouse_button_devices[Forward]->GetStatus()); + cur_entry.button.back.Assign(mouse_button_devices[Back]->GetStatus()); } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index 357ab7107..3b432a36e 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" #include "core/frontend/input.h" @@ -30,6 +31,27 @@ public: void OnLoadInputDevices() override; private: + struct Buttons { + union { + u32_le raw{}; + BitField<0, 1, u32> left; + BitField<1, 1, u32> right; + BitField<2, 1, u32> middle; + BitField<3, 1, u32> forward; + BitField<4, 1, u32> back; + }; + }; + static_assert(sizeof(Buttons) == 0x4, "Buttons is an invalid size"); + + struct Attributes { + union { + u32_le raw{}; + BitField<0, 1, u32> transferable; + BitField<1, 1, u32> is_connected; + }; + }; + static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + struct MouseState { s64_le sampling_number; s64_le sampling_number2; @@ -39,8 +61,8 @@ private: s32_le delta_y; s32_le mouse_wheel_x; s32_le mouse_wheel_y; - s32_le button; - s32_le attribute; + Buttons button; + Attributes attribute; }; static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size"); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 45fde8df2..70b9f3824 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -12,14 +12,16 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/input.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/settings.h" namespace Service::HID { constexpr s32 HID_JOYSTICK_MAX = 0x7fff; +constexpr s32 HID_TRIGGER_MAX = 0x7fff; [[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; @@ -47,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad( return NPadControllerType::JoyRight; case Settings::ControllerType::Handheld: return NPadControllerType::Handheld; + case Settings::ControllerType::GameCube: + return NPadControllerType::GameCube; default: UNREACHABLE(); return NPadControllerType::ProController; @@ -66,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType( return Settings::ControllerType::RightJoycon; case NPadControllerType::Handheld: return Settings::ControllerType::Handheld; + case NPadControllerType::GameCube: + return Settings::ControllerType::GameCube; default: UNREACHABLE(); return Settings::ControllerType::ProController; @@ -116,113 +122,180 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) { } } -Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {} -Controller_NPad::~Controller_NPad() = default; +bool Controller_NPad::IsNpadIdValid(u32 npad_id) { + switch (npad_id) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case NPAD_UNKNOWN: + case NPAD_HANDHELD: + return true; + default: + LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id); + return false; + } +} + +bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) { + return IsNpadIdValid(device_handle.npad_id) && + device_handle.npad_type < NpadType::MaxNpadType && + device_handle.device_index < DeviceIndex::MaxDeviceIndex; +} + +Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) { + latest_vibration_values.fill({DEFAULT_VIBRATION_VALUE, DEFAULT_VIBRATION_VALUE}); +} + +Controller_NPad::~Controller_NPad() { + OnRelease(); +} void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { const auto controller_type = connected_controllers[controller_idx].type; auto& controller = shared_memory_entries[controller_idx]; if (controller_type == NPadControllerType::None) { - styleset_changed_events[controller_idx].writable->Signal(); + styleset_changed_events[controller_idx]->GetWritableEvent()->Signal(); return; } - controller.joy_styles.raw = 0; // Zero out + controller.style_set.raw = 0; // Zero out controller.device_type.raw = 0; - controller.properties.raw = 0; + controller.system_properties.raw = 0; switch (controller_type) { case NPadControllerType::None: UNREACHABLE(); break; case NPadControllerType::ProController: - controller.joy_styles.pro_controller.Assign(1); - controller.device_type.pro_controller.Assign(1); - controller.properties.is_vertical.Assign(1); - controller.properties.use_plus.Assign(1); - controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.style_set.fullkey.Assign(1); + controller.device_type.fullkey.Assign(1); + controller.system_properties.is_vertical.Assign(1); + controller.system_properties.use_plus.Assign(1); + controller.system_properties.use_minus.Assign(1); + controller.assignment_mode = NpadAssignments::Single; + controller.footer_type = AppletFooterUiType::SwitchProController; break; case NPadControllerType::Handheld: - controller.joy_styles.handheld.Assign(1); - controller.device_type.handheld.Assign(1); - controller.properties.is_vertical.Assign(1); - controller.properties.use_plus.Assign(1); - controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.style_set.handheld.Assign(1); + controller.device_type.handheld_left.Assign(1); + controller.device_type.handheld_right.Assign(1); + controller.system_properties.is_vertical.Assign(1); + controller.system_properties.use_plus.Assign(1); + controller.system_properties.use_minus.Assign(1); + controller.assignment_mode = NpadAssignments::Dual; + controller.footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; break; case NPadControllerType::JoyDual: - controller.joy_styles.joycon_dual.Assign(1); + controller.style_set.joycon_dual.Assign(1); controller.device_type.joycon_left.Assign(1); controller.device_type.joycon_right.Assign(1); - controller.properties.is_vertical.Assign(1); - controller.properties.use_plus.Assign(1); - controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.system_properties.is_vertical.Assign(1); + controller.system_properties.use_plus.Assign(1); + controller.system_properties.use_minus.Assign(1); + controller.assignment_mode = NpadAssignments::Dual; + controller.footer_type = AppletFooterUiType::JoyDual; break; case NPadControllerType::JoyLeft: - controller.joy_styles.joycon_left.Assign(1); + controller.style_set.joycon_left.Assign(1); controller.device_type.joycon_left.Assign(1); - controller.properties.is_horizontal.Assign(1); - controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.system_properties.is_horizontal.Assign(1); + controller.system_properties.use_minus.Assign(1); + controller.assignment_mode = NpadAssignments::Single; + controller.footer_type = AppletFooterUiType::JoyLeftHorizontal; break; case NPadControllerType::JoyRight: - controller.joy_styles.joycon_right.Assign(1); + controller.style_set.joycon_right.Assign(1); controller.device_type.joycon_right.Assign(1); - controller.properties.is_horizontal.Assign(1); - controller.properties.use_plus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.system_properties.is_horizontal.Assign(1); + controller.system_properties.use_plus.Assign(1); + controller.assignment_mode = NpadAssignments::Single; + controller.footer_type = AppletFooterUiType::JoyRightHorizontal; + break; + case NPadControllerType::GameCube: + controller.style_set.gamecube.Assign(1); + // The GC Controller behaves like a wired Pro Controller + controller.device_type.fullkey.Assign(1); + controller.system_properties.is_vertical.Assign(1); + controller.system_properties.use_plus.Assign(1); break; case NPadControllerType::Pokeball: - controller.joy_styles.pokeball.Assign(1); - controller.device_type.pokeball.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.style_set.palma.Assign(1); + controller.device_type.palma.Assign(1); + controller.assignment_mode = NpadAssignments::Single; break; } - controller.single_color_error = ColorReadError::ReadOk; - controller.single_color.body_color = 0; - controller.single_color.button_color = 0; - - controller.dual_color_error = ColorReadError::ReadOk; - controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left; - controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left; - controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right; - controller.right_color.button_color = - Settings::values.players[controller_idx].button_color_right; - - controller.battery_level[0] = BATTERY_FULL; - controller.battery_level[1] = BATTERY_FULL; - controller.battery_level[2] = BATTERY_FULL; - styleset_changed_events[controller_idx].writable->Signal(); + controller.fullkey_color.attribute = ColorAttributes::Ok; + controller.fullkey_color.fullkey.body = 0; + controller.fullkey_color.fullkey.button = 0; + + controller.joycon_color.attribute = ColorAttributes::Ok; + controller.joycon_color.left.body = + Settings::values.players.GetValue()[controller_idx].body_color_left; + controller.joycon_color.left.button = + Settings::values.players.GetValue()[controller_idx].button_color_left; + controller.joycon_color.right.body = + Settings::values.players.GetValue()[controller_idx].body_color_right; + controller.joycon_color.right.button = + Settings::values.players.GetValue()[controller_idx].button_color_right; + + // TODO: Investigate when we should report all batery types + controller.battery_level_dual = BATTERY_FULL; + controller.battery_level_left = BATTERY_FULL; + controller.battery_level_right = BATTERY_FULL; + + SignalStyleSetChangedEvent(IndexToNPad(controller_idx)); } void Controller_NPad::OnInit() { auto& kernel = system.Kernel(); - for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { - styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( - kernel, fmt::format("npad:NpadStyleSetChanged_{}", i)); + for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) { + styleset_changed_events[i] = + Kernel::KEvent::Create(kernel, fmt::format("npad:NpadStyleSetChanged_{}", i)); + styleset_changed_events[i]->Initialize(); } if (!IsControllerActivated()) { return; } + OnLoadInputDevices(); + if (style.raw == 0) { // We want to support all controllers style.handheld.Assign(1); style.joycon_left.Assign(1); style.joycon_right.Assign(1); style.joycon_dual.Assign(1); - style.pro_controller.Assign(1); - style.pokeball.Assign(1); + style.fullkey.Assign(1); + style.gamecube.Assign(1); + style.palma.Assign(1); } - std::transform(Settings::values.players.begin(), Settings::values.players.end(), - connected_controllers.begin(), [](const Settings::PlayerInput& player) { + std::transform(Settings::values.players.GetValue().begin(), + Settings::values.players.GetValue().end(), connected_controllers.begin(), + [](const Settings::PlayerInput& player) { return ControllerHolder{MapSettingsTypeToNPad(player.controller_type), player.connected}; }); + // Connect the Player 1 or Handheld controller if none are connected. + if (std::none_of(connected_controllers.begin(), connected_controllers.end(), + [](const ControllerHolder& controller) { return controller.is_connected; })) { + const auto controller = + MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type); + if (controller == NPadControllerType::Handheld) { + Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; + connected_controllers[HANDHELD_INDEX] = {controller, true}; + } else { + Settings::values.players.GetValue()[0].connected = true; + connected_controllers[0] = {controller, true}; + } + } + // Account for handheld if (connected_controllers[HANDHELD_INDEX].is_connected) { connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld; @@ -241,7 +314,7 @@ void Controller_NPad::OnInit() { } void Controller_NPad::OnLoadInputDevices() { - const auto& players = Settings::values.players; + const auto& players = Settings::values.players.GetValue(); for (std::size_t i = 0; i < players.size(); ++i) { std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, @@ -249,20 +322,37 @@ void Controller_NPad::OnLoadInputDevices() { std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); + std::transform(players[i].vibrations.begin() + + Settings::NativeVibration::VIBRATION_HID_BEGIN, + players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END, + vibrations[i].begin(), Input::CreateDevice<Input::VibrationDevice>); + std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, + players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, + motions[i].begin(), Input::CreateDevice<Input::MotionDevice>); + for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) { + InitializeVibrationDeviceAtIndex(i, device_idx); + } } } -void Controller_NPad::OnRelease() {} +void Controller_NPad::OnRelease() { + for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) { + VibrateControllerAtIndex(npad_idx, device_idx, {}); + } + } +} void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { const auto controller_idx = NPadIdToIndex(npad_id); - [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type; + const auto controller_type = connected_controllers[controller_idx].type; if (!connected_controllers[controller_idx].is_connected) { return; } auto& pad_state = npad_pad_states[controller_idx].pad_states; auto& lstick_entry = npad_pad_states[controller_idx].l_stick; auto& rstick_entry = npad_pad_states[controller_idx].r_stick; + auto& trigger_entry = npad_trigger_states[controller_idx]; const auto& button_state = buttons[controller_idx]; const auto& analog_state = sticks[controller_idx]; const auto [stick_l_x_f, stick_l_y_f] = @@ -271,54 +361,74 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); using namespace Settings::NativeButton; - pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.l_stick_right.Assign( - analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::RIGHT)); - pad_state.l_stick_left.Assign( - analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::LEFT)); - pad_state.l_stick_up.Assign( - analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::UP)); - pad_state.l_stick_down.Assign( - analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::DOWN)); - - pad_state.r_stick_right.Assign( - analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); - pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); - pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); - pad_state.r_stick_down.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); - - pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); - - lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); - lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); - rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); - rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + if (controller_type != NPadControllerType::JoyLeft) { + pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.r_stick_right.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); + pad_state.r_stick_left.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); + pad_state.r_stick_up.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); + pad_state.r_stick_down.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); + rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + } + + if (controller_type != NPadControllerType::JoyRight) { + pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.l_stick_right.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); + pad_state.l_stick_left.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); + pad_state.l_stick_up.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); + pad_state.l_stick_down.Assign( + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); + lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + } + + if (controller_type == NPadControllerType::JoyLeft || + controller_type == NPadControllerType::JoyRight) { + pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + } + + if (controller_type == NPadControllerType::GameCube) { + trigger_entry.l_analog = static_cast<s32>( + button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); + trigger_entry.r_analog = static_cast<s32>( + button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); + pad_state.zl.Assign(false); + pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + } } void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, @@ -326,15 +436,17 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* if (!IsControllerActivated()) { return; } - for (std::size_t i = 0; i < shared_memory_entries.size(); i++) { + for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { auto& npad = shared_memory_entries[i]; - const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states, - &npad.handheld_states, - &npad.dual_states, - &npad.left_joy_states, - &npad.right_joy_states, - &npad.pokeball_states, - &npad.libnx}; + const std::array<NPadGeneric*, 7> controller_npads{ + &npad.fullkey_states, &npad.handheld_states, &npad.joy_dual_states, + &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states, + &npad.system_ext_states}; + + // There is the posibility to have more controllers with analog triggers + const std::array<TriggerGeneric*, 1> controller_triggers{ + &npad.gc_trigger_states, + }; for (auto* main_controller : controller_npads) { main_controller->common.entry_count = 16; @@ -353,29 +465,48 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* cur_entry.timestamp2 = cur_entry.timestamp; } + for (auto* analog_trigger : controller_triggers) { + analog_trigger->entry_count = 16; + analog_trigger->total_entry_count = 17; + + const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; + + analog_trigger->timestamp = core_timing.GetCPUTicks(); + analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17; + + auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; + + cur_entry.timestamp = last_entry.timestamp + 1; + cur_entry.timestamp2 = cur_entry.timestamp; + } + const auto& controller_type = connected_controllers[i].type; if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { continue; } const u32 npad_index = static_cast<u32>(i); + RequestPadStateUpdate(npad_index); auto& pad_state = npad_pad_states[npad_index]; + auto& trigger_state = npad_trigger_states[npad_index]; auto& main_controller = - npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; + npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index]; auto& handheld_entry = npad.handheld_states.npad[npad.handheld_states.common.last_entry_index]; - auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index]; - auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index]; + auto& dual_entry = npad.joy_dual_states.npad[npad.joy_dual_states.common.last_entry_index]; + auto& left_entry = npad.joy_left_states.npad[npad.joy_left_states.common.last_entry_index]; auto& right_entry = - npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index]; - auto& pokeball_entry = - npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index]; - auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; + npad.joy_right_states.npad[npad.joy_right_states.common.last_entry_index]; + auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index]; + auto& libnx_entry = + npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; + auto& trigger_entry = + npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index]; libnx_entry.connection_status.raw = 0; - libnx_entry.connection_status.IsConnected.Assign(1); + libnx_entry.connection_status.is_connected.Assign(1); switch (controller_type) { case NPadControllerType::None: @@ -383,67 +514,79 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* break; case NPadControllerType::ProController: main_controller.connection_status.raw = 0; - main_controller.connection_status.IsConnected.Assign(1); - main_controller.connection_status.IsWired.Assign(1); + main_controller.connection_status.is_connected.Assign(1); + main_controller.connection_status.is_wired.Assign(1); main_controller.pad.pad_states.raw = pad_state.pad_states.raw; main_controller.pad.l_stick = pad_state.l_stick; main_controller.pad.r_stick = pad_state.r_stick; - libnx_entry.connection_status.IsWired.Assign(1); + libnx_entry.connection_status.is_wired.Assign(1); break; case NPadControllerType::Handheld: handheld_entry.connection_status.raw = 0; - handheld_entry.connection_status.IsConnected.Assign(1); - handheld_entry.connection_status.IsWired.Assign(1); - handheld_entry.connection_status.IsLeftJoyConnected.Assign(1); - handheld_entry.connection_status.IsRightJoyConnected.Assign(1); - handheld_entry.connection_status.IsLeftJoyWired.Assign(1); - handheld_entry.connection_status.IsRightJoyWired.Assign(1); + handheld_entry.connection_status.is_connected.Assign(1); + handheld_entry.connection_status.is_wired.Assign(1); + handheld_entry.connection_status.is_left_connected.Assign(1); + handheld_entry.connection_status.is_right_connected.Assign(1); + handheld_entry.connection_status.is_left_wired.Assign(1); + handheld_entry.connection_status.is_right_wired.Assign(1); handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; handheld_entry.pad.l_stick = pad_state.l_stick; handheld_entry.pad.r_stick = pad_state.r_stick; - libnx_entry.connection_status.IsWired.Assign(1); - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); - libnx_entry.connection_status.IsLeftJoyWired.Assign(1); - libnx_entry.connection_status.IsRightJoyWired.Assign(1); + libnx_entry.connection_status.is_wired.Assign(1); + libnx_entry.connection_status.is_left_connected.Assign(1); + libnx_entry.connection_status.is_right_connected.Assign(1); + libnx_entry.connection_status.is_left_wired.Assign(1); + libnx_entry.connection_status.is_right_wired.Assign(1); break; case NPadControllerType::JoyDual: dual_entry.connection_status.raw = 0; - dual_entry.connection_status.IsConnected.Assign(1); - dual_entry.connection_status.IsLeftJoyConnected.Assign(1); - dual_entry.connection_status.IsRightJoyConnected.Assign(1); + dual_entry.connection_status.is_connected.Assign(1); + dual_entry.connection_status.is_left_connected.Assign(1); + dual_entry.connection_status.is_right_connected.Assign(1); dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; dual_entry.pad.l_stick = pad_state.l_stick; dual_entry.pad.r_stick = pad_state.r_stick; - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + libnx_entry.connection_status.is_left_connected.Assign(1); + libnx_entry.connection_status.is_right_connected.Assign(1); break; case NPadControllerType::JoyLeft: left_entry.connection_status.raw = 0; - left_entry.connection_status.IsConnected.Assign(1); - left_entry.connection_status.IsLeftJoyConnected.Assign(1); + left_entry.connection_status.is_connected.Assign(1); + left_entry.connection_status.is_left_connected.Assign(1); left_entry.pad.pad_states.raw = pad_state.pad_states.raw; left_entry.pad.l_stick = pad_state.l_stick; left_entry.pad.r_stick = pad_state.r_stick; - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + libnx_entry.connection_status.is_left_connected.Assign(1); break; case NPadControllerType::JoyRight: right_entry.connection_status.raw = 0; - right_entry.connection_status.IsConnected.Assign(1); - right_entry.connection_status.IsRightJoyConnected.Assign(1); + right_entry.connection_status.is_connected.Assign(1); + right_entry.connection_status.is_right_connected.Assign(1); right_entry.pad.pad_states.raw = pad_state.pad_states.raw; right_entry.pad.l_stick = pad_state.l_stick; right_entry.pad.r_stick = pad_state.r_stick; - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + libnx_entry.connection_status.is_right_connected.Assign(1); + break; + case NPadControllerType::GameCube: + main_controller.connection_status.raw = 0; + main_controller.connection_status.is_connected.Assign(1); + main_controller.connection_status.is_wired.Assign(1); + main_controller.pad.pad_states.raw = pad_state.pad_states.raw; + main_controller.pad.l_stick = pad_state.l_stick; + main_controller.pad.r_stick = pad_state.r_stick; + trigger_entry.l_analog = trigger_state.l_analog; + trigger_entry.r_analog = trigger_state.r_analog; + + libnx_entry.connection_status.is_wired.Assign(1); break; case NPadControllerType::Pokeball: pokeball_entry.connection_status.raw = 0; - pokeball_entry.connection_status.IsConnected.Assign(1); + pokeball_entry.connection_status.is_connected.Assign(1); pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; pokeball_entry.pad.l_stick = pad_state.l_stick; pokeball_entry.pad.r_stick = pad_state.r_stick; @@ -462,15 +605,153 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* shared_memory_entries.size() * sizeof(NPadEntry)); } -void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { +void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t data_len) { + if (!IsControllerActivated()) { + return; + } + for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { + auto& npad = shared_memory_entries[i]; + + const auto& controller_type = connected_controllers[i].type; + + if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { + continue; + } + + const std::array<SixAxisGeneric*, 6> controller_sixaxes{ + &npad.sixaxis_fullkey, &npad.sixaxis_handheld, &npad.sixaxis_dual_left, + &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right, + }; + + for (auto* sixaxis_sensor : controller_sixaxes) { + sixaxis_sensor->common.entry_count = 16; + sixaxis_sensor->common.total_entry_count = 17; + + const auto& last_entry = + sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; + + sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks(); + sixaxis_sensor->common.last_entry_index = + (sixaxis_sensor->common.last_entry_index + 1) % 17; + + auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index]; + + cur_entry.timestamp = last_entry.timestamp + 1; + cur_entry.timestamp2 = cur_entry.timestamp; + } + + // Try to read sixaxis sensor states + std::array<MotionDevice, 2> motion_devices; + + if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) { + sixaxis_at_rest = true; + for (std::size_t e = 0; e < motion_devices.size(); ++e) { + const auto& device = motions[i][e]; + if (device) { + std::tie(motion_devices[e].accel, motion_devices[e].gyro, + motion_devices[e].rotation, motion_devices[e].orientation) = + device->GetStatus(); + sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f; + } + } + } + + auto& full_sixaxis_entry = + npad.sixaxis_fullkey.sixaxis[npad.sixaxis_fullkey.common.last_entry_index]; + auto& handheld_sixaxis_entry = + npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index]; + auto& dual_left_sixaxis_entry = + npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index]; + auto& dual_right_sixaxis_entry = + npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index]; + auto& left_sixaxis_entry = + npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index]; + auto& right_sixaxis_entry = + npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index]; + + switch (controller_type) { + case NPadControllerType::None: + UNREACHABLE(); + break; + case NPadControllerType::ProController: + full_sixaxis_entry.attribute.raw = 0; + if (sixaxis_sensors_enabled && motions[i][0]) { + full_sixaxis_entry.attribute.is_connected.Assign(1); + full_sixaxis_entry.accel = motion_devices[0].accel; + full_sixaxis_entry.gyro = motion_devices[0].gyro; + full_sixaxis_entry.rotation = motion_devices[0].rotation; + full_sixaxis_entry.orientation = motion_devices[0].orientation; + } + break; + case NPadControllerType::Handheld: + handheld_sixaxis_entry.attribute.raw = 0; + if (sixaxis_sensors_enabled && motions[i][0]) { + handheld_sixaxis_entry.attribute.is_connected.Assign(1); + handheld_sixaxis_entry.accel = motion_devices[0].accel; + handheld_sixaxis_entry.gyro = motion_devices[0].gyro; + handheld_sixaxis_entry.rotation = motion_devices[0].rotation; + handheld_sixaxis_entry.orientation = motion_devices[0].orientation; + } + break; + case NPadControllerType::JoyDual: + dual_left_sixaxis_entry.attribute.raw = 0; + dual_right_sixaxis_entry.attribute.raw = 0; + if (sixaxis_sensors_enabled && motions[i][0]) { + // Set motion for the left joycon + dual_left_sixaxis_entry.attribute.is_connected.Assign(1); + dual_left_sixaxis_entry.accel = motion_devices[0].accel; + dual_left_sixaxis_entry.gyro = motion_devices[0].gyro; + dual_left_sixaxis_entry.rotation = motion_devices[0].rotation; + dual_left_sixaxis_entry.orientation = motion_devices[0].orientation; + } + if (sixaxis_sensors_enabled && motions[i][1]) { + // Set motion for the right joycon + dual_right_sixaxis_entry.attribute.is_connected.Assign(1); + dual_right_sixaxis_entry.accel = motion_devices[1].accel; + dual_right_sixaxis_entry.gyro = motion_devices[1].gyro; + dual_right_sixaxis_entry.rotation = motion_devices[1].rotation; + dual_right_sixaxis_entry.orientation = motion_devices[1].orientation; + } + break; + case NPadControllerType::JoyLeft: + left_sixaxis_entry.attribute.raw = 0; + if (sixaxis_sensors_enabled && motions[i][0]) { + left_sixaxis_entry.attribute.is_connected.Assign(1); + left_sixaxis_entry.accel = motion_devices[0].accel; + left_sixaxis_entry.gyro = motion_devices[0].gyro; + left_sixaxis_entry.rotation = motion_devices[0].rotation; + left_sixaxis_entry.orientation = motion_devices[0].orientation; + } + break; + case NPadControllerType::JoyRight: + right_sixaxis_entry.attribute.raw = 0; + if (sixaxis_sensors_enabled && motions[i][1]) { + right_sixaxis_entry.attribute.is_connected.Assign(1); + right_sixaxis_entry.accel = motion_devices[1].accel; + right_sixaxis_entry.gyro = motion_devices[1].gyro; + right_sixaxis_entry.rotation = motion_devices[1].rotation; + right_sixaxis_entry.orientation = motion_devices[1].orientation; + } + break; + case NPadControllerType::GameCube: + case NPadControllerType::Pokeball: + break; + } + } + std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), + shared_memory_entries.size() * sizeof(NPadEntry)); +} + +void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) { style.raw = style_set.raw; } -Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const { +Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const { return style; } -void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { +void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) { ASSERT(length > 0 && (length % sizeof(u32)) == 0); supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); @@ -482,7 +763,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size()); } -std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { +std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const { return supported_npad_id_types.size(); } @@ -494,37 +775,190 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { return hold_type; } -void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { +void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) { + handheld_activation_mode = activation_mode; +} + +Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const { + return handheld_activation_mode; +} + +void Controller_NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) { + communication_mode = communication_mode_; +} + +Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode() const { + return communication_mode; +} + +void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) { const std::size_t npad_index = NPadIdToIndex(npad_id); ASSERT(npad_index < shared_memory_entries.size()); - if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { - shared_memory_entries[npad_index].pad_assignment = assignment_mode; + if (shared_memory_entries[npad_index].assignment_mode != assignment_mode) { + shared_memory_entries[npad_index].assignment_mode = assignment_mode; + } +} + +bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value) { + if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) { + return false; + } + + const auto& player = Settings::values.players.GetValue()[npad_index]; + + if (!player.vibration_enabled) { + if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || + latest_vibration_values[npad_index][device_index].amp_high != 0.0f) { + // Send an empty vibration to stop any vibrations. + vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f); + // Then reset the vibration value to its default value. + latest_vibration_values[npad_index][device_index] = DEFAULT_VIBRATION_VALUE; + } + + return false; + } + + if (!Settings::values.enable_accurate_vibrations.GetValue()) { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + const auto now = steady_clock::now(); + + // Filter out non-zero vibrations that are within 10ms of each other. + if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) && + duration_cast<milliseconds>(now - last_vibration_timepoints[npad_index][device_index]) < + milliseconds(10)) { + return false; + } + + last_vibration_timepoints[npad_index][device_index] = now; } + + auto& vibration = vibrations[npad_index][device_index]; + const auto player_vibration_strength = static_cast<f32>(player.vibration_strength); + const auto amp_low = + std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f); + const auto amp_high = + std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f); + return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high, + vibration_value.freq_high); } -void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, - const std::vector<Vibration>& vibrations) { - LOG_DEBUG(Service_HID, "(STUBBED) called"); +void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value) { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return; + } - if (!Settings::values.vibration_enabled || !can_controllers_vibrate) { + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { return; } - for (std::size_t i = 0; i < controller_ids.size(); i++) { - std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i)); - if (connected_controllers[controller_pos].is_connected) { - // TODO(ogniK): Vibrate the physical controller - } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + + if (!vibration_devices_mounted[npad_index][device_index] || + !connected_controllers[npad_index].is_connected) { + return; } - last_processed_vibration = vibrations.back(); + + if (vibration_device_handle.device_index == DeviceIndex::None) { + UNREACHABLE_MSG("DeviceIndex should never be None!"); + return; + } + + // Some games try to send mismatched parameters in the device handle, block these. + if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && + (vibration_device_handle.npad_type == NpadType::JoyconRight || + vibration_device_handle.device_index == DeviceIndex::Right)) || + (connected_controllers[npad_index].type == NPadControllerType::JoyRight && + (vibration_device_handle.npad_type == NpadType::JoyconLeft || + vibration_device_handle.device_index == DeviceIndex::Left))) { + return; + } + + // Filter out vibrations with equivalent values to reduce unnecessary state changes. + if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low && + vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) { + return; + } + + if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) { + latest_vibration_values[npad_index][device_index] = vibration_value; + } +} + +void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles, + const std::vector<VibrationValue>& vibration_values) { + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { + return; + } + + ASSERT_OR_EXECUTE_MSG( + vibration_device_handles.size() == vibration_values.size(), { return; }, + "The amount of device handles does not match with the amount of vibration values," + "this is undefined behavior!"); + + for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { + VibrateController(vibration_device_handles[i], vibration_values[i]); + } +} + +Controller_NPad::VibrationValue Controller_NPad::GetLastVibration( + const DeviceHandle& vibration_device_handle) const { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return {}; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + return latest_vibration_values[npad_index][device_index]; +} + +void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + InitializeVibrationDeviceAtIndex(npad_index, device_index); +} + +void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index, + std::size_t device_index) { + if (vibrations[npad_index][device_index]) { + vibration_devices_mounted[npad_index][device_index] = + vibrations[npad_index][device_index]->GetStatus() == 1; + } else { + vibration_devices_mounted[npad_index][device_index] = false; + } +} + +void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) { + permit_vibration_session_enabled = permit_vibration_session; } -std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { +bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return false; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index); + return vibration_devices_mounted[npad_index][device_index]; +} + +std::shared_ptr<Kernel::KReadableEvent> Controller_NPad::GetStyleSetChangedEvent( + u32 npad_id) const { const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; - return styleset_event.readable; + return styleset_event->GetReadableEvent(); } -Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { - return last_processed_vibration; +void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const { + styleset_changed_events[NPadIdToIndex(npad_id)]->GetWritableEvent()->Signal(); } void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { @@ -534,36 +968,54 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected) { if (!connected) { - DisconnectNPad(IndexToNPad(npad_index)); + DisconnectNpadAtIndex(npad_index); return; } - if (controller == NPadControllerType::Handheld) { - Settings::values.players[HANDHELD_INDEX].controller_type = + if (controller == NPadControllerType::Handheld && npad_index == HANDHELD_INDEX) { + Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type = MapNPadToSettingsType(controller); - Settings::values.players[HANDHELD_INDEX].connected = true; + Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; connected_controllers[HANDHELD_INDEX] = {controller, true}; InitNewlyAddedController(HANDHELD_INDEX); return; } - Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller); - Settings::values.players[npad_index].connected = true; + Settings::values.players.GetValue()[npad_index].controller_type = + MapNPadToSettingsType(controller); + Settings::values.players.GetValue()[npad_index].connected = true; connected_controllers[npad_index] = {controller, true}; InitNewlyAddedController(npad_index); } -void Controller_NPad::DisconnectNPad(u32 npad_id) { - const auto npad_index = NPadIdToIndex(npad_id); +void Controller_NPad::DisconnectNpad(u32 npad_id) { + DisconnectNpadAtIndex(NPadIdToIndex(npad_id)); +} + +void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) { + // Send an empty vibration to stop any vibrations. + VibrateControllerAtIndex(npad_index, device_idx, {}); + vibration_devices_mounted[npad_index][device_idx] = false; + } + + Settings::values.players.GetValue()[npad_index].connected = false; connected_controllers[npad_index].is_connected = false; - Settings::values.players[npad_index].connected = false; auto& controller = shared_memory_entries[npad_index]; - controller.joy_styles.raw = 0; // Zero out + controller.style_set.raw = 0; // Zero out controller.device_type.raw = 0; - controller.properties.raw = 0; - - styleset_changed_events[npad_index].writable->Signal(); + controller.system_properties.raw = 0; + controller.button_properties.raw = 0; + controller.battery_level_dual = 0; + controller.battery_level_left = 0; + controller.battery_level_right = 0; + controller.fullkey_color = {}; + controller.joycon_color = {}; + controller.assignment_mode = NpadAssignments::Dual; + controller.footer_type = AppletFooterUiType::None; + + SignalStyleSetChangedEvent(IndexToNPad(npad_index)); } void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { @@ -574,6 +1026,47 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo return gyroscope_zero_drift_mode; } +bool Controller_NPad::IsSixAxisSensorAtRest() const { + return sixaxis_at_rest; +} + +void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) { + sixaxis_sensors_enabled = six_axis_status; +} + +void Controller_NPad::SetSixAxisFusionParameters(f32 parameter1, f32 parameter2) { + sixaxis_fusion_parameter1 = parameter1; + sixaxis_fusion_parameter2 = parameter2; +} + +std::pair<f32, f32> Controller_NPad::GetSixAxisFusionParameters() { + return { + sixaxis_fusion_parameter1, + sixaxis_fusion_parameter2, + }; +} + +void Controller_NPad::ResetSixAxisFusionParameters() { + sixaxis_fusion_parameter1 = 0.0f; + sixaxis_fusion_parameter2 = 0.0f; +} + +void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { + const auto npad_index_1 = NPadIdToIndex(npad_id_1); + const auto npad_index_2 = NPadIdToIndex(npad_id_2); + + // If the controllers at both npad indices form a pair of left and right joycons, merge them. + // Otherwise, do nothing. + if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft && + connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) || + (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft && + connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) { + // Disconnect the joycon at the second id and connect the dual joycon at the first index. + DisconnectNpad(npad_id_2); + AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1); + } +} + void Controller_NPad::StartLRAssignmentMode() { // Nothing internally is used for lr assignment mode. Since we have the ability to set the // controller types from boot, it doesn't really matter about showing a selection screen @@ -632,12 +1125,13 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { } } -void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { - can_controllers_vibrate = can_vibrate; +bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const { + return unintended_home_button_input_protection[NPadIdToIndex(npad_id)]; } -bool Controller_NPad::IsVibrationEnabled() const { - return can_controllers_vibrate; +void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, + u32 npad_id) { + unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled; } void Controller_NPad::ClearAllConnectedControllers() { @@ -650,13 +1144,13 @@ void Controller_NPad::ClearAllConnectedControllers() { } void Controller_NPad::DisconnectAllConnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.is_connected = false; } } void Controller_NPad::ConnectAllDisconnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { if (controller.type != NPadControllerType::None && !controller.is_connected) { controller.is_connected = true; } @@ -664,14 +1158,14 @@ void Controller_NPad::ConnectAllDisconnectedControllers() { } void Controller_NPad::ClearAllControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.type = NPadControllerType::None; controller.is_connected = false; } } u32 Controller_NPad::GetAndResetPressState() { - return std::exchange(press_state, 0); + return press_state.exchange(0); } bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { @@ -684,7 +1178,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const return false; } // Handheld should not be supported in docked mode - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { return false; } @@ -695,15 +1189,17 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const [](u32 npad_id) { return npad_id <= MAX_NPAD_ID; })) { switch (controller) { case NPadControllerType::ProController: - return style.pro_controller; + return style.fullkey; case NPadControllerType::JoyDual: return style.joycon_dual; case NPadControllerType::JoyLeft: return style.joycon_left; case NPadControllerType::JoyRight: return style.joycon_right; + case NPadControllerType::GameCube: + return style.gamecube; case NPadControllerType::Pokeball: - return style.pokeball; + return style.palma; default: return false; } @@ -712,92 +1208,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const return false; } -Controller_NPad::NPadControllerType Controller_NPad::DecideBestController( - NPadControllerType priority) const { - if (IsControllerSupported(priority)) { - return priority; - } - const auto is_docked = Settings::values.use_docked_mode; - if (is_docked && priority == NPadControllerType::Handheld) { - priority = NPadControllerType::JoyDual; - if (IsControllerSupported(priority)) { - return priority; - } - } - std::vector<NPadControllerType> priority_list; - switch (priority) { - case NPadControllerType::ProController: - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::Handheld: - priority_list.push_back(NPadControllerType::JoyDual); - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyDual: - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyLeft: - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyRight: - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::Pokeball: - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - break; - default: - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - break; - } - - const auto iter = std::find_if(priority_list.begin(), priority_list.end(), - [this](auto type) { return IsControllerSupported(type); }); - if (iter == priority_list.end()) { - UNIMPLEMENTED_MSG("Could not find supported controller!"); - return priority; - } - - return *iter; -} - } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 75ce5b731..bc2e6779d 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -5,14 +5,19 @@ #pragma once #include <array> +#include <atomic> #include "common/bit_field.h" #include "common/common_types.h" #include "core/frontend/input.h" #include "core/hle/kernel/object.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/controllers/controller_base.h" #include "core/settings.h" +namespace Kernel { +class KEvent; +class KReadableEvent; +} // namespace Kernel + namespace Service::HID { constexpr u32 NPAD_HANDHELD = 32; @@ -32,31 +37,41 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; + // When the controller is requesting a motion update for the shared memory + void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t size) override; + // Called when input devices should be loaded void OnLoadInputDevices() override; - struct NPadType { - union { - u32_le raw{}; - - BitField<0, 1, u32> pro_controller; - BitField<1, 1, u32> handheld; - BitField<2, 1, u32> joycon_dual; - BitField<3, 1, u32> joycon_left; - BitField<4, 1, u32> joycon_right; + enum class NPadControllerType { + None, + ProController, + Handheld, + JoyDual, + JoyLeft, + JoyRight, + GameCube, + Pokeball, + }; - BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible - }; + enum class NpadType : u8 { + ProController = 3, + Handheld = 4, + JoyconDual = 5, + JoyconLeft = 6, + JoyconRight = 7, + GameCube = 8, + Pokeball = 9, + MaxNpadType = 10, }; - static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size"); - struct Vibration { - f32 amp_low; - f32 freq_low; - f32 amp_high; - f32 freq_high; + enum class DeviceIndex : u8 { + Left = 0, + Right = 1, + None = 2, + MaxDeviceIndex = 3, }; - static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size"); enum class GyroscopeZeroDriftMode : u32 { Loose = 0, @@ -69,19 +84,65 @@ public: Horizontal = 1, }; - enum class NPadAssignments : u32_le { + enum class NpadAssignments : u32 { Dual = 0, Single = 1, }; - enum class NPadControllerType { - None, - ProController, - Handheld, - JoyDual, - JoyLeft, - JoyRight, - Pokeball, + enum class NpadHandheldActivationMode : u64 { + Dual = 0, + Single = 1, + None = 2, + }; + + enum class NpadCommunicationMode : u64 { + Mode_5ms = 0, + Mode_10ms = 1, + Mode_15ms = 2, + Default = 3, + }; + + struct DeviceHandle { + NpadType npad_type; + u8 npad_id; + DeviceIndex device_index; + INSERT_PADDING_BYTES_NOINIT(1); + }; + static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size"); + + struct NpadStyleSet { + union { + u32_le raw{}; + + BitField<0, 1, u32> fullkey; + BitField<1, 1, u32> handheld; + BitField<2, 1, u32> joycon_dual; + BitField<3, 1, u32> joycon_left; + BitField<4, 1, u32> joycon_right; + BitField<5, 1, u32> gamecube; + BitField<6, 1, u32> palma; + BitField<7, 1, u32> lark; + BitField<8, 1, u32> handheld_lark; + BitField<9, 1, u32> lucia; + BitField<29, 1, u32> system_ext; + BitField<30, 1, u32> system; + }; + }; + static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size"); + + struct VibrationValue { + f32 amp_low; + f32 freq_low; + f32 amp_high; + f32 freq_high; + }; + static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size"); + + static constexpr VibrationValue DEFAULT_VIBRATION_VALUE{ + .amp_low = 0.0f, + .freq_low = 160.0f, + .amp_high = 0.0f, + .freq_high = 320.0f, }; struct LedPattern { @@ -100,40 +161,70 @@ public: }; }; - void SetSupportedStyleSet(NPadType style_set); - NPadType GetSupportedStyleSet() const; + void SetSupportedStyleSet(NpadStyleSet style_set); + NpadStyleSet GetSupportedStyleSet() const; - void SetSupportedNPadIdTypes(u8* data, std::size_t length); + void SetSupportedNpadIdTypes(u8* data, std::size_t length); void GetSupportedNpadIdTypes(u32* data, std::size_t max_length); - std::size_t GetSupportedNPadIdTypesSize() const; + std::size_t GetSupportedNpadIdTypesSize() const; void SetHoldType(NpadHoldType joy_hold_type); NpadHoldType GetHoldType() const; - void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode); + void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode); + NpadHandheldActivationMode GetNpadHandheldActivationMode() const; + + void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_); + NpadCommunicationMode GetNpadCommunicationMode() const; + + void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode); + + bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value); + + void VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value); + + void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles, + const std::vector<VibrationValue>& vibration_values); + + VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const; + + void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle); + + void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index); - void VibrateController(const std::vector<u32>& controller_ids, - const std::vector<Vibration>& vibrations); + void SetPermitVibrationSession(bool permit_vibration_session); - std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; - Vibration GetLastVibration() const; + bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const; + + std::shared_ptr<Kernel::KReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; + void SignalStyleSetChangedEvent(u32 npad_id) const; // Adds a new controller at an index. void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index); // Adds a new controller at an index with connection status. void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); - void DisconnectNPad(u32 npad_id); + void DisconnectNpad(u32 npad_id); + void DisconnectNpadAtIndex(std::size_t index); + void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; + bool IsSixAxisSensorAtRest() const; + void SetSixAxisEnabled(bool six_axis_status); + void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2); + std::pair<f32, f32> GetSixAxisFusionParameters(); + void ResetSixAxisFusionParameters(); LedPattern GetLedPattern(u32 npad_id); - void SetVibrationEnabled(bool can_vibrate); - bool IsVibrationEnabled() const; + bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const; + void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id); void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); void ClearAllControllers(); + void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2); void StartLRAssignmentMode(); void StopLRAssignmentMode(); bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2); @@ -146,6 +237,8 @@ public: static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type); static std::size_t NPadIdToIndex(u32 npad_id); static u32 IndexToNPad(std::size_t index); + static bool IsNpadIdValid(u32 npad_id); + static bool IsDeviceHandleValid(const DeviceHandle& device_handle); private: struct CommonHeader { @@ -156,12 +249,32 @@ private: }; static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); + enum class ColorAttributes : u32_le { + Ok = 0, + ReadError = 1, + NoController = 2, + }; + static_assert(sizeof(ColorAttributes) == 4, "ColorAttributes is an invalid size"); + struct ControllerColor { - u32_le body_color; - u32_le button_color; + u32_le body; + u32_le button; }; static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size"); + struct FullKeyColor { + ColorAttributes attribute; + ControllerColor fullkey; + }; + static_assert(sizeof(FullKeyColor) == 0xC, "FullKeyColor is an invalid size"); + + struct JoyconColor { + ColorAttributes attribute; + ControllerColor left; + ControllerColor right; + }; + static_assert(sizeof(JoyconColor) == 0x14, "JoyconColor is an invalid size"); + struct ControllerPadState { union { u64_le raw{}; @@ -203,6 +316,9 @@ private: BitField<26, 1, u64> right_sl; BitField<27, 1, u64> right_sr; + + BitField<28, 1, u64> palma; + BitField<30, 1, u64> handheld_left_b; }; }; static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); @@ -216,12 +332,12 @@ private: struct ConnectionState { union { u32_le raw{}; - BitField<0, 1, u32> IsConnected; - BitField<1, 1, u32> IsWired; - BitField<2, 1, u32> IsLeftJoyConnected; - BitField<3, 1, u32> IsLeftJoyWired; - BitField<4, 1, u32> IsRightJoyConnected; - BitField<5, 1, u32> IsRightJoyWired; + BitField<0, 1, u32> is_connected; + BitField<1, 1, u32> is_wired; + BitField<2, 1, u32> is_left_connected; + BitField<3, 1, u32> is_left_wired; + BitField<4, 1, u32> is_right_connected; + BitField<5, 1, u32> is_right_wired; }; }; static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); @@ -247,63 +363,176 @@ private: }; static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); - enum class ColorReadError : u32_le { - ReadOk = 0, - ColorDoesntExist = 1, - NoController = 2, + struct SixAxisAttributes { + union { + u32_le raw{}; + BitField<0, 1, u32> is_connected; + BitField<1, 1, u32> is_interpolated; + }; + }; + static_assert(sizeof(SixAxisAttributes) == 4, "SixAxisAttributes is an invalid size"); + + struct SixAxisStates { + s64_le timestamp{}; + INSERT_PADDING_WORDS(2); + s64_le timestamp2{}; + Common::Vec3f accel{}; + Common::Vec3f gyro{}; + Common::Vec3f rotation{}; + std::array<Common::Vec3f, 3> orientation{}; + SixAxisAttributes attribute; + INSERT_PADDING_BYTES(4); // Reserved + }; + static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size"); + + struct SixAxisGeneric { + CommonHeader common{}; + std::array<SixAxisStates, 17> sixaxis{}; + }; + static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); + + struct TriggerState { + s64_le timestamp{}; + s64_le timestamp2{}; + s32_le l_analog{}; + s32_le r_analog{}; + }; + static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size"); + + struct TriggerGeneric { + INSERT_PADDING_BYTES(0x4); + s64_le timestamp; + INSERT_PADDING_BYTES(0x4); + s64_le total_entry_count; + s64_le last_entry_index; + s64_le entry_count; + std::array<TriggerState, 17> trigger{}; }; + static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size"); - struct NPadProperties { + struct NPadSystemProperties { union { s64_le raw{}; + BitField<0, 1, s64> is_charging_joy_dual; + BitField<1, 1, s64> is_charging_joy_left; + BitField<2, 1, s64> is_charging_joy_right; + BitField<3, 1, s64> is_powered_joy_dual; + BitField<4, 1, s64> is_powered_joy_left; + BitField<5, 1, s64> is_powered_joy_right; + BitField<9, 1, s64> is_system_unsupported_button; + BitField<10, 1, s64> is_system_ext_unsupported_button; BitField<11, 1, s64> is_vertical; BitField<12, 1, s64> is_horizontal; BitField<13, 1, s64> use_plus; BitField<14, 1, s64> use_minus; + BitField<15, 1, s64> use_directional_buttons; }; }; + static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size"); + + struct NPadButtonProperties { + union { + s32_le raw{}; + BitField<0, 1, s32> is_home_button_protection_enabled; + }; + }; + static_assert(sizeof(NPadButtonProperties) == 0x4, "NPadButtonProperties is an invalid size"); struct NPadDevice { union { u32_le raw{}; - BitField<0, 1, s32> pro_controller; - BitField<1, 1, s32> handheld; + BitField<0, 1, s32> fullkey; + BitField<1, 1, s32> debug_pad; BitField<2, 1, s32> handheld_left; BitField<3, 1, s32> handheld_right; BitField<4, 1, s32> joycon_left; BitField<5, 1, s32> joycon_right; - BitField<6, 1, s32> pokeball; + BitField<6, 1, s32> palma; + BitField<7, 1, s32> lark_hvc_left; + BitField<8, 1, s32> lark_hvc_right; + BitField<9, 1, s32> lark_nes_left; + BitField<10, 1, s32> lark_nes_right; + BitField<11, 1, s32> handheld_lark_hvc_left; + BitField<12, 1, s32> handheld_lark_hvc_right; + BitField<13, 1, s32> handheld_lark_nes_left; + BitField<14, 1, s32> handheld_lark_nes_right; + BitField<15, 1, s32> lucia; + BitField<31, 1, s32> system; }; }; - struct NPadEntry { - NPadType joy_styles; - NPadAssignments pad_assignment; + struct MotionDevice { + Common::Vec3f accel; + Common::Vec3f gyro; + Common::Vec3f rotation; + std::array<Common::Vec3f, 3> orientation; + }; - ColorReadError single_color_error; - ControllerColor single_color; + struct NfcXcdHandle { + INSERT_PADDING_BYTES(0x60); + }; + + struct AppletFooterUiAttributes { + INSERT_PADDING_BYTES(0x4); + }; - ColorReadError dual_color_error; - ControllerColor left_color; - ControllerColor right_color; + enum class AppletFooterUiType : u8 { + None = 0, + HandheldNone = 1, + HandheldJoyConLeftOnly = 1, + HandheldJoyConRightOnly = 3, + HandheldJoyConLeftJoyConRight = 4, + JoyDual = 5, + JoyDualLeftOnly = 6, + JoyDualRightOnly = 7, + JoyLeftHorizontal = 8, + JoyLeftVertical = 9, + JoyRightHorizontal = 10, + JoyRightVertical = 11, + SwitchProController = 12, + CompatibleProController = 13, + CompatibleJoyCon = 14, + LarkHvc1 = 15, + LarkHvc2 = 16, + LarkNesLeft = 17, + LarkNesRight = 18, + Lucia = 19, + Verification = 20, + }; + + struct NPadEntry { + NpadStyleSet style_set; + NpadAssignments assignment_mode; + FullKeyColor fullkey_color; + JoyconColor joycon_color; - NPadGeneric main_controller_states; + NPadGeneric fullkey_states; NPadGeneric handheld_states; - NPadGeneric dual_states; - NPadGeneric left_joy_states; - NPadGeneric right_joy_states; - NPadGeneric pokeball_states; - NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be - // relying on this for the time being - INSERT_PADDING_BYTES( - 0x708 * - 6); // TODO(ogniK): SixAxis states, require more information before implementation + NPadGeneric joy_dual_states; + NPadGeneric joy_left_states; + NPadGeneric joy_right_states; + NPadGeneric palma_states; + NPadGeneric system_ext_states; + SixAxisGeneric sixaxis_fullkey; + SixAxisGeneric sixaxis_handheld; + SixAxisGeneric sixaxis_dual_left; + SixAxisGeneric sixaxis_dual_right; + SixAxisGeneric sixaxis_left; + SixAxisGeneric sixaxis_right; NPadDevice device_type; - NPadProperties properties; - INSERT_PADDING_WORDS(1); - std::array<u32, 3> battery_level; - INSERT_PADDING_BYTES(0x5c); - INSERT_PADDING_BYTES(0xdf8); + INSERT_PADDING_BYTES(0x4); // reserved + NPadSystemProperties system_properties; + NPadButtonProperties button_properties; + u32 battery_level_dual; + u32 battery_level_left; + u32 battery_level_right; + AppletFooterUiAttributes footer_attributes; + AppletFooterUiType footer_type; + // nfc_states needs to be checked switchbrew does not match with HW + NfcXcdHandle nfc_states; + INSERT_PADDING_BYTES(0x8); // Mutex + TriggerGeneric gc_trigger_states; + INSERT_PADDING_BYTES(0xc1f); }; static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); @@ -314,30 +543,47 @@ private: void InitNewlyAddedController(std::size_t controller_idx); bool IsControllerSupported(NPadControllerType controller) const; - NPadControllerType DecideBestController(NPadControllerType priority) const; void RequestPadStateUpdate(u32 npad_id); - u32 press_state{}; + std::atomic<u32> press_state{}; - NPadType style{}; + NpadStyleSet style{}; std::array<NPadEntry, 10> shared_memory_entries{}; - std::array< + using ButtonArray = std::array< std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, - 10> - buttons; - std::array< + 10>; + using StickArray = std::array< std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, - 10> - sticks; + 10>; + using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>, + Settings::NativeVibration::NUM_VIBRATIONS_HID>, + 10>; + using MotionArray = std::array< + std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>, + 10>; + ButtonArray buttons; + StickArray sticks; + VibrationArray vibrations; + MotionArray motions; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; + NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; + NpadCommunicationMode communication_mode{NpadCommunicationMode::Default}; // Each controller should have their own styleset changed event - std::array<Kernel::EventPair, 10> styleset_changed_events; - Vibration last_processed_vibration{}; + std::array<std::shared_ptr<Kernel::KEvent>, 10> styleset_changed_events; + std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10> last_vibration_timepoints; + std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{}; + bool permit_vibration_session_enabled{false}; + std::array<std::array<bool, 2>, 10> vibration_devices_mounted{}; std::array<ControllerHolder, 10> connected_controllers{}; + std::array<bool, 10> unintended_home_button_input_protection{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; - bool can_controllers_vibrate{true}; + bool sixaxis_sensors_enabled{true}; + f32 sixaxis_fusion_parameter1{}; + f32 sixaxis_fusion_parameter2{}; + bool sixaxis_at_rest{true}; std::array<ControllerPad, 10> npad_pad_states{}; + std::array<TriggerState, 10> npad_trigger_states{}; bool is_in_lr_assignment_mode{false}; Core::System& system; }; diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index e326f8f5c..5219f2dad 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <cstring> #include "common/common_types.h" #include "core/core_timing.h" @@ -16,7 +17,13 @@ constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {} Controller_Touchscreen::~Controller_Touchscreen() = default; -void Controller_Touchscreen::OnInit() {} +void Controller_Touchscreen::OnInit() { + for (std::size_t id = 0; id < MAX_FINGERS; ++id) { + mouse_finger_id[id] = MAX_FINGERS; + keyboard_finger_id[id] = MAX_FINGERS; + udp_finger_id[id] = MAX_FINGERS; + } +} void Controller_Touchscreen::OnRelease() {} @@ -40,28 +47,106 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - const auto [x, y, pressed] = touch_device->GetStatus(); - auto& touch_entry = cur_entry.states[0]; - touch_entry.attribute.raw = 0; - if (pressed && Settings::values.touchscreen.enabled) { - touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); - touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); - touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; - touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; - touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; - const u64 tick = core_timing.GetCPUTicks(); - touch_entry.delta_time = tick - last_touch; - last_touch = tick; - touch_entry.finger = Settings::values.touchscreen.finger; - cur_entry.entry_count = 1; - } else { - cur_entry.entry_count = 0; + const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus(); + const Input::TouchStatus& udp_status = touch_udp_device->GetStatus(); + for (std::size_t id = 0; id < mouse_status.size(); ++id) { + mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]); + udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]); + } + + if (Settings::values.use_touch_from_button) { + const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus(); + for (std::size_t id = 0; id < mouse_status.size(); ++id) { + keyboard_finger_id[id] = + UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]); + } } + std::array<Finger, 16> active_fingers; + const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), + [](const auto& finger) { return finger.pressed; }); + const auto active_fingers_count = + static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter)); + + const u64 tick = core_timing.GetCPUTicks(); + cur_entry.entry_count = static_cast<s32_le>(active_fingers_count); + for (std::size_t id = 0; id < MAX_FINGERS; ++id) { + auto& touch_entry = cur_entry.states[id]; + if (id < active_fingers_count) { + touch_entry.x = static_cast<u16>(active_fingers[id].x * Layout::ScreenUndocked::Width); + touch_entry.y = static_cast<u16>(active_fingers[id].y * Layout::ScreenUndocked::Height); + touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; + touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; + touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; + touch_entry.delta_time = tick - active_fingers[id].last_touch; + fingers[active_fingers[id].id].last_touch = tick; + touch_entry.finger = active_fingers[id].id; + touch_entry.attribute.raw = active_fingers[id].attribute.raw; + } else { + // Clear touch entry + touch_entry.attribute.raw = 0; + touch_entry.x = 0; + touch_entry.y = 0; + touch_entry.diameter_x = 0; + touch_entry.diameter_y = 0; + touch_entry.rotation_angle = 0; + touch_entry.delta_time = 0; + touch_entry.finger = 0; + } + } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory)); } void Controller_Touchscreen::OnLoadInputDevices() { - touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); + touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window"); + touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp"); + touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button"); +} + +std::optional<std::size_t> Controller_Touchscreen::GetUnusedFingerID() const { + std::size_t first_free_id = 0; + while (first_free_id < MAX_FINGERS) { + if (!fingers[first_free_id].pressed) { + return first_free_id; + } else { + first_free_id++; + } + } + return std::nullopt; +} + +std::size_t Controller_Touchscreen::UpdateTouchInputEvent( + const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) { + const auto& [x, y, pressed] = touch_input; + if (pressed) { + Attributes attribute{}; + if (finger_id == MAX_FINGERS) { + const auto first_free_id = GetUnusedFingerID(); + if (!first_free_id) { + // Invalid finger id do nothing + return MAX_FINGERS; + } + finger_id = first_free_id.value(); + fingers[finger_id].pressed = true; + fingers[finger_id].id = static_cast<u32_le>(finger_id); + attribute.start_touch.Assign(1); + } + fingers[finger_id].x = x; + fingers[finger_id].y = y; + fingers[finger_id].attribute = attribute; + return finger_id; + } + + if (finger_id != MAX_FINGERS) { + if (!fingers[finger_id].attribute.end_touch) { + fingers[finger_id].attribute.end_touch.Assign(1); + fingers[finger_id].attribute.start_touch.Assign(0); + return finger_id; + } + fingers[finger_id].pressed = false; + } + + return MAX_FINGERS; } + } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index a1d97269e..784124e25 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -30,6 +30,18 @@ public: void OnLoadInputDevices() override; private: + static constexpr std::size_t MAX_FINGERS = 16; + + // Returns an unused finger id, if there is no fingers available std::nullopt will be returned + std::optional<std::size_t> GetUnusedFingerID() const; + + // If the touch is new it tries to assing a new finger id, if there is no fingers avaliable no + // changes will be made. Updates the coordinates if the finger id it's already set. If the touch + // ends delays the output by one frame to set the end_touch flag before finally freeing the + // finger id + std::size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input, + std::size_t finger_id); + struct Attributes { union { u32 raw{}; @@ -55,7 +67,7 @@ private: s64_le sampling_number; s64_le sampling_number2; s32_le entry_count; - std::array<TouchState, 16> states; + std::array<TouchState, MAX_FINGERS> states; }; static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size"); @@ -66,8 +78,23 @@ private: }; static_assert(sizeof(TouchScreenSharedMemory) == 0x3000, "TouchScreenSharedMemory is an invalid size"); + + struct Finger { + u64_le last_touch{}; + float x{}; + float y{}; + u32_le id{}; + bool pressed{}; + Attributes attribute; + }; + TouchScreenSharedMemory shared_memory{}; - std::unique_ptr<Input::TouchDevice> touch_device; - s64_le last_touch{}; + std::unique_ptr<Input::TouchDevice> touch_mouse_device; + std::unique_ptr<Input::TouchDevice> touch_udp_device; + std::unique_ptr<Input::TouchDevice> touch_btn_device; + std::array<std::size_t, MAX_FINGERS> mouse_finger_id; + std::array<std::size_t, MAX_FINGERS> keyboard_finger_id; + std::array<std::size_t, MAX_FINGERS> udp_finger_id; + std::array<Finger, MAX_FINGERS> fingers; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h index ad229787c..5b59961bd 100644 --- a/src/core/hle/service/hid/controllers/xpad.h +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -4,6 +4,7 @@ #pragma once +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -28,6 +29,67 @@ public: void OnLoadInputDevices() override; private: + struct Attributes { + union { + u32_le raw{}; + BitField<0, 1, u32> is_connected; + BitField<1, 1, u32> is_wired; + BitField<2, 1, u32> is_left_connected; + BitField<3, 1, u32> is_left_wired; + BitField<4, 1, u32> is_right_connected; + BitField<5, 1, u32> is_right_wired; + }; + }; + static_assert(sizeof(Attributes) == 4, "Attributes is an invalid size"); + + struct Buttons { + union { + u32_le raw{}; + // Button states + BitField<0, 1, u32> a; + BitField<1, 1, u32> b; + BitField<2, 1, u32> x; + BitField<3, 1, u32> y; + BitField<4, 1, u32> l_stick; + BitField<5, 1, u32> r_stick; + BitField<6, 1, u32> l; + BitField<7, 1, u32> r; + BitField<8, 1, u32> zl; + BitField<9, 1, u32> zr; + BitField<10, 1, u32> plus; + BitField<11, 1, u32> minus; + + // D-Pad + BitField<12, 1, u32> d_left; + BitField<13, 1, u32> d_up; + BitField<14, 1, u32> d_right; + BitField<15, 1, u32> d_down; + + // Left JoyStick + BitField<16, 1, u32> l_stick_left; + BitField<17, 1, u32> l_stick_up; + BitField<18, 1, u32> l_stick_right; + BitField<19, 1, u32> l_stick_down; + + // Right JoyStick + BitField<20, 1, u32> r_stick_left; + BitField<21, 1, u32> r_stick_up; + BitField<22, 1, u32> r_stick_right; + BitField<23, 1, u32> r_stick_down; + + // Not always active? + BitField<24, 1, u32> left_sl; + BitField<25, 1, u32> left_sr; + + BitField<26, 1, u32> right_sl; + BitField<27, 1, u32> right_sr; + + BitField<28, 1, u32> palma; + BitField<30, 1, u32> handheld_left_b; + }; + }; + static_assert(sizeof(Buttons) == 4, "Buttons is an invalid size"); + struct AnalogStick { s32_le x; s32_le y; @@ -37,10 +99,10 @@ private: struct XPadState { s64_le sampling_number; s64_le sampling_number2; - s32_le attributes; - u32_le pad_states; - AnalogStick x_stick; - AnalogStick y_stick; + Attributes attributes; + Buttons pad_states; + AnalogStick l_stick; + AnalogStick r_stick; }; static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size"); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 33416b5dd..1e2677320 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -14,10 +14,10 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/shared_memory.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/irs.h" @@ -40,11 +40,12 @@ namespace Service::HID { // Updating period for each HID device. // HID is polled every 15ms, this value was derived from // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet -constexpr auto pad_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.6Hz) +constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz) +constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz) constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; -IAppletResource::IAppletResource(Core::System& system) - : ServiceFramework("IAppletResource"), system(system) { +IAppletResource::IAppletResource(Core::System& system_) + : ServiceFramework{system_, "IAppletResource"} { static const FunctionInfo functions[] = { {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, }; @@ -58,31 +59,43 @@ IAppletResource::IAppletResource(Core::System& system) MakeController<Controller_Mouse>(HidController::Mouse); MakeController<Controller_Keyboard>(HidController::Keyboard); MakeController<Controller_XPad>(HidController::XPad); - MakeController<Controller_Stubbed>(HidController::Unknown1); - MakeController<Controller_Stubbed>(HidController::Unknown2); - MakeController<Controller_Stubbed>(HidController::Unknown3); - MakeController<Controller_Stubbed>(HidController::SixAxisSensor); + MakeController<Controller_Stubbed>(HidController::HomeButton); + MakeController<Controller_Stubbed>(HidController::SleepButton); + MakeController<Controller_Stubbed>(HidController::CaptureButton); + MakeController<Controller_Stubbed>(HidController::InputDetector); + MakeController<Controller_Stubbed>(HidController::UniquePad); MakeController<Controller_NPad>(HidController::NPad); MakeController<Controller_Gesture>(HidController::Gesture); + MakeController<Controller_Stubbed>(HidController::ConsoleSixAxisSensor); // Homebrew doesn't try to activate some controllers, so we activate them by default GetController<Controller_NPad>(HidController::NPad).ActivateController(); GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController(); - GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00); - GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00); - GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); + GetController<Controller_Stubbed>(HidController::HomeButton).SetCommonHeaderOffset(0x4C00); + GetController<Controller_Stubbed>(HidController::SleepButton).SetCommonHeaderOffset(0x4E00); + GetController<Controller_Stubbed>(HidController::CaptureButton).SetCommonHeaderOffset(0x5000); + GetController<Controller_Stubbed>(HidController::InputDetector).SetCommonHeaderOffset(0x5200); + GetController<Controller_Stubbed>(HidController::UniquePad).SetCommonHeaderOffset(0x5A00); + GetController<Controller_Stubbed>(HidController::ConsoleSixAxisSensor) + .SetCommonHeaderOffset(0x3C200); // Register update callbacks pad_update_event = Core::Timing::CreateEvent( "HID::UpdatePadCallback", [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); UpdateControllers(user_data, ns_late); }); - - // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) + motion_update_event = Core::Timing::CreateEvent( + "HID::MotionPadCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); + UpdateMotion(user_data, ns_late); + }); system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event); + system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event); ReloadInputDevices(); } @@ -97,6 +110,7 @@ void IAppletResource::DeactivateController(HidController controller) { IAppletResource ::~IAppletResource() { system.CoreTiming().UnscheduleEvent(pad_update_event, 0); + system.CoreTiming().UnscheduleEvent(motion_update_event, 0); } void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { @@ -119,25 +133,62 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data, controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); } + // If ns_late is higher than the update rate ignore the delay + if (ns_late > motion_update_ns) { + ns_late = {}; + } + core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event); } +void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + auto& core_timing = system.CoreTiming(); + + controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate( + core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); + + // If ns_late is higher than the update rate ignore the delay + if (ns_late > motion_update_ns) { + ns_late = {}; + } + + core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event); +} + class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { public: - IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") { + explicit IActiveVibrationDeviceList(Core::System& system_, + std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework{system_, "IActiveVibrationDeviceList"}, + applet_resource(applet_resource_) { + // clang-format off static const FunctionInfo functions[] = { - {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"}, + {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"}, }; + // clang-format on + RegisterHandlers(functions); } private: - void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_HID, "(STUBBED) called"); + void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + + if (applet_resource != nullptr) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .InitializeVibrationDevice(vibration_device_handle); + } + + LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", + vibration_device_handle.npad_type, vibration_device_handle.npad_id, + vibration_device_handle.device_index); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + std::shared_ptr<IAppletResource> applet_resource; }; std::shared_ptr<IAppletResource> Hid::GetAppletResource() { @@ -148,7 +199,7 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { return applet_resource; } -Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { +Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} { // clang-format off static const FunctionInfo functions[] = { {0, &Hid::CreateAppletResource, "CreateAppletResource"}, @@ -164,8 +215,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {56, nullptr, "ActivateJoyXpad"}, {58, nullptr, "GetJoyXpadLifoHandle"}, {59, nullptr, "GetJoyXpadIds"}, - {60, nullptr, "ActivateSixAxisSensor"}, - {61, nullptr, "DeactivateSixAxisSensor"}, + {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"}, + {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"}, {62, nullptr, "GetSixAxisSensorLifoHandle"}, {63, nullptr, "ActivateJoySixAxisSensor"}, {64, nullptr, "DeactivateJoySixAxisSensor"}, @@ -173,10 +224,10 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, {68, nullptr, "IsSixAxisSensorFusionEnabled"}, - {69, nullptr, "EnableSixAxisSensorFusion"}, - {70, nullptr, "SetSixAxisSensorFusionParameters"}, - {71, nullptr, "GetSixAxisSensorFusionParameters"}, - {72, nullptr, "ResetSixAxisSensorFusionParameters"}, + {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"}, + {70, &Hid::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"}, + {71, &Hid::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"}, + {72, &Hid::ResetSixAxisSensorFusionParameters, "ResetSixAxisSensorFusionParameters"}, {73, nullptr, "SetAccelerometerParameters"}, {74, nullptr, "GetAccelerometerParameters"}, {75, nullptr, "ResetAccelerometerParameters"}, @@ -209,8 +260,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, {129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"}, {130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"}, - {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, - {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, + {131, &Hid::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"}, + {132, &Hid::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"}, {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, {134, nullptr, "SetNpadAnalogStickUseCenterClamp"}, {135, nullptr, "SetNpadCaptureButtonAssignment"}, @@ -226,7 +277,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {208, nullptr, "GetActualVibrationGcErmCommand"}, {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, - {211, nullptr, "IsVibrationDeviceMounted"}, + {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, @@ -245,7 +296,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {404, nullptr, "HasLeftRightBattery"}, {405, nullptr, "GetNpadInterfaceType"}, {406, nullptr, "GetNpadLeftRightInterfaceType"}, - {407, nullptr, "GetNpadOfHighestBatteryLevelForJoyLeft"}, + {407, nullptr, "GetNpadOfHighestBatteryLevel"}, {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, {500, nullptr, "GetPalmaConnectionHandle"}, {501, nullptr, "InitializePalma"}, @@ -277,8 +328,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {527, nullptr, "EnablePalmaBoostMode"}, {528, nullptr, "GetPalmaBluetoothAddress"}, {529, nullptr, "SetDisallowedPalmaConnection"}, - {1000, nullptr, "SetNpadCommunicationMode"}, - {1001, nullptr, "GetNpadCommunicationMode"}, + {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, + {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, {1002, nullptr, "SetTouchScreenConfiguration"}, {1003, nullptr, "IsFirmwareUpdateNeededForNotification"}, {2000, nullptr, "ActivateDigitizer"}, @@ -305,117 +356,152 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IAppletResource>(applet_resource); } -void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { +void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto basic_xpad_id{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id, - applet_resource_user_id); + applet_resource->ActivateController(HidController::DebugPad); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::XPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { +void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); + applet_resource->ActivateController(HidController::Touchscreen); - IPC::ResponseBuilder rb{ctx, 3}; + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push(0); } -void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { +void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Mouse); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::DebugPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { +void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Keyboard); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::Touchscreen); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { +void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto flags{rp.Pop<u32>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); - applet_resource->ActivateController(HidController::Mouse); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { +void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 basic_xpad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::XPad); + + LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", + parameters.basic_xpad_id, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::Keyboard); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { +void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto flags{rp.Pop<u32>()}; - LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; + LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.Push(0); } -void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { +void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::Gesture); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { - // Should have no effect with how our npad sets up the data +void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -423,11 +509,120 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool enable_sixaxis_sensor_fusion; + INSERT_PADDING_BYTES_NOINIT(3); + Controller_NPad::DeviceHandle sixaxis_handle; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_HID, + "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " + "device_index={}, applet_resource_user_id={}", + parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + f32 parameter1; + f32 parameter2; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSixAxisFusionParameters(parameters.parameter1, parameters.parameter2); + + LOG_WARNING(Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, parameter1={}, " + "parameter2={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.parameter1, + parameters.parameter2, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + f32 parameter1 = 0; + f32 parameter2 = 0; + const auto parameters{rp.PopRaw<Parameters>()}; + + std::tie(parameter1, parameter2) = + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetSixAxisFusionParameters(); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(parameter1); + rb.Push(parameter2); +} + +void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .ResetSixAxisFusionParameters(); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -435,14 +630,17 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto drift_mode{rp.Pop<u32>()}; + const auto sixaxis_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode{drift_mode}); + .SetGyroscopeZeroDriftMode(drift_mode); - LOG_DEBUG(Service_HID, "called, handle={}, drift_mode={}, applet_resource_user_id={}", handle, + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, " + "applet_resource_user_id={}", + sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index, drift_mode, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -451,29 +649,42 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>( - static_cast<u32>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetGyroscopeZeroDriftMode())); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetGyroscopeZeroDriftMode()); } void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -481,27 +692,53 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. - rb.Push(true); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .IsSixAxisSensorAtRest()); +} + +void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::Gesture); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto supported_styleset{rp.Pop<u32>()}; - LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); - applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetSupportedStyleSet({supported_styleset}); + LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -512,21 +749,22 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(controller.GetSupportedStyleSet().raw); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetSupportedStyleSet() + .raw); } void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -535,48 +773,62 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->ActivateController(HidController::NPad); } void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->DeactivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->DeactivateController(HidController::NPad); } void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto unknown{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + u64 unknown; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id, - applet_resource_user_id, unknown); + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", + parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetStyleSetChangedEvent(npad_id)); + .GetStyleSetChangedEvent(parameters.npad_id)); } void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .DisconnectNpad(parameters.npad_id); + + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, + parameters.applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -589,22 +841,41 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetLedPattern(npad_id) - .raw); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLedPattern(npad_id) + .raw); +} + +void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + // Should have no effect with how our npad sets up the data + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::NPad); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto hold_type{rp.Pop<u64>()}; + const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}", applet_resource_user_id, hold_type); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -615,22 +886,26 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad).GetHoldType()); } void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -639,16 +914,22 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { // TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto npad_joy_device_type{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + u64 npad_joy_device_type; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", - npad_id, applet_resource_user_id, npad_joy_device_type); - - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + parameters.npad_id, parameters.applet_resource_user_id, + parameters.npad_joy_device_type); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -656,14 +937,19 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual); + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -671,13 +957,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto unknown_1{rp.Pop<u32>()}; - const auto unknown_2{rp.Pop<u32>()}; + const auto npad_id_1{rp.Pop<u32>()}; + const auto npad_id_2{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, - "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}", - unknown_1, unknown_2, applet_resource_user_id); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); + + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", + npad_id_1, npad_id_2, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -687,9 +975,9 @@ void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).StartLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.StartLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -699,9 +987,9 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).StopLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.StopLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -710,10 +998,13 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto mode{rp.Pop<u64>()}; + const auto activation_mode{rp.PopEnum<Controller_NPad::NpadHandheldActivationMode>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadHandheldActivationMode(activation_mode); - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}", - applet_resource_user_id, mode); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}", + applet_resource_user_id, activation_mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -723,25 +1014,28 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", - applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetNpadHandheldActivationMode()); } void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_1{rp.Pop<u32>()}; - const auto npad_2{rp.Pop<u32>()}; + const auto npad_id_1{rp.Pop<u32>()}; + const auto npad_id_2{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}", - applet_resource_user_id, npad_1, npad_2); + const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SwapNpadAssignment(npad_id_1, npad_id_2); + + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", + npad_id_1, npad_id_2, applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; - if (controller.SwapNpadAssignment(npad_1, npad_2)) { + if (res) { rb.Push(RESULT_SUCCESS); } else { LOG_ERROR(Service_HID, "Npads are not connected!"); @@ -749,61 +1043,99 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { } } -void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { +void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); -void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); - - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id)); } -void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { +void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop<u32>()}; - const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + bool unintended_home_button_input_protection; + INSERT_PADDING_BYTES_NOINIT(3); + u32 npad_id; + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetUnintendedHomeButtonInputProtectionEnabled( + parameters.unintended_home_button_input_protection, parameters.npad_id); + + LOG_WARNING(Service_HID, + "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={}," + "applet_resource_user_id={}", + parameters.unintended_home_button_input_protection, parameters.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController({controller_id}, {vibration_values}); } -void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { +void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + + VibrationDeviceInfo vibration_device_info; + + vibration_device_info.type = VibrationDeviceType::LinearResonantActuator; + + switch (vibration_device_handle.device_index) { + case Controller_NPad::DeviceIndex::Left: + vibration_device_info.position = VibrationDevicePosition::Left; + break; + case Controller_NPad::DeviceIndex::Right: + vibration_device_info.position = VibrationDevicePosition::Right; + break; + case Controller_NPad::DeviceIndex::None: + default: + UNREACHABLE_MSG("DeviceIndex should never be None!"); + vibration_device_info.position = VibrationDevicePosition::None; + break; + } - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}", + vibration_device_info.type, vibration_device_info.position); - const auto controllers = ctx.ReadBuffer(0); - const auto vibrations = ctx.ReadBuffer(1); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(vibration_device_info); +} - std::vector<u32> controller_list(controllers.size() / sizeof(u32)); - std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() / - sizeof(Controller_NPad::Vibration)); +void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle; + Controller_NPad::VibrationValue vibration_value; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - std::memcpy(controller_list.data(), controllers.data(), controllers.size()); - std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); - std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(), - [](u32 controller_id) { return controller_id - 3; }); + const auto parameters{rp.PopRaw<Parameters>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController(controller_list, vibration_list); + .VibrateController(parameters.vibration_device_handle, parameters.vibration_value); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -811,25 +1143,24 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<Controller_NPad::Vibration>( - applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration()); -} + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); -void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); - - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); - rb.Push<u32>(0); + rb.PushRaw(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLastVibration(parameters.vibration_device_handle)); } void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { @@ -837,13 +1168,14 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IActiveVibrationDeviceList>(); + rb.PushIpcInterface<IActiveVibrationDeviceList>(system, applet_resource); } void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_vibrate{rp.Pop<bool>()}; - Settings::values.vibration_enabled = can_vibrate; + + Settings::values.vibration_enabled.SetValue(can_vibrate); LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); @@ -856,7 +1188,76 @@ void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(Settings::values.vibration_enabled); + rb.Push(Settings::values.vibration_enabled.GetValue()); +} + +void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + const auto handles = ctx.ReadBuffer(0); + const auto vibrations = ctx.ReadBuffer(1); + + std::vector<Controller_NPad::DeviceHandle> vibration_device_handles( + handles.size() / sizeof(Controller_NPad::DeviceHandle)); + std::vector<Controller_NPad::VibrationValue> vibration_values( + vibrations.size() / sizeof(Controller_NPad::VibrationValue)); + + std::memcpy(vibration_device_handles.data(), handles.data(), handles.size()); + std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size()); + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateControllers(vibration_device_handles, vibration_values); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetPermitVibrationSession(true); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetPermitVibrationSession(false); + + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .IsVibrationDeviceMounted(parameters.vibration_device_handle)); } void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { @@ -872,11 +1273,19 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -884,11 +1293,19 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -979,9 +1396,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Hid::SetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto communication_mode{rp.PopEnum<Controller_NPad::NpadCommunicationMode>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadCommunicationMode(communication_mode); + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, communication_mode={}", + applet_resource_user_id, communication_mode); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetNpadCommunicationMode()); +} + class HidDbg final : public ServiceFramework<HidDbg> { public: - explicit HidDbg() : ServiceFramework{"hid:dbg"} { + explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "DeactivateDebugPad"}, @@ -1108,7 +1553,7 @@ public: class HidSys final : public ServiceFramework<HidSys> { public: - explicit HidSys() : ServiceFramework{"hid:sys"} { + explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { // clang-format off static const FunctionInfo functions[] = { {31, nullptr, "SendKeyboardLockKeyEvent"}, @@ -1242,7 +1687,7 @@ public: class HidTmp final : public ServiceFramework<HidTmp> { public: - explicit HidTmp() : ServiceFramework{"hid:tmp"} { + explicit HidTmp(Core::System& system_) : ServiceFramework{system_, "hid:tmp"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetConsoleSixAxisSensorCalibrationValues"}, @@ -1255,7 +1700,7 @@ public: class HidBus final : public ServiceFramework<HidBus> { public: - explicit HidBus() : ServiceFramework{"hidbus"} { + explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "GetBusHandle"}, @@ -1285,15 +1730,15 @@ void ReloadInputDevices() { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared<Hid>(system)->InstallAsService(service_manager); - std::make_shared<HidBus>()->InstallAsService(service_manager); - std::make_shared<HidDbg>()->InstallAsService(service_manager); - std::make_shared<HidSys>()->InstallAsService(service_manager); - std::make_shared<HidTmp>()->InstallAsService(service_manager); + std::make_shared<HidBus>(system)->InstallAsService(service_manager); + std::make_shared<HidDbg>(system)->InstallAsService(service_manager); + std::make_shared<HidSys>(system)->InstallAsService(service_manager); + std::make_shared<HidTmp>(system)->InstallAsService(service_manager); std::make_shared<IRS>(system)->InstallAsService(service_manager); - std::make_shared<IRS_SYS>()->InstallAsService(service_manager); + std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager); - std::make_shared<XCD_SYS>()->InstallAsService(service_manager); + std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index efb07547f..7cc0433e2 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -29,19 +29,21 @@ enum class HidController : std::size_t { Mouse, Keyboard, XPad, - Unknown1, - Unknown2, - Unknown3, - SixAxisSensor, + HomeButton, + SleepButton, + CaptureButton, + InputDetector, + UniquePad, NPad, Gesture, + ConsoleSixAxisSensor, MaxControllers, }; class IAppletResource final : public ServiceFramework<IAppletResource> { public: - explicit IAppletResource(Core::System& system); + explicit IAppletResource(Core::System& system_); ~IAppletResource() override; void ActivateController(HidController controller); @@ -65,11 +67,12 @@ private: void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); + void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); std::shared_ptr<Kernel::SharedMemory> shared_mem; std::shared_ptr<Core::Timing::EventType> pad_update_event; - Core::System& system; + std::shared_ptr<Core::Timing::EventType> motion_update_event; std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> controllers{}; @@ -77,28 +80,33 @@ private: class Hid final : public ServiceFramework<Hid> { public: - explicit Hid(Core::System& system); + explicit Hid(Core::System& system_); ~Hid() override; std::shared_ptr<IAppletResource> GetAppletResource(); private: void CreateAppletResource(Kernel::HLERequestContext& ctx); - void ActivateXpad(Kernel::HLERequestContext& ctx); - void GetXpadIDs(Kernel::HLERequestContext& ctx); void ActivateDebugPad(Kernel::HLERequestContext& ctx); void ActivateTouchScreen(Kernel::HLERequestContext& ctx); void ActivateMouse(Kernel::HLERequestContext& ctx); void ActivateKeyboard(Kernel::HLERequestContext& ctx); void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx); - void ActivateGesture(Kernel::HLERequestContext& ctx); - void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); + void ActivateXpad(Kernel::HLERequestContext& ctx); + void GetXpadIDs(Kernel::HLERequestContext& ctx); + void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); + void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void StartSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); + void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx); + void SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx); + void GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx); + void ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx); void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); + void ActivateGesture(Kernel::HLERequestContext& ctx); void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx); @@ -107,6 +115,7 @@ private: void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx); void DisconnectNpad(Kernel::HLERequestContext& ctx); void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); + void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx); void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx); void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx); @@ -118,15 +127,18 @@ private: void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void SwapNpadAssignment(Kernel::HLERequestContext& ctx); - void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); - void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); + void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx); + void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx); + void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); void SendVibrationValue(Kernel::HLERequestContext& ctx); - void SendVibrationValues(Kernel::HLERequestContext& ctx); void GetActualVibrationValue(Kernel::HLERequestContext& ctx); - void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); void PermitVibration(Kernel::HLERequestContext& ctx); void IsVibrationPermitted(Kernel::HLERequestContext& ctx); + void SendVibrationValues(Kernel::HLERequestContext& ctx); + void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); + void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); + void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx); void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); @@ -138,9 +150,26 @@ private: void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx); void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); + void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx); + void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); + + enum class VibrationDeviceType : u32 { + LinearResonantActuator = 1, + }; + + enum class VibrationDevicePosition : u32 { + None = 0, + Left = 1, + Right = 2, + }; + + struct VibrationDeviceInfo { + VibrationDeviceType type{}; + VibrationDevicePosition position{}; + }; + static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size."); std::shared_ptr<IAppletResource> applet_resource; - Core::System& system; }; /// Reload input devices. Used when input configuration changed diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index e82fd031b..c8413099f 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -12,7 +12,7 @@ namespace Service::HID { -IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) { +IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { // clang-format off static const FunctionInfo functions[] = { {302, &IRS::ActivateIrsensor, "ActivateIrsensor"}, @@ -175,7 +175,7 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { IRS::~IRS() = default; -IRS_SYS::IRS_SYS() : ServiceFramework{"irs:sys"} { +IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { // clang-format off static const FunctionInfo functions[] = { {500, nullptr, "SetAppletResourceUserId"}, diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 8918ad6ca..be0c486ba 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -7,6 +7,10 @@ #include "core/hle/kernel/object.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class SharedMemory; } @@ -15,7 +19,7 @@ namespace Service::HID { class IRS final : public ServiceFramework<IRS> { public: - explicit IRS(Core::System& system); + explicit IRS(Core::System& system_); ~IRS() override; private: @@ -37,14 +41,14 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + std::shared_ptr<Kernel::SharedMemory> shared_mem; const u32 device_handle{0xABCD}; - Core::System& system; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { public: - explicit IRS_SYS(); + explicit IRS_SYS(Core::System& system); ~IRS_SYS() override; }; diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp index c8e9125f6..43a8840d0 100644 --- a/src/core/hle/service/hid/xcd.cpp +++ b/src/core/hle/service/hid/xcd.cpp @@ -6,7 +6,7 @@ namespace Service::HID { -XCD_SYS::XCD_SYS() : ServiceFramework{"xcd:sys"} { +XCD_SYS::XCD_SYS(Core::System& system_) : ServiceFramework{system_, "xcd:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetDataFormat"}, diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h index fd506d303..54932c228 100644 --- a/src/core/hle/service/hid/xcd.h +++ b/src/core/hle/service/hid/xcd.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::HID { class XCD_SYS final : public ServiceFramework<XCD_SYS> { public: - explicit XCD_SYS(); + explicit XCD_SYS(Core::System& system_); ~XCD_SYS() override; }; diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp index 17350b403..f4490f3d9 100644 --- a/src/core/hle/service/lbl/lbl.cpp +++ b/src/core/hle/service/lbl/lbl.cpp @@ -15,35 +15,35 @@ namespace Service::LBL { class LBL final : public ServiceFramework<LBL> { public: - explicit LBL() : ServiceFramework{"lbl"} { + explicit LBL(Core::System& system_) : ServiceFramework{system_, "lbl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SaveCurrentSetting"}, {1, nullptr, "LoadCurrentSetting"}, - {2, nullptr, "SetCurrentBrightnessSetting"}, - {3, nullptr, "GetCurrentBrightnessSetting"}, + {2, &LBL::SetCurrentBrightnessSetting, "SetCurrentBrightnessSetting"}, + {3, &LBL::GetCurrentBrightnessSetting, "GetCurrentBrightnessSetting"}, {4, nullptr, "ApplyCurrentBrightnessSettingToBacklight"}, {5, nullptr, "GetBrightnessSettingAppliedToBacklight"}, - {6, nullptr, "SwitchBacklightOn"}, - {7, nullptr, "SwitchBacklightOff"}, - {8, nullptr, "GetBacklightSwitchStatus"}, - {9, nullptr, "EnableDimming"}, - {10, nullptr, "DisableDimming"}, - {11, nullptr, "IsDimmingEnabled"}, - {12, nullptr, "EnableAutoBrightnessControl"}, - {13, nullptr, "DisableAutoBrightnessControl"}, - {14, nullptr, "IsAutoBrightnessControlEnabled"}, - {15, nullptr, "SetAmbientLightSensorValue"}, - {16, nullptr, "GetAmbientLightSensorValue"}, - {17, nullptr, "SetBrightnessReflectionDelayLevel"}, - {18, nullptr, "GetBrightnessReflectionDelayLevel"}, - {19, nullptr, "SetCurrentBrightnessMapping"}, - {20, nullptr, "GetCurrentBrightnessMapping"}, - {21, nullptr, "SetCurrentAmbientLightSensorMapping"}, - {22, nullptr, "GetCurrentAmbientLightSensorMapping"}, - {23, nullptr, "IsAmbientLightSensorAvailable"}, - {24, nullptr, "SetCurrentBrightnessSettingForVrMode"}, - {25, nullptr, "GetCurrentBrightnessSettingForVrMode"}, + {6, &LBL::SwitchBacklightOn, "SwitchBacklightOn"}, + {7, &LBL::SwitchBacklightOff, "SwitchBacklightOff"}, + {8, &LBL::GetBacklightSwitchStatus, "GetBacklightSwitchStatus"}, + {9, &LBL::EnableDimming, "EnableDimming"}, + {10, &LBL::DisableDimming, "DisableDimming"}, + {11, &LBL::IsDimmingEnabled, "IsDimmingEnabled"}, + {12, &LBL::EnableAutoBrightnessControl, "EnableAutoBrightnessControl"}, + {13, &LBL::DisableAutoBrightnessControl, "DisableAutoBrightnessControl"}, + {14, &LBL::IsAutoBrightnessControlEnabled, "IsAutoBrightnessControlEnabled"}, + {15, &LBL::SetAmbientLightSensorValue, "SetAmbientLightSensorValue"}, + {16, &LBL::GetAmbientLightSensorValue, "GetAmbientLightSensorValue"}, + {17, &LBL::SetBrightnessReflectionDelayLevel, "SetBrightnessReflectionDelayLevel"}, + {18, &LBL::GetBrightnessReflectionDelayLevel, "GetBrightnessReflectionDelayLevel"}, + {19, &LBL::SetCurrentBrightnessMapping, "SetCurrentBrightnessMapping"}, + {20, &LBL::GetCurrentBrightnessMapping, "GetCurrentBrightnessMapping"}, + {21, &LBL::SetCurrentAmbientLightSensorMapping, "SetCurrentAmbientLightSensorMapping"}, + {22, &LBL::GetCurrentAmbientLightSensorMapping, "GetCurrentAmbientLightSensorMapping"}, + {23, &LBL::IsAmbientLightSensorAvailable, "IsAmbientLightSensorAvailable"}, + {24, &LBL::SetCurrentBrightnessSettingForVrMode, "SetCurrentBrightnessSettingForVrMode"}, + {25, &LBL::GetCurrentBrightnessSettingForVrMode, "GetCurrentBrightnessSettingForVrMode"}, {26, &LBL::EnableVrMode, "EnableVrMode"}, {27, &LBL::DisableVrMode, "DisableVrMode"}, {28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"}, @@ -55,6 +55,237 @@ public: } private: + enum class BacklightSwitchStatus : u32 { + Off = 0, + On = 1, + }; + + void SetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto brightness = rp.Pop<float>(); + + if (!std::isfinite(brightness)) { + LOG_ERROR(Service_LBL, "Brightness is infinite!"); + brightness = 0.0f; + } + + LOG_DEBUG(Service_LBL, "called brightness={}", brightness); + + current_brightness = brightness; + update_instantly = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto brightness = current_brightness; + if (!std::isfinite(brightness)) { + LOG_ERROR(Service_LBL, "Brightness is infinite!"); + brightness = 0.0f; + } + + LOG_DEBUG(Service_LBL, "called brightness={}", brightness); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(brightness); + } + + void SwitchBacklightOn(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fade_time = rp.Pop<u64_le>(); + LOG_WARNING(Service_LBL, "(STUBBED) called, fade_time={}", fade_time); + + backlight_enabled = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SwitchBacklightOff(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto fade_time = rp.Pop<u64_le>(); + LOG_WARNING(Service_LBL, "(STUBBED) called, fade_time={}", fade_time); + + backlight_enabled = false; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetBacklightSwitchStatus(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum<BacklightSwitchStatus>(backlight_enabled ? BacklightSwitchStatus::On + : BacklightSwitchStatus::Off); + } + + void EnableDimming(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + dimming = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void DisableDimming(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + dimming = false; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void IsDimmingEnabled(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(dimming); + } + + void EnableAutoBrightnessControl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + auto_brightness = true; + update_instantly = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void DisableAutoBrightnessControl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + auto_brightness = false; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void IsAutoBrightnessControlEnabled(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(auto_brightness); + } + + void SetAmbientLightSensorValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto light_value = rp.Pop<float>(); + + LOG_DEBUG(Service_LBL, "called light_value={}", light_value); + + ambient_light_value = light_value; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetAmbientLightSensorValue(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(ambient_light_value); + } + + void SetBrightnessReflectionDelayLevel(Kernel::HLERequestContext& ctx) { + // This is Intentional, this function does absolutely nothing + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetBrightnessReflectionDelayLevel(Kernel::HLERequestContext& ctx) { + // This is intentional, the function is hard coded to return 0.0f on hardware + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(0.0f); + } + + void SetCurrentBrightnessMapping(Kernel::HLERequestContext& ctx) { + // This is Intentional, this function does absolutely nothing + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetCurrentBrightnessMapping(Kernel::HLERequestContext& ctx) { + // This is Intentional, this function does absolutely nothing + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + // This function is suppose to return something but it seems like it doesn't + } + + void SetCurrentAmbientLightSensorMapping(Kernel::HLERequestContext& ctx) { + // This is Intentional, this function does absolutely nothing + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetCurrentAmbientLightSensorMapping(Kernel::HLERequestContext& ctx) { + // This is Intentional, this function does absolutely nothing + LOG_DEBUG(Service_LBL, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + // This function is suppose to return something but it seems like it doesn't + } + + void IsAmbientLightSensorAvailable(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LBL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + // TODO(ogniK): Only return true if there's no device error + rb.Push(true); + } + + void SetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto brightness = rp.Pop<float>(); + + if (!std::isfinite(brightness)) { + LOG_ERROR(Service_LBL, "Brightness is infinite!"); + brightness = 0.0f; + } + + LOG_DEBUG(Service_LBL, "called brightness={}", brightness); + + current_vr_brightness = brightness; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto brightness = current_vr_brightness; + if (!std::isfinite(brightness)) { + LOG_ERROR(Service_LBL, "Brightness is infinite!"); + brightness = 0.0f; + } + + LOG_DEBUG(Service_LBL, "called brightness={}", brightness); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(brightness); + } + void EnableVrMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_LBL, "called"); @@ -82,10 +313,18 @@ private: } bool vr_mode_enabled = false; + float current_brightness = 1.0f; + float backlight_brightness = 1.0f; + float ambient_light_value = 0.0f; + float current_vr_brightness = 1.0f; + bool dimming = true; + bool backlight_enabled = true; + bool update_instantly = false; + bool auto_brightness = false; // TODO(ogniK): Move to system settings }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<LBL>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<LBL>(system)->InstallAsService(sm); } } // namespace Service::LBL diff --git a/src/core/hle/service/lbl/lbl.h b/src/core/hle/service/lbl/lbl.h index bf6f400f8..9c2021026 100644 --- a/src/core/hle/service/lbl/lbl.h +++ b/src/core/hle/service/lbl/lbl.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::LBL { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::LBL diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h new file mode 100644 index 000000000..a718c5c66 --- /dev/null +++ b/src/core/hle/service/ldn/errors.h @@ -0,0 +1,13 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::LDN { + +constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22}; + +} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index 49972cd69..c630d93cd 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -6,6 +6,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/result.h" +#include "core/hle/service/ldn/errors.h" #include "core/hle/service/ldn/ldn.h" #include "core/hle/service/sm/sm.h" @@ -13,7 +14,7 @@ namespace Service::LDN { class IMonitorService final : public ServiceFramework<IMonitorService> { public: - explicit IMonitorService() : ServiceFramework{"IMonitorService"} { + explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetStateForMonitor"}, @@ -33,7 +34,7 @@ public: class LDNM final : public ServiceFramework<LDNM> { public: - explicit LDNM() : ServiceFramework{"ldn:m"} { + explicit LDNM(Core::System& system_) : ServiceFramework{system_, "ldn:m"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNM::CreateMonitorService, "CreateMonitorService"} @@ -48,15 +49,15 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IMonitorService>(); + rb.PushIpcInterface<IMonitorService>(system); } }; class ISystemLocalCommunicationService final : public ServiceFramework<ISystemLocalCommunicationService> { public: - explicit ISystemLocalCommunicationService() - : ServiceFramework{"ISystemLocalCommunicationService"} { + explicit ISystemLocalCommunicationService(Core::System& system_) + : ServiceFramework{system_, "ISystemLocalCommunicationService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, @@ -99,10 +100,11 @@ public: class IUserLocalCommunicationService final : public ServiceFramework<IUserLocalCommunicationService> { public: - explicit IUserLocalCommunicationService() : ServiceFramework{"IUserLocalCommunicationService"} { + explicit IUserLocalCommunicationService(Core::System& system_) + : ServiceFramework{system_, "IUserLocalCommunicationService"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetState"}, + {0, &IUserLocalCommunicationService::GetState, "GetState"}, {1, nullptr, "GetNetworkInfo"}, {2, nullptr, "GetIpv4Address"}, {3, nullptr, "GetDisconnectReason"}, @@ -137,18 +139,43 @@ public: RegisterHandlers(functions); } - void Initialize2(Kernel::HLERequestContext& ctx) { + void GetState(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_LDN, "(STUBBED) called"); - // Result success seem make this services start network and continue. - // If we just pass result error then it will stop and maybe try again and again. + + IPC::ResponseBuilder rb{ctx, 3}; + + // Indicate a network error, as we do not actually emulate LDN + rb.Push(static_cast<u32>(State::Error)); + + rb.Push(RESULT_SUCCESS); + } + + void Initialize2(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LDN, "called"); + + is_initialized = true; + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_UNKNOWN); + rb.Push(RESULT_SUCCESS); } + +private: + enum class State { + None, + Initialized, + AccessPointOpened, + AccessPointCreated, + StationOpened, + StationConnected, + Error, + }; + + bool is_initialized{}; }; class LDNS final : public ServiceFramework<LDNS> { public: - explicit LDNS() : ServiceFramework{"ldn:s"} { + explicit LDNS(Core::System& system_) : ServiceFramework{system_, "ldn:s"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNS::CreateSystemLocalCommunicationService, "CreateSystemLocalCommunicationService"}, @@ -163,13 +190,13 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemLocalCommunicationService>(); + rb.PushIpcInterface<ISystemLocalCommunicationService>(system); } }; class LDNU final : public ServiceFramework<LDNU> { public: - explicit LDNU() : ServiceFramework{"ldn:u"} { + explicit LDNU(Core::System& system_) : ServiceFramework{system_, "ldn:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNU::CreateUserLocalCommunicationService, "CreateUserLocalCommunicationService"}, @@ -184,14 +211,14 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IUserLocalCommunicationService>(); + rb.PushIpcInterface<IUserLocalCommunicationService>(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<LDNM>()->InstallAsService(sm); - std::make_shared<LDNS>()->InstallAsService(sm); - std::make_shared<LDNU>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<LDNM>(system)->InstallAsService(sm); + std::make_shared<LDNS>(system)->InstallAsService(sm); + std::make_shared<LDNU>(system)->InstallAsService(sm); } } // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h index 6b2a3c2b2..3ccd9738b 100644 --- a/src/core/hle/service/ldn/ldn.h +++ b/src/core/hle/service/ldn/ldn.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::LDN { /// Registers all LDN services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::LDN diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index d8cd10e31..c724d2554 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -9,11 +9,12 @@ #include "common/alignment.h" #include "common/hex_util.h" #include "common/scope_exit.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/system_control.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/svc_results.h" #include "core/hle/service/ldr/ldr.h" #include "core/hle/service/service.h" #include "core/loader/nro.h" @@ -23,7 +24,7 @@ namespace Service::LDR { constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2}; -constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; +[[maybe_unused]] constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52}; constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53}; constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54}; @@ -33,7 +34,7 @@ constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57}; constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81}; constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82}; constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84}; -constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; +[[maybe_unused]] constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87}; constexpr std::size_t MAXIMUM_LOADED_RO{0x40}; @@ -114,7 +115,7 @@ static_assert(sizeof(NROInfo) == 0x60, "NROInfo has invalid size."); class DebugMonitor final : public ServiceFramework<DebugMonitor> { public: - explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} { + explicit DebugMonitor(Core::System& system_) : ServiceFramework{system_, "ldr:dmnt"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AddProcessToDebugLaunchQueue"}, @@ -129,7 +130,7 @@ public: class ProcessManager final : public ServiceFramework<ProcessManager> { public: - explicit ProcessManager() : ServiceFramework{"ldr:pm"} { + explicit ProcessManager(Core::System& system_) : ServiceFramework{system_, "ldr:pm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateProcess"}, @@ -146,7 +147,7 @@ public: class Shell final : public ServiceFramework<Shell> { public: - explicit Shell() : ServiceFramework{"ldr:shel"} { + explicit Shell(Core::System& system_) : ServiceFramework{system_, "ldr:shel"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AddProcessToLaunchQueue"}, @@ -160,13 +161,13 @@ public: class RelocatableObject final : public ServiceFramework<RelocatableObject> { public: - explicit RelocatableObject(Core::System& system) : ServiceFramework{"ldr:ro"}, system(system) { + explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} { // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadNro, "LoadNro"}, {1, &RelocatableObject::UnloadNro, "UnloadNro"}, {2, &RelocatableObject::LoadNrr, "LoadNrr"}, - {3, nullptr, "UnloadNrr"}, + {3, &RelocatableObject::UnloadNrr, "UnloadNrr"}, {4, &RelocatableObject::Initialize, "Initialize"}, {10, nullptr, "LoadNrrEx"}, }; @@ -272,6 +273,20 @@ public: rb.Push(RESULT_SUCCESS); } + void UnloadNrr(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto pid = rp.Pop<u64>(); + const auto nrr_address = rp.Pop<VAddr>(); + + LOG_DEBUG(Service_LDR, "called with pid={}, nrr_address={:016X}", pid, nrr_address); + + nrr.erase(nrr_address); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + } + bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start, std::size_t size) const { constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize}; @@ -315,7 +330,7 @@ public: const VAddr addr{GetRandomMapRegion(page_table, size)}; const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)}; - if (result == Kernel::ERR_INVALID_ADDRESS_STATE) { + if (result == Kernel::ResultInvalidCurrentMemory) { continue; } @@ -346,7 +361,7 @@ public: const ResultCode result{ page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)}; - if (result == Kernel::ERR_INVALID_ADDRESS_STATE) { + if (result == Kernel::ResultInvalidCurrentMemory) { continue; } @@ -512,9 +527,6 @@ public: header.segment_headers[RO_INDEX].memory_size, header.segment_headers[DATA_INDEX].memory_size, nro_address}); - // Invalidate JIT caches for the newly mapped process code - system.InvalidateCpuInstructionCaches(); - IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push(*map_result); @@ -575,8 +587,6 @@ public: const auto result{UnmapNro(iter->second)}; - system.InvalidateCpuInstructionCaches(); - nro.erase(iter); IPC::ResponseBuilder rb{ctx, 2}; @@ -624,13 +634,12 @@ private: Common::Is4KBAligned(header.segment_headers[RO_INDEX].memory_size) && Common::Is4KBAligned(header.segment_headers[DATA_INDEX].memory_size); } - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared<DebugMonitor>()->InstallAsService(sm); - std::make_shared<ProcessManager>()->InstallAsService(sm); - std::make_shared<Shell>()->InstallAsService(sm); + std::make_shared<DebugMonitor>(system)->InstallAsService(sm); + std::make_shared<ProcessManager>(system)->InstallAsService(sm); + std::make_shared<Shell>(system)->InstallAsService(sm); std::make_shared<RelocatableObject>(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h index 7ac8c0b65..104fc15c5 100644 --- a/src/core/hle/service/ldr/ldr.h +++ b/src/core/hle/service/ldr/ldr.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index dec96b771..7d7542fc2 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -5,20 +5,71 @@ #include <sstream> #include <string> +#include <optional> +#include <unordered_map> +#include <boost/container_hash/hash.hpp> #include "common/logging/log.h" #include "common/scope_exit.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/lm/lm.h" -#include "core/hle/service/lm/manager.h" #include "core/hle/service/service.h" #include "core/memory.h" namespace Service::LM { +enum class LogSeverity : u8 { + Trace = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal = 4, +}; + +// To keep flags out of hashing as well as the payload size +struct LogPacketHeaderEntry { + u64_le pid{}; + u64_le tid{}; + LogSeverity severity{}; + u8 verbosity{}; + + auto operator<=>(const LogPacketHeaderEntry&) const = default; +}; +} // namespace Service::LM + +namespace std { +template <> +struct hash<Service::LM::LogPacketHeaderEntry> { + std::size_t operator()(const Service::LM::LogPacketHeaderEntry& k) const noexcept { + std::size_t seed{}; + boost::hash_combine(seed, k.pid); + boost::hash_combine(seed, k.tid); + boost::hash_combine(seed, k.severity); + boost::hash_combine(seed, k.verbosity); + return seed; + }; +}; +} // namespace std + +namespace Service::LM { + +enum class LogDestination : u32 { + TargetManager = 1 << 0, + Uart = 1 << 1, + UartSleep = 1 << 2, + All = 0xffff, +}; +DECLARE_ENUM_FLAG_OPERATORS(LogDestination); + +enum class LogPacketFlags : u8 { + Head = 1 << 0, + Tail = 1 << 1, + LittleEndian = 1 << 2, +}; +DECLARE_ENUM_FLAG_OPERATORS(LogPacketFlags); class ILogger final : public ServiceFramework<ILogger> { public: - explicit ILogger(Manager& manager_, Core::Memory::Memory& memory_) - : ServiceFramework("ILogger"), manager{manager_}, memory{memory_} { + explicit ILogger(Core::System& system_) : ServiceFramework{system_, "ILogger"} { static const FunctionInfo functions[] = { {0, &ILogger::Log, "Log"}, {1, &ILogger::SetDestination, "SetDestination"}, @@ -28,60 +79,269 @@ public: private: void Log(Kernel::HLERequestContext& ctx) { + std::size_t offset{}; + const auto data = ctx.ReadBuffer(); + // This function only succeeds - Get that out of the way IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - // Read MessageHeader, despite not doing anything with it right now - MessageHeader header{}; - VAddr addr{ctx.BufferDescriptorX()[0].Address()}; - const VAddr end_addr{addr + ctx.BufferDescriptorX()[0].size}; - memory.ReadBlock(addr, &header, sizeof(MessageHeader)); - addr += sizeof(MessageHeader); - - FieldMap fields; - while (addr < end_addr) { - const auto field = static_cast<Field>(memory.Read8(addr++)); - const auto length = memory.Read8(addr++); + if (data.size() < sizeof(LogPacketHeader)) { + LOG_ERROR(Service_LM, "Data size is too small for header! size={}", data.size()); + return; + } - if (static_cast<Field>(memory.Read8(addr)) == Field::Skip) { - ++addr; - } + LogPacketHeader header{}; + std::memcpy(&header, data.data(), sizeof(LogPacketHeader)); + offset += sizeof(LogPacketHeader); - SCOPE_EXIT({ addr += length; }); + LogPacketHeaderEntry entry{ + .pid = header.pid, + .tid = header.tid, + .severity = header.severity, + .verbosity = header.verbosity, + }; - if (field == Field::Skip) { - continue; + if (True(header.flags & LogPacketFlags::Head)) { + std::vector<u8> tmp(data.size() - sizeof(LogPacketHeader)); + std::memcpy(tmp.data(), data.data() + offset, tmp.size()); + entries[entry] = std::move(tmp); + } else { + // Append to existing entry + if (!entries.contains(entry)) { + LOG_ERROR(Service_LM, "Log entry does not exist!"); + return; } + std::vector<u8> tmp(data.size() - sizeof(LogPacketHeader)); - std::vector<u8> data(length); - memory.ReadBlock(addr, data.data(), length); - fields.emplace(field, std::move(data)); + auto& existing_entry = entries[entry]; + const auto base = existing_entry.size(); + existing_entry.resize(base + (data.size() - sizeof(LogPacketHeader))); + std::memcpy(existing_entry.data() + base, data.data() + offset, + (data.size() - sizeof(LogPacketHeader))); } - manager.Log({header, std::move(fields)}); + if (True(header.flags & LogPacketFlags::Tail)) { + auto it = entries.find(entry); + if (it == entries.end()) { + LOG_ERROR(Service_LM, "Log entry does not exist!"); + return; + } + ParseLog(it->first, it->second); + entries.erase(it); + } } void SetDestination(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto destination = rp.PopEnum<DestinationFlag>(); - - LOG_DEBUG(Service_LM, "called, destination={:08X}", static_cast<u32>(destination)); + const auto log_destination = rp.PopEnum<LogDestination>(); - manager.SetDestination(destination); + LOG_DEBUG(Service_LM, "called, destination={}", DestinationToString(log_destination)); + destination = log_destination; IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - Manager& manager; - Core::Memory::Memory& memory; + u64 ReadLeb128(const std::vector<u8>& data, std::size_t& offset) { + u64 result{}; + u32 shift{}; + + for (std::size_t i = 0; i < sizeof(u64); i++) { + const auto v = data[offset]; + result |= (static_cast<u64>(v & 0x7f) << shift); + shift += 7; + offset++; + if (offset >= data.size() || ((v & 0x80) == 0)) { + break; + } + } + return result; + } + + std::optional<std::string> ReadString(const std::vector<u8>& data, std::size_t& offset, + std::size_t length) { + if (length == 0) { + return std::nullopt; + } + const auto length_to_read = std::min(length, data.size() - offset); + + std::string output(length_to_read, '\0'); + std::memcpy(output.data(), data.data() + offset, length_to_read); + offset += length_to_read; + return output; + } + + u32_le ReadAsU32(const std::vector<u8>& data, std::size_t& offset, std::size_t length) { + ASSERT(length == sizeof(u32)); + u32_le output{}; + std::memcpy(&output, data.data() + offset, sizeof(u32)); + offset += length; + return output; + } + + u64_le ReadAsU64(const std::vector<u8>& data, std::size_t& offset, std::size_t length) { + ASSERT(length == sizeof(u64)); + u64_le output{}; + std::memcpy(&output, data.data() + offset, sizeof(u64)); + offset += length; + return output; + } + + void ParseLog(const LogPacketHeaderEntry entry, const std::vector<u8>& log_data) { + // Possible entries + std::optional<std::string> text_log; + std::optional<u32> line_number; + std::optional<std::string> file_name; + std::optional<std::string> function_name; + std::optional<std::string> module_name; + std::optional<std::string> thread_name; + std::optional<u64> log_pack_drop_count; + std::optional<s64> user_system_clock; + std::optional<std::string> process_name; + + std::size_t offset{}; + while (offset < log_data.size()) { + const auto key = static_cast<LogDataChunkKey>(ReadLeb128(log_data, offset)); + const auto chunk_size = ReadLeb128(log_data, offset); + + switch (key) { + case LogDataChunkKey::LogSessionBegin: + case LogDataChunkKey::LogSessionEnd: + break; + case LogDataChunkKey::TextLog: + text_log = ReadString(log_data, offset, chunk_size); + break; + case LogDataChunkKey::LineNumber: + line_number = ReadAsU32(log_data, offset, chunk_size); + break; + case LogDataChunkKey::FileName: + file_name = ReadString(log_data, offset, chunk_size); + break; + case LogDataChunkKey::FunctionName: + function_name = ReadString(log_data, offset, chunk_size); + break; + case LogDataChunkKey::ModuleName: + module_name = ReadString(log_data, offset, chunk_size); + break; + case LogDataChunkKey::ThreadName: + thread_name = ReadString(log_data, offset, chunk_size); + break; + case LogDataChunkKey::LogPacketDropCount: + log_pack_drop_count = ReadAsU64(log_data, offset, chunk_size); + break; + case LogDataChunkKey::UserSystemClock: + user_system_clock = ReadAsU64(log_data, offset, chunk_size); + break; + case LogDataChunkKey::ProcessName: + process_name = ReadString(log_data, offset, chunk_size); + break; + } + } + + std::string output_log{}; + if (process_name) { + output_log += fmt::format("Process: {}\n", *process_name); + } + if (module_name) { + output_log += fmt::format("Module: {}\n", *module_name); + } + if (file_name) { + output_log += fmt::format("File: {}\n", *file_name); + } + if (function_name) { + output_log += fmt::format("Function: {}\n", *function_name); + } + if (line_number && *line_number != 0) { + output_log += fmt::format("Line: {}\n", *line_number); + } + output_log += fmt::format("ProcessID: {:X}\n", entry.pid); + output_log += fmt::format("ThreadID: {:X}\n", entry.tid); + + if (text_log) { + output_log += fmt::format("Log Text: {}\n", *text_log); + } + + switch (entry.severity) { + case LogSeverity::Trace: + LOG_DEBUG(Service_LM, "LogManager TRACE ({}):\n{}", DestinationToString(destination), + output_log); + break; + case LogSeverity::Info: + LOG_INFO(Service_LM, "LogManager INFO ({}):\n{}", DestinationToString(destination), + output_log); + break; + case LogSeverity::Warning: + LOG_WARNING(Service_LM, "LogManager WARNING ({}):\n{}", + DestinationToString(destination), output_log); + break; + case LogSeverity::Error: + LOG_ERROR(Service_LM, "LogManager ERROR ({}):\n{}", DestinationToString(destination), + output_log); + break; + case LogSeverity::Fatal: + LOG_CRITICAL(Service_LM, "LogManager FATAL ({}):\n{}", DestinationToString(destination), + output_log); + break; + default: + LOG_CRITICAL(Service_LM, "LogManager UNKNOWN ({}):\n{}", + DestinationToString(destination), output_log); + break; + } + } + + static std::string DestinationToString(LogDestination destination) { + if (True(destination & LogDestination::All)) { + return "TargetManager | Uart | UartSleep"; + } + std::string output{}; + if (True(destination & LogDestination::TargetManager)) { + output += "| TargetManager"; + } + if (True(destination & LogDestination::Uart)) { + output += "| Uart"; + } + if (True(destination & LogDestination::UartSleep)) { + output += "| UartSleep"; + } + if (output.length() > 0) { + return output.substr(2); + } + return "No Destination"; + } + + enum class LogDataChunkKey : u32 { + LogSessionBegin = 0, + LogSessionEnd = 1, + TextLog = 2, + LineNumber = 3, + FileName = 4, + FunctionName = 5, + ModuleName = 6, + ThreadName = 7, + LogPacketDropCount = 8, + UserSystemClock = 9, + ProcessName = 10, + }; + + struct LogPacketHeader { + u64_le pid{}; + u64_le tid{}; + LogPacketFlags flags{}; + INSERT_PADDING_BYTES(1); + LogSeverity severity{}; + u8 verbosity{}; + u32_le payload_size{}; + }; + static_assert(sizeof(LogPacketHeader) == 0x18, "LogPacketHeader is an invalid size"); + + std::unordered_map<LogPacketHeaderEntry, std::vector<u8>> entries{}; + LogDestination destination{LogDestination::All}; }; class LM final : public ServiceFramework<LM> { public: - explicit LM(Manager& manager_, Core::Memory::Memory& memory_) - : ServiceFramework{"lm"}, manager{manager_}, memory{memory_} { + explicit LM(Core::System& system_) : ServiceFramework{system_, "lm"} { // clang-format off static const FunctionInfo functions[] = { {0, &LM::OpenLogger, "OpenLogger"}, @@ -97,16 +357,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILogger>(manager, memory); + rb.PushIpcInterface<ILogger>(system); } - - Manager& manager; - Core::Memory::Memory& memory; }; void InstallInterfaces(Core::System& system) { - std::make_shared<LM>(system.GetLogManager(), system.Memory()) - ->InstallAsService(system.ServiceManager()); + std::make_shared<LM>(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::LM diff --git a/src/core/hle/service/lm/manager.cpp b/src/core/hle/service/lm/manager.cpp deleted file mode 100644 index 3ee2374e7..000000000 --- a/src/core/hle/service/lm/manager.cpp +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "core/hle/service/lm/manager.h" -#include "core/reporter.h" - -namespace Service::LM { - -std::ostream& operator<<(std::ostream& os, DestinationFlag dest) { - std::vector<std::string> array; - const auto check_single_flag = [dest, &array](DestinationFlag check, std::string name) { - if ((static_cast<u32>(check) & static_cast<u32>(dest)) != 0) { - array.emplace_back(std::move(name)); - } - }; - - check_single_flag(DestinationFlag::Default, "Default"); - check_single_flag(DestinationFlag::UART, "UART"); - check_single_flag(DestinationFlag::UARTSleeping, "UART (Sleeping)"); - - os << "["; - for (const auto& entry : array) { - os << entry << ", "; - } - return os << "]"; -} - -std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity) { - switch (severity) { - case MessageHeader::Severity::Trace: - return os << "Trace"; - case MessageHeader::Severity::Info: - return os << "Info"; - case MessageHeader::Severity::Warning: - return os << "Warning"; - case MessageHeader::Severity::Error: - return os << "Error"; - case MessageHeader::Severity::Critical: - return os << "Critical"; - default: - return os << fmt::format("{:08X}", static_cast<u32>(severity)); - } -} - -std::ostream& operator<<(std::ostream& os, Field field) { - switch (field) { - case Field::Skip: - return os << "Skip"; - case Field::Message: - return os << "Message"; - case Field::Line: - return os << "Line"; - case Field::Filename: - return os << "Filename"; - case Field::Function: - return os << "Function"; - case Field::Module: - return os << "Module"; - case Field::Thread: - return os << "Thread"; - default: - return os << fmt::format("{:08X}", static_cast<u32>(field)); - } -} - -std::string FormatField(Field type, const std::vector<u8>& data) { - switch (type) { - case Field::Skip: - return ""; - case Field::Line: - if (data.size() >= sizeof(u32)) { - u32 line; - std::memcpy(&line, data.data(), sizeof(u32)); - return fmt::format("{}", line); - } - return "[ERROR DECODING LINE NUMBER]"; - case Field::Message: - case Field::Filename: - case Field::Function: - case Field::Module: - case Field::Thread: - return Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(data.data()), data.size()); - default: - UNIMPLEMENTED_MSG("Unimplemented field type={}", type); - return ""; - } -} - -Manager::Manager(Core::Reporter& reporter) : reporter(reporter) {} - -Manager::~Manager() = default; - -void Manager::SetEnabled(bool enabled) { - this->enabled = enabled; -} - -void Manager::SetDestination(DestinationFlag destination) { - this->destination = destination; -} - -void Manager::Log(LogMessage message) { - if (message.header.IsHeadLog()) { - InitializeLog(); - } - - current_log.emplace_back(std::move(message)); - - if (current_log.back().header.IsTailLog()) { - FinalizeLog(); - } -} - -void Manager::Flush() { - FinalizeLog(); -} - -void Manager::InitializeLog() { - current_log.clear(); - - LOG_INFO(Service_LM, "Initialized new log session"); -} - -void Manager::FinalizeLog() { - reporter.SaveLogReport(static_cast<u32>(destination), std::move(current_log)); - - LOG_INFO(Service_LM, "Finalized current log session"); -} - -} // namespace Service::LM diff --git a/src/core/hle/service/lm/manager.h b/src/core/hle/service/lm/manager.h deleted file mode 100644 index 544e636ba..000000000 --- a/src/core/hle/service/lm/manager.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <ostream> -#include <vector> -#include "common/bit_field.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace Core { -class Reporter; -} - -namespace Service::LM { - -enum class DestinationFlag : u32 { - Default = 1, - UART = 2, - UARTSleeping = 4, - - All = 0xFFFF, -}; - -struct MessageHeader { - enum Flags : u32_le { - IsHead = 1, - IsTail = 2, - }; - enum Severity : u32_le { - Trace, - Info, - Warning, - Error, - Critical, - }; - - u64_le pid; - u64_le thread_context; - union { - BitField<0, 16, Flags> flags; - BitField<16, 8, Severity> severity; - BitField<24, 8, u32> verbosity; - }; - u32_le payload_size; - - bool IsHeadLog() const { - return flags & IsHead; - } - bool IsTailLog() const { - return flags & IsTail; - } -}; -static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size"); - -enum class Field : u8 { - Skip = 1, - Message = 2, - Line = 3, - Filename = 4, - Function = 5, - Module = 6, - Thread = 7, -}; - -std::ostream& operator<<(std::ostream& os, DestinationFlag dest); -std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity); -std::ostream& operator<<(std::ostream& os, Field field); - -using FieldMap = std::map<Field, std::vector<u8>>; - -struct LogMessage { - MessageHeader header; - FieldMap fields; -}; - -std::string FormatField(Field type, const std::vector<u8>& data); - -class Manager { -public: - explicit Manager(Core::Reporter& reporter); - ~Manager(); - - void SetEnabled(bool enabled); - void SetDestination(DestinationFlag destination); - - void Log(LogMessage message); - - void Flush(); - -private: - void InitializeLog(); - void FinalizeLog(); - - bool enabled = true; - DestinationFlag destination = DestinationFlag::All; - - std::vector<LogMessage> current_log; - - Core::Reporter& reporter; -}; - -} // namespace Service::LM diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp index 113a4665c..1599d941b 100644 --- a/src/core/hle/service/mig/mig.cpp +++ b/src/core/hle/service/mig/mig.cpp @@ -12,7 +12,7 @@ namespace Service::Migration { class MIG_USR final : public ServiceFramework<MIG_USR> { public: - explicit MIG_USR() : ServiceFramework{"mig:usr"} { + explicit MIG_USR(Core::System& system_) : ServiceFramework{system_, "mig:usr"} { // clang-format off static const FunctionInfo functions[] = { {10, nullptr, "TryGetLastMigrationInfo"}, @@ -33,8 +33,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<MIG_USR>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<MIG_USR>(system)->InstallAsService(sm); } } // namespace Service::Migration diff --git a/src/core/hle/service/mig/mig.h b/src/core/hle/service/mig/mig.h index 288c1c1b3..2b24cdf2c 100644 --- a/src/core/hle/service/mig/mig.h +++ b/src/core/hle/service/mig/mig.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::Migration { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Migration diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp index 4730070cb..70350a2a3 100644 --- a/src/core/hle/service/mii/manager.cpp +++ b/src/core/hle/service/mii/manager.cpp @@ -21,7 +21,7 @@ namespace { constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; -constexpr std::size_t DefaultMiiCount{sizeof(RawData::DefaultMii) / sizeof(DefaultMii)}; +constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; @@ -100,6 +100,7 @@ MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { .mole_scale = static_cast<u8>(bf.mole_scale.Value()), .mole_x = static_cast<u8>(bf.mole_x.Value()), .mole_y = static_cast<u8>(bf.mole_y.Value()), + .padding = 0, }; } @@ -131,7 +132,7 @@ template <typename T> T GetRandomValue(T min, T max) { std::random_device device; std::mt19937 gen(device()); - std::uniform_int_distribution<u64> distribution(0, static_cast<u64>(max)); + std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max)); return static_cast<T>(distribution(gen)); } @@ -140,13 +141,6 @@ T GetRandomValue(T max) { return GetRandomValue<T>({}, max); } -template <typename T> -T GetArrayValue(const u8* data, std::size_t index) { - T result{}; - std::memcpy(&result, &data[index * sizeof(T)], sizeof(T)); - return result; -} - MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { MiiStoreBitFields bf{}; @@ -192,32 +186,20 @@ MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Commo const std::size_t index{3 * static_cast<std::size_t>(age) + 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; - const auto faceline_type_info{ - GetArrayValue<RandomMiiData4>(&RawData::RandomMiiFaceline[0], index)}; - const auto faceline_color_info{GetArrayValue<RandomMiiData3>( - RawData::RandomMiiFacelineColor.data(), + const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; + const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; - const auto faceline_wrinkle_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineWrinkle.data(), index)}; - const auto faceline_makeup_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineMakeup.data(), index)}; - const auto hair_type_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiHairType.data(), index)}; - const auto hair_color_info{GetArrayValue<RandomMiiData3>(RawData::RandomMiiHairColor.data(), - 3 * static_cast<std::size_t>(race) + - static_cast<std::size_t>(age))}; - const auto eye_type_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyeType.data(), index)}; - const auto eye_color_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiEyeColor.data(), - static_cast<std::size_t>(race))}; - const auto eyebrow_type_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyebrowType.data(), index)}; - const auto nose_type_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiNoseType.data(), index)}; - const auto mouth_type_info{ - GetArrayValue<RandomMiiData4>(RawData::RandomMiiMouthType.data(), index)}; - const auto glasses_type_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiGlassType.data(), - static_cast<std::size_t>(age))}; + const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; + const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; + const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; + const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + + static_cast<std::size_t>(age))}; + const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; + const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; + const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; + const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; + const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; + const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; bf.faceline_type.Assign( faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); @@ -428,7 +410,7 @@ bool MiiManager::IsFullDatabase() const { } u32 MiiManager::GetCount(SourceFlag source_flag) const { - u32 count{}; + std::size_t count{}; if ((source_flag & SourceFlag::Database) != SourceFlag::None) { // TODO(bunnei): We don't implement the Mii database, but when we do, update this count += 0; @@ -436,7 +418,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) != SourceFlag::None) { count += DefaultMiiCount; } - return count; + return static_cast<u32>(count); } ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, @@ -454,8 +436,7 @@ MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { } MiiInfo MiiManager::BuildDefault(std::size_t index) { - return ConvertStoreDataToInfo(BuildDefaultStoreData( - GetArrayValue<DefaultMii>(RawData::DefaultMii.data(), index), user_id)); + return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); } ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { diff --git a/src/core/hle/service/mii/manager.h b/src/core/hle/service/mii/manager.h index 927451dea..2106a528a 100644 --- a/src/core/hle/service/mii/manager.h +++ b/src/core/hle/service/mii/manager.h @@ -27,58 +27,58 @@ enum class SourceFlag : u32 { DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); struct MiiInfo { - Common::UUID uuid{Common::INVALID_UUID}; - std::array<char16_t, 11> name{}; - u8 font_region{}; - u8 favorite_color{}; - u8 gender{}; - u8 height{}; - u8 build{}; - u8 type{}; - u8 region_move{}; - u8 faceline_type{}; - u8 faceline_color{}; - u8 faceline_wrinkle{}; - u8 faceline_make{}; - u8 hair_type{}; - u8 hair_color{}; - u8 hair_flip{}; - u8 eye_type{}; - u8 eye_color{}; - u8 eye_scale{}; - u8 eye_aspect{}; - u8 eye_rotate{}; - u8 eye_x{}; - u8 eye_y{}; - u8 eyebrow_type{}; - u8 eyebrow_color{}; - u8 eyebrow_scale{}; - u8 eyebrow_aspect{}; - u8 eyebrow_rotate{}; - u8 eyebrow_x{}; - u8 eyebrow_y{}; - u8 nose_type{}; - u8 nose_scale{}; - u8 nose_y{}; - u8 mouth_type{}; - u8 mouth_color{}; - u8 mouth_scale{}; - u8 mouth_aspect{}; - u8 mouth_y{}; - u8 beard_color{}; - u8 beard_type{}; - u8 mustache_type{}; - u8 mustache_scale{}; - u8 mustache_y{}; - u8 glasses_type{}; - u8 glasses_color{}; - u8 glasses_scale{}; - u8 glasses_y{}; - u8 mole_type{}; - u8 mole_scale{}; - u8 mole_x{}; - u8 mole_y{}; - INSERT_PADDING_BYTES(1); + Common::UUID uuid; + std::array<char16_t, 11> name; + u8 font_region; + u8 favorite_color; + u8 gender; + u8 height; + u8 build; + u8 type; + u8 region_move; + u8 faceline_type; + u8 faceline_color; + u8 faceline_wrinkle; + u8 faceline_make; + u8 hair_type; + u8 hair_color; + u8 hair_flip; + u8 eye_type; + u8 eye_color; + u8 eye_scale; + u8 eye_aspect; + u8 eye_rotate; + u8 eye_x; + u8 eye_y; + u8 eyebrow_type; + u8 eyebrow_color; + u8 eyebrow_scale; + u8 eyebrow_aspect; + u8 eyebrow_rotate; + u8 eyebrow_x; + u8 eyebrow_y; + u8 nose_type; + u8 nose_scale; + u8 nose_y; + u8 mouth_type; + u8 mouth_color; + u8 mouth_scale; + u8 mouth_aspect; + u8 mouth_y; + u8 beard_color; + u8 beard_type; + u8 mustache_type; + u8 mustache_scale; + u8 mustache_y; + u8 glasses_type; + u8 glasses_color; + u8 glasses_scale; + u8 glasses_y; + u8 mole_type; + u8 mole_scale; + u8 mole_x; + u8 mole_y; + u8 padding; std::u16string Name() const; }; @@ -233,7 +233,7 @@ struct RandomMiiData4 { Age age{}; Race race{}; u32 values_count{}; - std::array<u8, 0xbc> values{}; + std::array<u32, 47> values{}; }; static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); @@ -241,14 +241,14 @@ struct RandomMiiData3 { u32 arg_1; u32 arg_2; u32 values_count; - std::array<u8, 0xbc> values{}; + std::array<u32, 47> values{}; }; static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); struct RandomMiiData2 { u32 arg_1; u32 values_count; - std::array<u8, 0xbc> values{}; + std::array<u32, 47> values{}; }; static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); @@ -324,7 +324,7 @@ public: ResultCode GetIndex(const MiiInfo& info, u32& index); private: - const Common::UUID user_id; + const Common::UUID user_id{Common::INVALID_UUID}; u64 update_counter{}; }; diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index b81bf6277..26be9e45b 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -18,7 +18,8 @@ constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; class IDatabaseService final : public ServiceFramework<IDatabaseService> { public: - explicit IDatabaseService() : ServiceFramework{"IDatabaseService"} { + explicit IDatabaseService(Core::System& system_) + : ServiceFramework{system_, "IDatabaseService"} { // clang-format off static const FunctionInfo functions[] = { {0, &IDatabaseService::IsUpdated, "IsUpdated"}, @@ -47,6 +48,7 @@ public: {23, nullptr, "Convert"}, {24, nullptr, "ConvertCoreDataToCharInfo"}, {25, nullptr, "ConvertCharInfoToCoreData"}, + {26, nullptr, "Append"}, }; // clang-format on @@ -251,7 +253,8 @@ private: class MiiDBModule final : public ServiceFramework<MiiDBModule> { public: - explicit MiiDBModule(const char* name) : ServiceFramework{name} { + explicit MiiDBModule(Core::System& system_, const char* name) + : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, @@ -265,7 +268,7 @@ private: void GetDatabaseService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IDatabaseService>(); + rb.PushIpcInterface<IDatabaseService>(system); LOG_DEBUG(Service_Mii, "called"); } @@ -273,7 +276,7 @@ private: class MiiImg final : public ServiceFramework<MiiImg> { public: - explicit MiiImg() : ServiceFramework{"miiimg"} { + explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -297,11 +300,11 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<MiiDBModule>("mii:e")->InstallAsService(sm); - std::make_shared<MiiDBModule>("mii:u")->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<MiiDBModule>(system, "mii:e")->InstallAsService(sm); + std::make_shared<MiiDBModule>(system, "mii:u")->InstallAsService(sm); - std::make_shared<MiiImg>()->InstallAsService(sm); + std::make_shared<MiiImg>(system)->InstallAsService(sm); } } // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index 7ce9be50e..9d3238e72 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::Mii { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp index 25d7bae0c..9d3c8017a 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/raw_data.cpp @@ -22,2240 +22,1642 @@ namespace Service::Mii::RawData { -const std::array<u8, 1728> DefaultMii{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, - 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, - 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, - 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, - 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, - 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, - 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 0, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 33, + .hair_color = 1, + .hair_flip = 0, + .eye_type = 2, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 4, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 6, + .eyebrow_color = 1, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Male, + .favorite_color = 0, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 0, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 12, + .hair_color = 1, + .hair_flip = 0, + .eye_type = 4, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 3, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 0, + .eyebrow_color = 1, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Female, + .favorite_color = 0, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 4, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 68, + .hair_color = 0, + .hair_flip = 0, + .eye_type = 2, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 4, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 6, + .eyebrow_color = 0, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Male, + .favorite_color = 4, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 0, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 55, + .hair_color = 6, + .hair_flip = 0, + .eye_type = 2, + .eye_color = 4, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 4, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 6, + .eyebrow_color = 6, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Male, + .favorite_color = 5, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 1, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 33, + .hair_color = 1, + .hair_flip = 0, + .eye_type = 2, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 4, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 6, + .eyebrow_color = 1, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Male, + .favorite_color = 0, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 2, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 24, + .hair_color = 0, + .hair_flip = 0, + .eye_type = 4, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 3, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 0, + .eyebrow_color = 0, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Female, + .favorite_color = 2, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 0, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 14, + .hair_color = 7, + .hair_flip = 0, + .eye_type = 4, + .eye_color = 5, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 3, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 0, + .eyebrow_color = 7, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Female, + .favorite_color = 6, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, + Service::Mii::DefaultMii{ + .face_type = 0, + .face_color = 0, + .face_wrinkle = 0, + .face_makeup = 0, + .hair_type = 12, + .hair_color = 1, + .hair_flip = 0, + .eye_type = 4, + .eye_color = 0, + .eye_scale = 4, + .eye_aspect = 3, + .eye_rotate = 3, + .eye_x = 2, + .eye_y = 12, + .eyebrow_type = 0, + .eyebrow_color = 1, + .eyebrow_scale = 4, + .eyebrow_aspect = 3, + .eyebrow_rotate = 6, + .eyebrow_x = 2, + .eyebrow_y = 10, + .nose_type = 1, + .nose_scale = 4, + .nose_y = 9, + .mouth_type = 23, + .mouth_color = 0, + .mouth_scale = 4, + .mouth_aspect = 3, + .mouth_y = 13, + .mustache_type = 0, + .beard_type = 0, + .beard_color = 0, + .mustache_scale = 4, + .mustache_y = 10, + .glasses_type = 0, + .glasses_color = 0, + .glasses_scale = 4, + .glasses_y = 10, + .mole_type = 0, + .mole_scale = 4, + .mole_x = 2, + .mole_y = 20, + .height = 64, + .weight = 64, + .gender = Gender::Female, + .favorite_color = 7, + .region = 0, + .font_region = FontRegion::Standard, + .type = 0, + }, -const std::array<u8, 3672> RandomMiiFaceline{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +}; -const std::array<u8, 1200> RandomMiiFacelineColor{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 12, + .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 13, + .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 12, + .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 12, + .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 13, + .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 12, + .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 10, + .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 12, + .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, + }, +}; -const std::array<u8, 3672> RandomMiiFacelineWrinkle{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 0, + .values_count = 10, + .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 1, + .values_count = 10, + .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 2, + .values_count = 10, + .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 0, + .values_count = 10, + .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 1, + .values_count = 10, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 2, + .values_count = 10, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + }, +}; -const std::array<u8, 3672> RandomMiiFacelineMakeup{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 20, + .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, + }, +}; -const std::array<u8, 3672> RandomMiiHairType{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x42, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, - 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, - 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, - 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, - 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, - 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, - 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, - 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, - 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, - 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, - 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, - 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, - 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x4a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, - 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, - 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, - 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, - 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x4a, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, - 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, - 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, - 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, - 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x51, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, - 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, - 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, +}; -const std::array<u8, 1800> RandomMiiHairColor{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 30, + .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, + 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 31, + .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, + 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 31, + .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, + 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 38, + .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 39, + .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 39, + .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 18, + .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 19, + .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 19, + .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 39, + .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 42, + .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, + 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 42, + .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, + 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 44, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, + 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 44, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, + 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 44, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, + 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 24, + .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, + 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 27, + .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, + 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 27, + .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, + 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, + }, +}; -const std::array<u8, 3672> RandomMiiEyeType{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, - 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, - 0x39, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, - 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, - 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, - 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, - 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, - 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, - 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<RandomMiiData3, 9> RandomMiiHairColor{ + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 0, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 1, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 0, + .arg_2 = 2, + .values_count = 20, + .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 0, + .values_count = 20, + .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 1, + .values_count = 20, + .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 1, + .arg_2 = 2, + .values_count = 20, + .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 2, + .arg_2 = 0, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 2, + .arg_2 = 1, + .values_count = 20, + .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, + }, + Service::Mii::RandomMiiData3{ + .arg_1 = 2, + .arg_2 = 2, + .values_count = 20, + .values = {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + }, +}; -const std::array<u8, 588> RandomMiiEyeColor{ - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 26, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, + 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 26, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, + 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 27, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, + 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 35, + .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, + 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 35, + .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, + 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 35, + .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, + 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 30, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, + 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 30, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, + 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 30, + .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, + 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 39, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, + 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 39, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, + 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 40, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, + 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 46, + .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, + 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, + 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 46, + .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, + 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, + 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 46, + .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, + 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, + 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 34, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, + 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 34, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, + 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 35, + .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, + 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, + }, +}; -const std::array<u8, 3672> RandomMiiEyebrowType{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ + Service::Mii::RandomMiiData2{ + .arg_1 = 0, + .values_count = 10, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + }, + Service::Mii::RandomMiiData2{ + .arg_1 = 1, + .values_count = 10, + .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, + }, + Service::Mii::RandomMiiData2{ + .arg_1 = 2, + .values_count = 10, + .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + }, +}; -const std::array<u8, 3672> RandomMiiNoseType{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 23, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 23, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 23, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 21, + .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 21, + .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 21, + .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 9, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 9, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 9, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 11, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 11, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 11, + .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 9, + .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 9, + .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 9, + .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, + }, +}; -const std::array<u8, 3672> RandomMiiMouthType{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 11, + .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 11, + .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 11, + .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 15, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 18, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 15, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 8, + .values = {0, 1, 3, 4, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 8, + .values = {0, 1, 3, 4, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 8, + .values = {0, 1, 3, 4, 8, 10, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 12, + .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 11, + .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 10, + .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 12, + .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 11, + .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 10, + .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, + }, +}; -const std::array<u8, 588> RandomMiiGlassType{ - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Black, + .values_count = 25, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, + 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Black, + .values_count = 27, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, + 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Black, + .values_count = 28, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, + 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::White, + .values_count = 24, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::White, + .values_count = 26, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::White, + .values_count = 26, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Young, + .race = Race::Asian, + .values_count = 24, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 26, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Male, + .age = Age::Old, + .race = Race::Asian, + .values_count = 26, + .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Black, + .values_count = 25, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, + 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Black, + .values_count = 26, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Black, + .values_count = 26, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::White, + .values_count = 25, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, + 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::White, + .values_count = 26, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::White, + .values_count = 25, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Young, + .race = Race::Asian, + .values_count = 24, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, + 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Normal, + .race = Race::Asian, + .values_count = 25, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, + }, + Service::Mii::RandomMiiData4{ + .gender = Gender::Female, + .age = Age::Old, + .race = Race::Asian, + .values_count = 25, + .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, + 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, + }, +}; + +const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ + Service::Mii::RandomMiiData2{ + .arg_1 = 0, + .values_count = 9, + .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, + }, + Service::Mii::RandomMiiData2{ + .arg_1 = 1, + .values_count = 9, + .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, + }, + Service::Mii::RandomMiiData2{ + .arg_1 = 2, + .values_count = 9, + .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, + }, +}; } // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h index a02a5c0fd..0e35d69d2 100644 --- a/src/core/hle/service/mii/raw_data.h +++ b/src/core/hle/service/mii/raw_data.h @@ -7,21 +7,22 @@ #include <array> #include "common/common_types.h" +#include "core/hle/service/mii/manager.h" namespace Service::Mii::RawData { -extern const std::array<u8, 1728> DefaultMii; -extern const std::array<u8, 3672> RandomMiiFaceline; -extern const std::array<u8, 1200> RandomMiiFacelineColor; -extern const std::array<u8, 3672> RandomMiiFacelineWrinkle; -extern const std::array<u8, 3672> RandomMiiFacelineMakeup; -extern const std::array<u8, 3672> RandomMiiHairType; -extern const std::array<u8, 1800> RandomMiiHairColor; -extern const std::array<u8, 3672> RandomMiiEyeType; -extern const std::array<u8, 588> RandomMiiEyeColor; -extern const std::array<u8, 3672> RandomMiiEyebrowType; -extern const std::array<u8, 3672> RandomMiiNoseType; -extern const std::array<u8, 3672> RandomMiiMouthType; -extern const std::array<u8, 588> RandomMiiGlassType; +extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; +extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType; +extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType; +extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType; +extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType; +extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType; } // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 25c24e537..b0cb07d24 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -6,12 +6,13 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_session.h" #include "core/hle/service/mm/mm_u.h" +#include "core/hle/service/sm/sm.h" namespace Service::MM { class MM_U final : public ServiceFramework<MM_U> { public: - explicit MM_U() : ServiceFramework{"mm:u"} { + explicit MM_U(Core::System& system_) : ServiceFramework{system_, "mm:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &MM_U::InitializeOld, "InitializeOld"}, @@ -104,8 +105,8 @@ private: u32 id{1}; }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<MM_U>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<MM_U>(system)->InstallAsService(service_manager); } } // namespace Service::MM diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h index 5439fa653..49b6a3355 100644 --- a/src/core/hle/service/mm/mm_u.h +++ b/src/core/hle/service/mm/mm_u.h @@ -4,11 +4,17 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} namespace Service::MM { /// Registers all MM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::MM diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp index e38dea1f4..2dcda16f6 100644 --- a/src/core/hle/service/ncm/ncm.cpp +++ b/src/core/hle/service/ncm/ncm.cpp @@ -14,8 +14,8 @@ namespace Service::NCM { class ILocationResolver final : public ServiceFramework<ILocationResolver> { public: - explicit ILocationResolver(FileSys::StorageId id) - : ServiceFramework{"ILocationResolver"}, storage(id) { + explicit ILocationResolver(Core::System& system_, FileSys::StorageId id) + : ServiceFramework{system_, "ILocationResolver"}, storage{id} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, @@ -45,12 +45,13 @@ public: } private: - FileSys::StorageId storage; + [[maybe_unused]] FileSys::StorageId storage; }; class IRegisteredLocationResolver final : public ServiceFramework<IRegisteredLocationResolver> { public: - explicit IRegisteredLocationResolver() : ServiceFramework{"IRegisteredLocationResolver"} { + explicit IRegisteredLocationResolver(Core::System& system_) + : ServiceFramework{system_, "IRegisteredLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, @@ -72,7 +73,8 @@ public: class IAddOnContentLocationResolver final : public ServiceFramework<IAddOnContentLocationResolver> { public: - explicit IAddOnContentLocationResolver() : ServiceFramework{"IAddOnContentLocationResolver"} { + explicit IAddOnContentLocationResolver(Core::System& system_) + : ServiceFramework{system_, "IAddOnContentLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveAddOnContentPath"}, @@ -89,7 +91,7 @@ public: class LR final : public ServiceFramework<LR> { public: - explicit LR() : ServiceFramework{"lr"} { + explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenLocationResolver"}, @@ -105,7 +107,7 @@ public: class NCM final : public ServiceFramework<NCM> { public: - explicit NCM() : ServiceFramework{"ncm"} { + explicit NCM(Core::System& system_) : ServiceFramework{system_, "ncm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateContentStorage"}, @@ -130,9 +132,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<LR>()->InstallAsService(sm); - std::make_shared<NCM>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<LR>(system)->InstallAsService(sm); + std::make_shared<NCM>(system)->InstallAsService(sm); } } // namespace Service::NCM diff --git a/src/core/hle/service/ncm/ncm.h b/src/core/hle/service/ncm/ncm.h index 7bc8518a6..ee01eddc0 100644 --- a/src/core/hle/service/ncm/ncm.h +++ b/src/core/hle/service/ncm/ncm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NCM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NCM diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 780ea30fe..6ab35de47 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -16,7 +16,7 @@ namespace Service::NFC { class IAm final : public ServiceFramework<IAm> { public: - explicit IAm() : ServiceFramework{"NFC::IAm"} { + explicit IAm(Core::System& system_) : ServiceFramework{system_, "NFC::IAm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -31,7 +31,7 @@ public: class NFC_AM final : public ServiceFramework<NFC_AM> { public: - explicit NFC_AM() : ServiceFramework{"nfc:am"} { + explicit NFC_AM(Core::System& system_) : ServiceFramework{system_, "nfc:am"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_AM::CreateAmInterface, "CreateAmInterface"}, @@ -47,13 +47,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAm>(); + rb.PushIpcInterface<IAm>(system); } }; class MFIUser final : public ServiceFramework<MFIUser> { public: - explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} { + explicit MFIUser(Core::System& system_) : ServiceFramework{system_, "NFC::MFIUser"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -79,7 +79,7 @@ public: class NFC_MF_U final : public ServiceFramework<NFC_MF_U> { public: - explicit NFC_MF_U() : ServiceFramework{"nfc:mf:u"} { + explicit NFC_MF_U(Core::System& system_) : ServiceFramework{system_, "nfc:mf:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"}, @@ -95,13 +95,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<MFIUser>(); + rb.PushIpcInterface<MFIUser>(system); } }; class IUser final : public ServiceFramework<IUser> { public: - explicit IUser() : ServiceFramework{"NFC::IUser"} { + explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} { // clang-format off static const FunctionInfo functions[] = { {0, &IUser::InitializeOld, "InitializeOld"}, @@ -171,7 +171,7 @@ private: class NFC_U final : public ServiceFramework<NFC_U> { public: - explicit NFC_U() : ServiceFramework{"nfc:user"} { + explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_U::CreateUserInterface, "CreateUserInterface"}, @@ -187,13 +187,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IUser>(); + rb.PushIpcInterface<IUser>(system); } }; class ISystem final : public ServiceFramework<ISystem> { public: - explicit ISystem() : ServiceFramework{"ISystem"} { + explicit ISystem(Core::System& system_) : ServiceFramework{system_, "ISystem"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -230,7 +230,7 @@ public: class NFC_SYS final : public ServiceFramework<NFC_SYS> { public: - explicit NFC_SYS() : ServiceFramework{"nfc:sys"} { + explicit NFC_SYS(Core::System& system_) : ServiceFramework{system_, "nfc:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"}, @@ -246,15 +246,15 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystem>(); + rb.PushIpcInterface<ISystem>(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<NFC_AM>()->InstallAsService(sm); - std::make_shared<NFC_MF_U>()->InstallAsService(sm); - std::make_shared<NFC_U>()->InstallAsService(sm); - std::make_shared<NFC_SYS>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<NFC_AM>(system)->InstallAsService(sm); + std::make_shared<NFC_MF_U>(system)->InstallAsService(sm); + std::make_shared<NFC_U>(system)->InstallAsService(sm); + std::make_shared<NFC_SYS>(system)->InstallAsService(sm); } } // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index 4d2d815f9..5a94b076d 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NFC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NFC diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 5e2d769a4..2d1d4d67f 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -2,15 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <atomic> #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/lock.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp_user.h" @@ -20,18 +22,20 @@ namespace ErrCodes { constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152); } // namespace ErrCodes -Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) { +Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} { auto& kernel = system.Kernel(); - nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, "IUser:NFCTagDetected"); + nfc_tag_load = Kernel::KEvent::Create(kernel, "IUser:NFCTagDetected"); + nfc_tag_load->Initialize(); } Module::Interface::~Interface() = default; class IUser final : public ServiceFramework<IUser> { public: - IUser(Module::Interface& nfp_interface, Core::System& system) - : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) { + explicit IUser(Module::Interface& nfp_interface_, Core::System& system_) + : ServiceFramework{system_, "NFP::IUser"}, nfp_interface{nfp_interface_} { static const FunctionInfo functions[] = { {0, &IUser::Initialize, "Initialize"}, {1, &IUser::Finalize, "Finalize"}, @@ -62,9 +66,10 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - deactivate_event = Kernel::WritableEvent::CreateEventPair(kernel, "IUser:DeactivateEvent"); - availability_change_event = - Kernel::WritableEvent::CreateEventPair(kernel, "IUser:AvailabilityChangeEvent"); + deactivate_event = Kernel::KEvent::Create(kernel, "IUser:DeactivateEvent"); + deactivate_event->Initialize(); + availability_change_event = Kernel::KEvent::Create(kernel, "IUser:AvailabilityChangeEvent"); + availability_change_event->Initialize(); } private: @@ -72,10 +77,10 @@ private: std::array<u8, 10> uuid; u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it // mean something else - INSERT_PADDING_BYTES(0x15); + std::array<u8, 0x15> padding_1; u32_le protocol; u32_le tag_type; - INSERT_PADDING_BYTES(0x2c); + std::array<u8, 0x2c> padding_2; }; static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size"); @@ -162,7 +167,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(deactivate_event.readable); + rb.PushCopyObjects(deactivate_event->GetReadableEvent()); } void StopDetection(Kernel::HLERequestContext& ctx) { @@ -171,7 +176,7 @@ private: switch (device_state) { case DeviceState::TagFound: case DeviceState::TagNearby: - deactivate_event.writable->Signal(); + deactivate_event->GetWritableEvent()->Signal(); device_state = DeviceState::Initialized; break; case DeviceState::SearchingForTag: @@ -188,12 +193,6 @@ private: void GetDeviceState(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); - auto nfc_event = nfp_interface.GetNFCEvent(); - if (!nfc_event->ShouldWait(&ctx.GetThread()) && !has_attached_handle) { - device_state = DeviceState::TagFound; - nfc_event->Clear(); - } - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(device_state)); @@ -213,13 +212,15 @@ private: LOG_DEBUG(Service_NFP, "called"); IPC::ResponseBuilder rb{ctx, 2}; - auto amiibo = nfp_interface.GetAmiiboBuffer(); - TagInfo tag_info{}; - tag_info.uuid = amiibo.uuid; - tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size()); - - tag_info.protocol = 1; // TODO(ogniK): Figure out actual values - tag_info.tag_type = 2; + const auto& amiibo = nfp_interface.GetAmiiboBuffer(); + const TagInfo tag_info{ + .uuid = amiibo.uuid, + .uuid_length = static_cast<u8>(amiibo.uuid.size()), + .padding_1 = {}, + .protocol = 1, // TODO(ogniK): Figure out actual values + .tag_type = 2, + .padding_2 = {}, + }; ctx.WriteBuffer(tag_info); rb.Push(RESULT_SUCCESS); } @@ -236,7 +237,7 @@ private: LOG_DEBUG(Service_NFP, "called"); IPC::ResponseBuilder rb{ctx, 2}; - auto amiibo = nfp_interface.GetAmiiboBuffer(); + const auto& amiibo = nfp_interface.GetAmiiboBuffer(); ctx.WriteBuffer(amiibo.model_info); rb.Push(RESULT_SUCCESS); } @@ -264,7 +265,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(availability_change_event.readable); + rb.PushCopyObjects(availability_change_event->GetReadableEvent()); } void GetRegisterInfo(Kernel::HLERequestContext& ctx) { @@ -318,8 +319,8 @@ private: const u32 npad_id{0}; // Player 1 controller State state{State::NonInitialized}; DeviceState device_state{DeviceState::Initialized}; - Kernel::EventPair deactivate_event; - Kernel::EventPair availability_change_event; + std::shared_ptr<Kernel::KEvent> deactivate_event; + std::shared_ptr<Kernel::KEvent> availability_change_event; const Module::Interface& nfp_interface; }; @@ -338,12 +339,12 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { } std::memcpy(&amiibo, buffer.data(), sizeof(amiibo)); - nfc_tag_load.writable->Signal(); + nfc_tag_load->GetWritableEvent()->Signal(); return true; } -const std::shared_ptr<Kernel::ReadableEvent>& Module::Interface::GetNFCEvent() const { - return nfc_tag_load.readable; +const std::shared_ptr<Kernel::KReadableEvent>& Module::Interface::GetNFCEvent() const { + return nfc_tag_load->GetReadableEvent(); } const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const { diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 200013795..c46551760 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -6,17 +6,21 @@ #include <array> #include <vector> -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" + #include "core/hle/service/service.h" +namespace Kernel { +class KEvent; +} + namespace Service::NFP { class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name); ~Interface() override; struct ModelInfo { @@ -34,16 +38,15 @@ public: void CreateUserInterface(Kernel::HLERequestContext& ctx); bool LoadAmiibo(const std::vector<u8>& buffer); - const std::shared_ptr<Kernel::ReadableEvent>& GetNFCEvent() const; + const std::shared_ptr<Kernel::KReadableEvent>& GetNFCEvent() const; const AmiiboFile& GetAmiiboBuffer() const; private: - Kernel::EventPair nfc_tag_load{}; + std::shared_ptr<Kernel::KEvent> nfc_tag_load; AmiiboFile amiibo{}; protected: std::shared_ptr<Module> module; - Core::System& system; }; }; diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index 298184f17..10b0ef944 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -6,8 +6,8 @@ namespace Service::NFP { -NFP_User::NFP_User(std::shared_ptr<Module> module, Core::System& system) - : Module::Interface(std::move(module), system, "nfp:user") { +NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_) + : Interface(std::move(module_), system_, "nfp:user") { static const FunctionInfo functions[] = { {0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, }; diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h index 1686ebf20..7f3c124f6 100644 --- a/src/core/hle/service/nfp/nfp_user.h +++ b/src/core/hle/service/nfp/nfp_user.h @@ -10,7 +10,7 @@ namespace Service::NFP { class NFP_User final : public Module::Interface { public: - explicit NFP_User(std::shared_ptr<Module> module, Core::System& system); + explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_); ~NFP_User() override; }; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 01ddcdbd6..afb3342d6 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -4,11 +4,12 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/service.h" +#include "core/network/network.h" #include "core/settings.h" namespace Service::NIFM { @@ -20,9 +21,96 @@ enum class RequestState : u32 { Connected = 3, }; +struct IpAddressSetting { + bool is_automatic{}; + Network::IPv4Address current_address{}; + Network::IPv4Address subnet_mask{}; + Network::IPv4Address gateway{}; +}; +static_assert(sizeof(IpAddressSetting) == 0xD, "IpAddressSetting has incorrect size."); + +struct DnsSetting { + bool is_automatic{}; + Network::IPv4Address primary_dns{}; + Network::IPv4Address secondary_dns{}; +}; +static_assert(sizeof(DnsSetting) == 0x9, "DnsSetting has incorrect size."); + +struct ProxySetting { + bool enabled{}; + INSERT_PADDING_BYTES(1); + u16 port{}; + std::array<char, 0x64> proxy_server{}; + bool automatic_auth_enabled{}; + std::array<char, 0x20> user{}; + std::array<char, 0x20> password{}; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(ProxySetting) == 0xAA, "ProxySetting has incorrect size."); + +struct IpSettingData { + IpAddressSetting ip_address_setting{}; + DnsSetting dns_setting{}; + ProxySetting proxy_setting{}; + u16 mtu{}; +}; +static_assert(sizeof(IpSettingData) == 0xC2, "IpSettingData has incorrect size."); + +struct SfWirelessSettingData { + u8 ssid_length{}; + std::array<char, 0x20> ssid{}; + u8 unknown_1{}; + u8 unknown_2{}; + u8 unknown_3{}; + std::array<char, 0x41> passphrase{}; +}; +static_assert(sizeof(SfWirelessSettingData) == 0x65, "SfWirelessSettingData has incorrect size."); + +struct NifmWirelessSettingData { + u8 ssid_length{}; + std::array<char, 0x21> ssid{}; + u8 unknown_1{}; + INSERT_PADDING_BYTES(1); + u32 unknown_2{}; + u32 unknown_3{}; + std::array<char, 0x41> passphrase{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(NifmWirelessSettingData) == 0x70, + "NifmWirelessSettingData has incorrect size."); + +#pragma pack(push, 1) +struct SfNetworkProfileData { + IpSettingData ip_setting_data{}; + u128 uuid{}; + std::array<char, 0x40> network_name{}; + u8 unknown_1{}; + u8 unknown_2{}; + u8 unknown_3{}; + u8 unknown_4{}; + SfWirelessSettingData wireless_setting_data{}; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(SfNetworkProfileData) == 0x17C, "SfNetworkProfileData has incorrect size."); + +struct NifmNetworkProfileData { + u128 uuid{}; + std::array<char, 0x40> network_name{}; + u32 unknown_1{}; + u32 unknown_2{}; + u8 unknown_3{}; + u8 unknown_4{}; + INSERT_PADDING_BYTES(2); + NifmWirelessSettingData wireless_setting_data{}; + IpSettingData ip_setting_data{}; +}; +static_assert(sizeof(NifmNetworkProfileData) == 0x18E, + "NifmNetworkProfileData has incorrect size."); +#pragma pack(pop) + class IScanRequest final : public ServiceFramework<IScanRequest> { public: - explicit IScanRequest() : ServiceFramework("IScanRequest") { + explicit IScanRequest(Core::System& system_) : ServiceFramework{system_, "IScanRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Submit"}, @@ -39,7 +127,7 @@ public: class IRequest final : public ServiceFramework<IRequest> { public: - explicit IRequest(Core::System& system) : ServiceFramework("IRequest") { + explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} { static const FunctionInfo functions[] = { {0, &IRequest::GetRequestState, "GetRequestState"}, {1, &IRequest::GetResult, "GetResult"}, @@ -61,7 +149,7 @@ public: {18, nullptr, "SetRequirementByRevision"}, {19, nullptr, "GetRequirement"}, {20, nullptr, "GetRevision"}, - {21, nullptr, "GetAppletInfo"}, + {21, &IRequest::GetAppletInfo, "GetAppletInfo"}, {22, nullptr, "GetAdditionalInfo"}, {23, nullptr, "SetKeptInSleep"}, {24, nullptr, "RegisterSocketDescriptor"}, @@ -70,8 +158,11 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - event1 = Kernel::WritableEvent::CreateEventPair(kernel, "IRequest:Event1"); - event2 = Kernel::WritableEvent::CreateEventPair(kernel, "IRequest:Event2"); + + event1 = Kernel::KEvent::Create(kernel, "IRequest:Event1"); + event1->Initialize(); + event2 = Kernel::KEvent::Create(kernel, "IRequest:Event2"); + event2->Initialize(); } private: @@ -107,7 +198,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 2}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(event1.readable, event2.readable); + rb.PushCopyObjects(event1->GetReadableEvent(), event2->GetReadableEvent()); } void Cancel(Kernel::HLERequestContext& ctx) { @@ -124,12 +215,26 @@ private: rb.Push(RESULT_SUCCESS); } - Kernel::EventPair event1, event2; + void GetAppletInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + std::vector<u8> out_buffer(ctx.GetWriteBufferSize()); + + ctx.WriteBuffer(out_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + rb.Push<u32>(0); + rb.Push<u32>(0); + } + + std::shared_ptr<Kernel::KEvent> event1, event2; }; class INetworkProfile final : public ServiceFramework<INetworkProfile> { public: - explicit INetworkProfile() : ServiceFramework("INetworkProfile") { + explicit INetworkProfile(Core::System& system_) : ServiceFramework{system_, "INetworkProfile"} { static const FunctionInfo functions[] = { {0, nullptr, "Update"}, {1, nullptr, "PersistOld"}, @@ -141,7 +246,7 @@ public: class IGeneralService final : public ServiceFramework<IGeneralService> { public: - IGeneralService(Core::System& system); + explicit IGeneralService(Core::System& system_); private: void GetClientId(Kernel::HLERequestContext& ctx) { @@ -158,7 +263,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IScanRequest>(); + rb.PushIpcInterface<IScanRequest>(system); } void CreateRequest(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -168,12 +273,62 @@ private: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IRequest>(system); } + void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + const SfNetworkProfileData network_profile_data{ + .ip_setting_data{ + .ip_address_setting{ + .is_automatic{true}, + .current_address{192, 168, 1, 100}, + .subnet_mask{255, 255, 255, 0}, + .gateway{192, 168, 1, 1}, + }, + .dns_setting{ + .is_automatic{true}, + .primary_dns{1, 1, 1, 1}, + .secondary_dns{1, 0, 0, 1}, + }, + .proxy_setting{ + .enabled{false}, + .port{}, + .proxy_server{}, + .automatic_auth_enabled{}, + .user{}, + .password{}, + }, + .mtu{1500}, + }, + .uuid{0xdeadbeef, 0xdeadbeef}, + .network_name{"yuzu Network"}, + .wireless_setting_data{ + .ssid_length{12}, + .ssid{"yuzu Network"}, + .passphrase{"yuzupassword"}, + }, + }; + + ctx.WriteBuffer(network_profile_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + const auto [ipv4, error] = Network::GetHostIPv4Address(); + UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(ipv4); + } void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -186,9 +341,37 @@ private: IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<INetworkProfile>(); + rb.PushIpcInterface<INetworkProfile>(system); rb.PushRaw<u128>(uuid); } + void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + struct IpConfigInfo { + IpAddressSetting ip_address_setting; + DnsSetting dns_setting; + }; + static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), + "IpConfigInfo has incorrect size."); + + const IpConfigInfo ip_config_info{ + .ip_address_setting{ + .is_automatic{true}, + .current_address{192, 168, 1, 100}, + .subnet_mask{255, 255, 255, 0}, + .gateway{192, 168, 1, 1}, + }, + .dns_setting{ + .is_automatic{true}, + .primary_dns{1, 1, 1, 1}, + .secondary_dns{1, 0, 0, 1}, + }, + }; + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(IpConfigInfo) / sizeof(u32)}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<IpConfigInfo>(ip_config_info); + } void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -218,27 +401,26 @@ private: rb.Push<u8>(1); } } - Core::System& system; }; -IGeneralService::IGeneralService(Core::System& system) - : ServiceFramework("IGeneralService"), system(system) { +IGeneralService::IGeneralService(Core::System& system_) + : ServiceFramework{system_, "IGeneralService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"}, {4, &IGeneralService::CreateRequest, "CreateRequest"}, - {5, nullptr, "GetCurrentNetworkProfile"}, + {5, &IGeneralService::GetCurrentNetworkProfile, "GetCurrentNetworkProfile"}, {6, nullptr, "EnumerateNetworkInterfaces"}, {7, nullptr, "EnumerateNetworkProfiles"}, {8, nullptr, "GetNetworkProfile"}, {9, nullptr, "SetNetworkProfile"}, {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"}, {11, nullptr, "GetScanDataOld"}, - {12, nullptr, "GetCurrentIpAddress"}, + {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"}, {13, nullptr, "GetCurrentAccessPointOld"}, {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"}, - {15, nullptr, "GetCurrentIpConfigInfo"}, + {15, &IGeneralService::GetCurrentIpConfigInfo, "GetCurrentIpConfigInfo"}, {16, nullptr, "SetWirelessCommunicationEnabled"}, {17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"}, {18, nullptr, "GetInternetConnectionStatus"}, @@ -275,8 +457,8 @@ IGeneralService::IGeneralService(Core::System& system) class NetworkInterface final : public ServiceFramework<NetworkInterface> { public: - explicit NetworkInterface(const char* name, Core::System& system) - : ServiceFramework{name}, system(system) { + explicit NetworkInterface(const char* name, Core::System& system_) + : ServiceFramework{system_, name} { static const FunctionInfo functions[] = { {4, &NetworkInterface::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NetworkInterface::CreateGeneralService, "CreateGeneralService"}, @@ -284,6 +466,7 @@ public: RegisterHandlers(functions); } +private: void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -299,9 +482,6 @@ public: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IGeneralService>(system); } - -private: - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 6857e18f9..c3dd4f386 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::NIFM { /// Registers all NIFM services with the specified service manager. diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 11aa74828..f3be0b878 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -6,9 +6,10 @@ #include <ctime> #include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -17,7 +18,8 @@ namespace Service::NIM { class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> { public: - IShopServiceAsync() : ServiceFramework("IShopServiceAsync") { + explicit IShopServiceAsync(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAsync"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Cancel"}, @@ -35,7 +37,8 @@ public: class IShopServiceAccessor final : public ServiceFramework<IShopServiceAccessor> { public: - IShopServiceAccessor() : ServiceFramework("IShopServiceAccessor") { + explicit IShopServiceAccessor(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAccessor"} { // clang-format off static const FunctionInfo functions[] = { {0, &IShopServiceAccessor::CreateAsyncInterface, "CreateAsyncInterface"}, @@ -50,13 +53,14 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IShopServiceAsync>(); + rb.PushIpcInterface<IShopServiceAsync>(system); } }; class IShopServiceAccessServer final : public ServiceFramework<IShopServiceAccessServer> { public: - IShopServiceAccessServer() : ServiceFramework("IShopServiceAccessServer") { + explicit IShopServiceAccessServer(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAccessServer"} { // clang-format off static const FunctionInfo functions[] = { {0, &IShopServiceAccessServer::CreateAccessorInterface, "CreateAccessorInterface"}, @@ -71,13 +75,13 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IShopServiceAccessor>(); + rb.PushIpcInterface<IShopServiceAccessor>(system); } }; class NIM final : public ServiceFramework<NIM> { public: - explicit NIM() : ServiceFramework{"nim"} { + explicit NIM(Core::System& system_) : ServiceFramework{system_, "nim"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateSystemUpdateTask"}, @@ -207,14 +211,14 @@ public: class NIM_ECA final : public ServiceFramework<NIM_ECA> { public: - explicit NIM_ECA() : ServiceFramework{"nim:eca"} { + explicit NIM_ECA(Core::System& system_) : ServiceFramework{system_, "nim:eca"} { // clang-format off static const FunctionInfo functions[] = { {0, &NIM_ECA::CreateServerInterface, "CreateServerInterface"}, {1, nullptr, "RefreshDebugAvailability"}, {2, nullptr, "ClearDebugResponse"}, {3, nullptr, "RegisterDebugResponse"}, - {4, nullptr, "IsLargeResourceAvailable"}, + {4, &NIM_ECA::IsLargeResourceAvailable, "IsLargeResourceAvailable"}, }; // clang-format on @@ -226,13 +230,25 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IShopServiceAccessServer>(); + rb.PushIpcInterface<IShopServiceAccessServer>(system); + } + + void IsLargeResourceAvailable(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown{rp.Pop<u64>()}; + + LOG_INFO(Service_NIM, "(STUBBED) called, unknown={}", unknown); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); } }; class NIM_SHP final : public ServiceFramework<NIM_SHP> { public: - explicit NIM_SHP() : ServiceFramework{"nim:shp"} { + explicit NIM_SHP(Core::System& system_) : ServiceFramework{system_, "nim:shp"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestDeviceAuthenticationToken"}, @@ -272,8 +288,8 @@ public: class IEnsureNetworkClockAvailabilityService final : public ServiceFramework<IEnsureNetworkClockAvailabilityService> { public: - explicit IEnsureNetworkClockAvailabilityService(Core::System& system) - : ServiceFramework("IEnsureNetworkClockAvailabilityService") { + explicit IEnsureNetworkClockAvailabilityService(Core::System& system_) + : ServiceFramework{system_, "IEnsureNetworkClockAvailabilityService"} { static const FunctionInfo functions[] = { {0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"}, {1, &IEnsureNetworkClockAvailabilityService::GetFinishNotificationEvent, @@ -286,17 +302,18 @@ public: RegisterHandlers(functions); auto& kernel = system.Kernel(); - finished_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IEnsureNetworkClockAvailabilityService:FinishEvent"); + finished_event = + Kernel::KEvent::Create(kernel, "IEnsureNetworkClockAvailabilityService:FinishEvent"); + finished_event->Initialize(); } private: - Kernel::EventPair finished_event; + std::shared_ptr<Kernel::KEvent> finished_event; void StartTask(Kernel::HLERequestContext& ctx) { // No need to connect to the internet, just finish the task straight away. LOG_DEBUG(Service_NIM, "called"); - finished_event.writable->Signal(); + finished_event->GetWritableEvent()->Signal(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -306,7 +323,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(finished_event.readable); + rb.PushCopyObjects(finished_event->GetReadableEvent()); } void GetResult(Kernel::HLERequestContext& ctx) { @@ -318,7 +335,7 @@ private: void Cancel(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIM, "called"); - finished_event.writable->Clear(); + finished_event->GetWritableEvent()->Clear(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -345,7 +362,7 @@ private: class NTC final : public ServiceFramework<NTC> { public: - explicit NTC(Core::System& system) : ServiceFramework{"ntc"}, system(system) { + explicit NTC(Core::System& system_) : ServiceFramework{system_, "ntc"} { // clang-format off static const FunctionInfo functions[] = { {0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"}, @@ -380,13 +397,12 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared<NIM>()->InstallAsService(sm); - std::make_shared<NIM_ECA>()->InstallAsService(sm); - std::make_shared<NIM_SHP>()->InstallAsService(sm); + std::make_shared<NIM>(system)->InstallAsService(sm); + std::make_shared<NIM_ECA>(system)->InstallAsService(sm); + std::make_shared<NIM_SHP>(system)->InstallAsService(sm); std::make_shared<NTC>(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h index dbe25dc01..571153fe6 100644 --- a/src/core/hle/service/nim/nim.h +++ b/src/core/hle/service/nim/nim.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::NIM { void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp index 8fa16fb08..f7a58f659 100644 --- a/src/core/hle/service/npns/npns.cpp +++ b/src/core/hle/service/npns/npns.cpp @@ -12,7 +12,7 @@ namespace Service::NPNS { class NPNS_S final : public ServiceFramework<NPNS_S> { public: - explicit NPNS_S() : ServiceFramework{"npns:s"} { + explicit NPNS_S(Core::System& system_) : ServiceFramework{system_, "npns:s"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -62,7 +62,7 @@ public: class NPNS_U final : public ServiceFramework<NPNS_U> { public: - explicit NPNS_U() : ServiceFramework{"npns:u"} { + explicit NPNS_U(Core::System& system_) : ServiceFramework{system_, "npns:u"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -91,9 +91,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<NPNS_S>()->InstallAsService(sm); - std::make_shared<NPNS_U>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<NPNS_S>(system)->InstallAsService(sm); + std::make_shared<NPNS_U>(system)->InstallAsService(sm); } } // namespace Service::NPNS diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h index 861cd3e48..3b7596b6b 100644 --- a/src/core/hle/service/npns/npns.h +++ b/src/core/hle/service/npns/npns.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NPNS { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NPNS diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 886450be2..6ccf8995c 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -3,8 +3,10 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/ns/errors.h" @@ -16,7 +18,8 @@ namespace Service::NS { -IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} { +IAccountProxyInterface::IAccountProxyInterface(Core::System& system_) + : ServiceFramework{system_, "IAccountProxyInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateUserAccount"}, @@ -28,8 +31,8 @@ IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountPro IAccountProxyInterface::~IAccountProxyInterface() = default; -IApplicationManagerInterface::IApplicationManagerInterface() - : ServiceFramework{"IApplicationManagerInterface"} { +IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_) + : ServiceFramework{system_, "IApplicationManagerInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ListApplicationRecord"}, @@ -297,7 +300,8 @@ void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestC const auto size = ctx.GetWriteBufferSize(); - const FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); std::vector<u8> out; @@ -425,8 +429,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag return MakeResult(static_cast<u64>(*language_code)); } -IApplicationVersionInterface::IApplicationVersionInterface() - : ServiceFramework{"IApplicationVersionInterface"} { +IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_) + : ServiceFramework{system_, "IApplicationVersionInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLaunchRequiredVersion"}, @@ -446,8 +450,8 @@ IApplicationVersionInterface::IApplicationVersionInterface() IApplicationVersionInterface::~IApplicationVersionInterface() = default; -IContentManagementInterface::IContentManagementInterface() - : ServiceFramework{"IContentManagementInterface"} { +IContentManagementInterface::IContentManagementInterface(Core::System& system_) + : ServiceFramework{system_, "IContentManagementInterface"} { // clang-format off static const FunctionInfo functions[] = { {11, nullptr, "CalculateApplicationOccupiedSize"}, @@ -466,7 +470,8 @@ IContentManagementInterface::IContentManagementInterface() IContentManagementInterface::~IContentManagementInterface() = default; -IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} { +IDocumentInterface::IDocumentInterface(Core::System& system_) + : ServiceFramework{system_, "IDocumentInterface"} { // clang-format off static const FunctionInfo functions[] = { {21, nullptr, "GetApplicationContentPath"}, @@ -480,7 +485,8 @@ IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface" IDocumentInterface::~IDocumentInterface() = default; -IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} { +IDownloadTaskInterface::IDownloadTaskInterface(Core::System& system_) + : ServiceFramework{system_, "IDownloadTaskInterface"} { // clang-format off static const FunctionInfo functions[] = { {701, nullptr, "ClearTaskStatusList"}, @@ -500,7 +506,8 @@ IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTa IDownloadTaskInterface::~IDownloadTaskInterface() = default; -IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterface"} { +IECommerceInterface::IECommerceInterface(Core::System& system_) + : ServiceFramework{system_, "IECommerceInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestLinkDevice"}, @@ -518,8 +525,8 @@ IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterfa IECommerceInterface::~IECommerceInterface() = default; -IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface() - : ServiceFramework{"IFactoryResetInterface"} { +IFactoryResetInterface::IFactoryResetInterface(Core::System& system_) + : ServiceFramework{system_, "IFactoryResetInterface"} { // clang-format off static const FunctionInfo functions[] = { {100, nullptr, "ResetToFactorySettings"}, @@ -537,14 +544,14 @@ IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface() IFactoryResetInterface::~IFactoryResetInterface() = default; -NS::NS(const char* name) : ServiceFramework{name} { +NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"}, {7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"}, {7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"}, {7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"}, - {7996, &NS::PushInterface<IApplicationManagerInterface>, "GetApplicationManagerInterface"}, + {7996, &NS::PushIApplicationManagerInterface, "GetApplicationManagerInterface"}, {7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"}, {7998, &NS::PushInterface<IContentManagementInterface>, "GetContentManagementInterface"}, {7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"}, @@ -557,12 +564,12 @@ NS::NS(const char* name) : ServiceFramework{name} { NS::~NS() = default; std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const { - return GetInterface<IApplicationManagerInterface>(); + return GetInterface<IApplicationManagerInterface>(system); } class NS_DEV final : public ServiceFramework<NS_DEV> { public: - explicit NS_DEV() : ServiceFramework{"ns:dev"} { + explicit NS_DEV(Core::System& system_) : ServiceFramework{system_, "ns:dev"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LaunchProgram"}, @@ -589,7 +596,8 @@ public: class ISystemUpdateControl final : public ServiceFramework<ISystemUpdateControl> { public: - explicit ISystemUpdateControl() : ServiceFramework{"ISystemUpdateControl"} { + explicit ISystemUpdateControl(Core::System& system_) + : ServiceFramework{system_, "ISystemUpdateControl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "HasDownloaded"}, @@ -624,7 +632,7 @@ public: class NS_SU final : public ServiceFramework<NS_SU> { public: - explicit NS_SU() : ServiceFramework{"ns:su"} { + explicit NS_SU(Core::System& system_) : ServiceFramework{system_, "ns:su"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetBackgroundNetworkUpdateState"}, @@ -656,16 +664,16 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemUpdateControl>(); + rb.PushIpcInterface<ISystemUpdateControl>(system); } }; class NS_VM final : public ServiceFramework<NS_VM> { public: - explicit NS_VM() : ServiceFramework{"ns:vm"} { + explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} { // clang-format off static const FunctionInfo functions[] = { - {1200, nullptr, "NeedsUpdateVulnerability"}, + {1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"}, {1201, nullptr, "UpdateSafeSystemVersionForDebug"}, {1202, nullptr, "GetSafeSystemVersion"}, }; @@ -673,19 +681,28 @@ public: RegisterHandlers(functions); } + +private: + void NeedsUpdateVulnerability(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); + } }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - std::make_shared<NS>("ns:am2")->InstallAsService(service_manager); - std::make_shared<NS>("ns:ec")->InstallAsService(service_manager); - std::make_shared<NS>("ns:rid")->InstallAsService(service_manager); - std::make_shared<NS>("ns:rt")->InstallAsService(service_manager); - std::make_shared<NS>("ns:web")->InstallAsService(service_manager); + std::make_shared<NS>("ns:am2", system)->InstallAsService(service_manager); + std::make_shared<NS>("ns:ec", system)->InstallAsService(service_manager); + std::make_shared<NS>("ns:rid", system)->InstallAsService(service_manager); + std::make_shared<NS>("ns:rt", system)->InstallAsService(service_manager); + std::make_shared<NS>("ns:web", system)->InstallAsService(service_manager); - std::make_shared<NS_DEV>()->InstallAsService(service_manager); - std::make_shared<NS_SU>()->InstallAsService(service_manager); - std::make_shared<NS_VM>()->InstallAsService(service_manager); + std::make_shared<NS_DEV>(system)->InstallAsService(service_manager); + std::make_shared<NS_SU>(system)->InstallAsService(service_manager); + std::make_shared<NS_VM>(system)->InstallAsService(service_manager); std::make_shared<PL_U>(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index c2554b878..991271f3e 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service { namespace FileSystem { @@ -16,13 +20,13 @@ namespace NS { class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> { public: - explicit IAccountProxyInterface(); + explicit IAccountProxyInterface(Core::System& system_); ~IAccountProxyInterface() override; }; class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> { public: - explicit IApplicationManagerInterface(); + explicit IApplicationManagerInterface(Core::System& system_); ~IApplicationManagerInterface() override; ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages); @@ -36,63 +40,71 @@ private: class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> { public: - explicit IApplicationVersionInterface(); + explicit IApplicationVersionInterface(Core::System& system_); ~IApplicationVersionInterface() override; }; class IContentManagementInterface final : public ServiceFramework<IContentManagementInterface> { public: - explicit IContentManagementInterface(); + explicit IContentManagementInterface(Core::System& system_); ~IContentManagementInterface() override; }; class IDocumentInterface final : public ServiceFramework<IDocumentInterface> { public: - explicit IDocumentInterface(); + explicit IDocumentInterface(Core::System& system_); ~IDocumentInterface() override; }; class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> { public: - explicit IDownloadTaskInterface(); + explicit IDownloadTaskInterface(Core::System& system_); ~IDownloadTaskInterface() override; }; class IECommerceInterface final : public ServiceFramework<IECommerceInterface> { public: - explicit IECommerceInterface(); + explicit IECommerceInterface(Core::System& system_); ~IECommerceInterface() override; }; class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> { public: - explicit IFactoryResetInterface(); + explicit IFactoryResetInterface(Core::System& system_); ~IFactoryResetInterface() override; }; class NS final : public ServiceFramework<NS> { public: - explicit NS(const char* name); + explicit NS(const char* name, Core::System& system_); ~NS() override; std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const; private: - template <typename T> + template <typename T, typename... Args> void PushInterface(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<T>(); + rb.PushIpcInterface<T>(system); + } + + void PushIApplicationManagerInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IApplicationManagerInterface>(system); } - template <typename T> - std::shared_ptr<T> GetInterface() const { + template <typename T, typename... Args> + std::shared_ptr<T> GetInterface(Args&&... args) const { static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>, "Not a base of ServiceFrameworkBase"); - return std::make_shared<T>(); + return std::make_shared<T>(std::forward<Args>(args)...); } }; diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 40838a225..b6ac0a81a 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -27,42 +27,14 @@ namespace Service::NS { -enum class FontArchives : u64 { - Extension = 0x0100000000000810, - Standard = 0x0100000000000811, - Korean = 0x0100000000000812, - ChineseTraditional = 0x0100000000000813, - ChineseSimple = 0x0100000000000814, -}; - struct FontRegion { u32 offset; u32 size; }; -constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ - std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), - std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), -}; - -constexpr std::array<const char*, 7> SHARED_FONTS_TTF{ - "FontStandard.ttf", - "FontChineseSimplified.ttf", - "FontExtendedChineseSimplified.ttf", - "FontChineseTraditional.ttf", - "FontKorean.ttf", - "FontNintendoExtended.ttf", - "FontNintendoExtended2.ttf", -}; - // The below data is specific to shared font data dumped from Switch on f/w 2.2 // Virtual address and offsets/sizes likely will vary by dump -constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; @@ -90,6 +62,23 @@ static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMem offset += transformed_font.size() * sizeof(u32); } +void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) { + ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); + + if (input.size() < 2) { + LOG_ERROR(Service_NS, "Input font is empty"); + return; + } + + const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor + std::vector<u32> transformed_font(input.size()); + // TODO(ogniK): Figure out a better way to do this + std::transform(input.begin(), input.end(), transformed_font.begin(), + [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + std::memcpy(output.data(), transformed_font.data() + 2, + (transformed_font.size() - 2) * sizeof(u32)); +} + void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset) { ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, @@ -151,8 +140,8 @@ struct PL_U::Impl { std::vector<FontRegion> shared_font_regions; }; -PL_U::PL_U(Core::System& system) - : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()}, system(system) { +PL_U::PL_U(Core::System& system_) + : ServiceFramework{system_, "pl:u"}, impl{std::make_unique<Impl>()} { // clang-format off static const FunctionInfo functions[] = { {0, &PL_U::RequestLoad, "RequestLoad"}, @@ -192,21 +181,18 @@ PL_U::PL_U(Core::System& system) } if (!romfs) { - LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", - static_cast<u64>(font.first)); + LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first); continue; } const auto extracted_romfs = FileSys::ExtractRomFS(romfs); if (!extracted_romfs) { - LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", - static_cast<u64>(font.first)); + LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); continue; } const auto font_fp = extracted_romfs->GetFile(font.second); if (!font_fp) { - LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", - static_cast<u64>(font.first), font.second); + LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); continue; } std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32)); diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index 27161bd7a..f920c7f69 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -16,11 +16,30 @@ class FileSystemController; namespace NS { +enum class FontArchives : u64 { + Extension = 0x0100000000000810, + Standard = 0x0100000000000811, + Korean = 0x0100000000000812, + ChineseTraditional = 0x0100000000000813, + ChineseSimple = 0x0100000000000814, +}; + +constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ + std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), + std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), +}; + +void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output); void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset); class PL_U final : public ServiceFramework<PL_U> { public: - explicit PL_U(Core::System& system); + explicit PL_U(Core::System& system_); ~PL_U() override; private: @@ -33,7 +52,6 @@ private: struct Impl; std::unique_ptr<Impl> impl; - Core::System& system; }; } // namespace NS diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 0240d6643..5681599ba 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -24,25 +24,37 @@ public: explicit nvdevice(Core::System& system) : system{system} {} virtual ~nvdevice() = default; - union Ioctl { - u32_le raw; - BitField<0, 8, u32> cmd; - BitField<8, 8, u32> group; - BitField<16, 14, u32> length; - BitField<30, 1, u32> is_in; - BitField<31, 1, u32> is_out; - }; + /** + * Handles an ioctl1 request. + * @param command The ioctl command id. + * @param input A buffer containing the input data for the ioctl. + * @param output A buffer where the output data will be written to. + * @returns The result code of the ioctl. + */ + virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) = 0; + + /** + * Handles an ioctl2 request. + * @param command The ioctl command id. + * @param input A buffer containing the input data for the ioctl. + * @param inline_input A buffer containing the input data for the ioctl which has been inlined. + * @param output A buffer where the output data will be written to. + * @returns The result code of the ioctl. + */ + virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) = 0; /** - * Handles an ioctl request. + * Handles an ioctl3 request. * @param command The ioctl command id. * @param input A buffer containing the input data for the ioctl. * @param output A buffer where the output data will be written to. + * @param inline_output A buffer where the inlined output data will be written to. * @returns The result code of the ioctl. */ - virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) = 0; + virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) = 0; protected: Core::System& system; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 3f7b8e670..ce615c758 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -18,11 +18,22 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvdisp_disp0 ::~nvdisp_disp0() = default; -u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; +NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 6fcdeee84..55a33b7e4 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -20,9 +20,11 @@ public: explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvdisp_disp0() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index d4ba88147..6b062e10e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -17,57 +17,77 @@ namespace Service::Nvidia::Devices { -namespace NvErrCodes { -constexpr u32 Success{}; -constexpr u32 OutOfMemory{static_cast<u32>(-12)}; -constexpr u32 InvalidInput{static_cast<u32>(-22)}; -} // namespace NvErrCodes - nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_as_gpu::~nvhost_as_gpu() = default; -u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocInitalizeExCommand: - return InitalizeEx(input, output); - case IoctlCommand::IocAllocateSpaceCommand: - return AllocateSpace(input, output); - case IoctlCommand::IocMapBufferExCommand: - return MapBufferEx(input, output); - case IoctlCommand::IocBindChannelCommand: - return BindChannel(input, output); - case IoctlCommand::IocGetVaRegionsCommand: - return GetVARegions(input, output); - case IoctlCommand::IocUnmapBufferCommand: - return UnmapBuffer(input, output); +NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + switch (command.group) { + case 'A': + switch (command.cmd) { + case 0x1: + return BindChannel(input, output); + case 0x2: + return AllocateSpace(input, output); + case 0x3: + return FreeSpace(input, output); + case 0x5: + return UnmapBuffer(input, output); + case 0x6: + return MapBufferEx(input, output); + case 0x8: + return GetVARegions(input, output); + case 0x9: + return InitalizeEx(input, output); + case 0x14: + return Remap(input, output); + default: + break; + } + break; default: break; } - if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) { - return Remap(input, output); - } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - UNIMPLEMENTED_MSG("Unimplemented ioctl command"); - return 0; +NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + switch (command.group) { + case 'A': + switch (command.cmd) { + case 0x8: + return GetVARegions(input, output, inline_output); + default: + break; + } + break; + default: + break; + } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) { IoctlInitalizeEx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size); - return 0; + return NvResult::Success; } -u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocSpace params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -81,22 +101,36 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& params.offset = system.GPU().MemoryManager().Allocate(size, params.align); } - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; if (!params.offset) { LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size); - result = NvErrCodes::OutOfMemory; + result = NvResult::InsufficientMemory; } std::memcpy(output.data(), ¶ms, output.size()); return result; } -u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlFreeSpace params{}; + std::memcpy(¶ms, input.data(), input.size()); + + LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, + params.pages, params.page_size); + + system.GPU().MemoryManager().Unmap(params.offset, + static_cast<std::size_t>(params.pages) * params.page_size); + + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; +} + +NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { const auto num_entries = input.size() / sizeof(IoctlRemapEntry); LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries); - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; std::vector<IoctlRemapEntry> entries(num_entries); std::memcpy(entries.data(), input.data(), input.size()); @@ -107,7 +141,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) const auto object{nvmap_dev->GetObject(entry.nvmap_handle)}; if (!object) { LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; break; } @@ -118,7 +152,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) if (!addr) { LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!"); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; break; } } @@ -127,7 +161,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) return result; } -u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { IoctlMapBufferEx params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -141,7 +175,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou if (!object) { LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } // The real nvservices doesn't make a distinction between handles and ids, and @@ -168,16 +202,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou params.mapping_size, params.offset); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::Success; + return NvResult::Success; } else { LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } } @@ -197,10 +231,10 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size); } - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; if (!params.offset) { LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; } else { AddBufferMap(params.offset, size, physical_address, is_alloc); } @@ -209,7 +243,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou return result; } -u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { IoctlUnmapBuffer params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -222,20 +256,42 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou } std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::Success; + return NvResult::Success; } -u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) { IoctlBindChannel params{}; std::memcpy(¶ms, input.data(), input.size()); - - LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd); channel = params.fd; - return 0; + return NvResult::Success; +} + +NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetVaRegions params{}; + std::memcpy(¶ms, input.data(), input.size()); + + LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr, + params.buf_size); + + params.buf_size = 0x30; + params.regions[0].offset = 0x04000000; + params.regions[0].page_size = 0x1000; + params.regions[0].pages = 0x3fbfff; + + params.regions[1].offset = 0x04000000; + params.regions[1].page_size = 0x10000; + params.regions[1].pages = 0x1bffff; + + // TODO(ogniK): This probably can stay stubbed but should add support way way later + + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; } -u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { IoctlGetVaRegions params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -254,7 +310,8 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o // TODO(ogniK): This probably can stay stubbed but should add support way way later std::memcpy(output.data(), ¶ms, output.size()); - return 0; + std::memcpy(inline_output.data(), ¶ms.regions, inline_output.size()); + return NvResult::Success; } std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const { @@ -265,7 +322,7 @@ std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gp } } - return {}; + return std::nullopt; } void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, @@ -286,7 +343,7 @@ std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) { return size; } - return {}; + return std::nullopt; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 9a0cdff0c..08035fa0e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -30,9 +30,11 @@ public: explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvhost_as_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; private: class BufferMap final { @@ -74,31 +76,21 @@ private: bool is_allocated{}; }; - enum class IoctlCommand : u32_le { - IocInitalizeExCommand = 0x40284109, - IocAllocateSpaceCommand = 0xC0184102, - IocRemapCommand = 0x00000014, - IocMapBufferExCommand = 0xC0284106, - IocBindChannelCommand = 0x40044101, - IocGetVaRegionsCommand = 0xC0404108, - IocUnmapBufferCommand = 0xC0084105, - }; - struct IoctlInitalizeEx { - u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default - s32_le as_fd; // ignored; passes 0 - u32_le flags; // passes 0 - u32_le reserved; // ignored; passes 0 - u64_le unk0; - u64_le unk1; - u64_le unk2; + u32_le big_page_size{}; // depends on GPU's available_big_page_sizes; 0=default + s32_le as_fd{}; // ignored; passes 0 + u32_le flags{}; // passes 0 + u32_le reserved{}; // ignored; passes 0 + u64_le unk0{}; + u64_le unk1{}; + u64_le unk2{}; }; static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size"); struct IoctlAllocSpace { - u32_le pages; - u32_le page_size; - AddressSpaceFlags flags; + u32_le pages{}; + u32_le page_size{}; + AddressSpaceFlags flags{}; INSERT_PADDING_WORDS(1); union { u64_le offset; @@ -107,63 +99,74 @@ private: }; static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size"); + struct IoctlFreeSpace { + u64_le offset{}; + u32_le pages{}; + u32_le page_size{}; + }; + static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size"); + struct IoctlRemapEntry { - u16_le flags; - u16_le kind; - u32_le nvmap_handle; - u32_le map_offset; - u32_le offset; - u32_le pages; + u16_le flags{}; + u16_le kind{}; + u32_le nvmap_handle{}; + u32_le map_offset{}; + u32_le offset{}; + u32_le pages{}; }; static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); struct IoctlMapBufferEx { - AddressSpaceFlags flags; // bit0: fixed_offset, bit2: cacheable - u32_le kind; // -1 is default - u32_le nvmap_handle; - u32_le page_size; // 0 means don't care - s64_le buffer_offset; - u64_le mapping_size; - s64_le offset; + AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable + u32_le kind{}; // -1 is default + u32_le nvmap_handle{}; + u32_le page_size{}; // 0 means don't care + s64_le buffer_offset{}; + u64_le mapping_size{}; + s64_le offset{}; }; static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size"); struct IoctlUnmapBuffer { - s64_le offset; + s64_le offset{}; }; static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size"); struct IoctlBindChannel { - u32_le fd; + s32_le fd{}; }; static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size"); struct IoctlVaRegion { - u64_le offset; - u32_le page_size; + u64_le offset{}; + u32_le page_size{}; INSERT_PADDING_WORDS(1); - u64_le pages; + u64_le pages{}; }; static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size"); struct IoctlGetVaRegions { - u64_le buf_addr; // (contained output user ptr on linux, ignored) - u32_le buf_size; // forced to 2*sizeof(struct va_region) - u32_le reserved; - IoctlVaRegion regions[2]; + u64_le buf_addr{}; // (contained output user ptr on linux, ignored) + u32_le buf_size{}; // forced to 2*sizeof(struct va_region) + u32_le reserved{}; + IoctlVaRegion regions[2]{}; }; static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2, "IoctlGetVaRegions is incorrect size"); - u32 channel{}; + s32 channel{}; + + NvResult InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output); + NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output); + NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output); + NvResult MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); + NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); + NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output); + NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output); - u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output); - u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output); - u32 Remap(const std::vector<u8>& input, std::vector<u8>& output); - u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); - u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); - u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output); - u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output); std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const; void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index b27ee0502..f6129ef10 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -8,52 +8,66 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" #include "video_core/gpu.h" namespace Service::Nvidia::Devices { -nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface) - : nvdevice(system), events_interface{events_interface} {} +nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface, + SyncpointManager& syncpoint_manager) + : nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {} nvhost_ctrl::~nvhost_ctrl() = default; -u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocGetConfigCommand: - return NvOsGetConfigU32(input, output); - case IoctlCommand::IocCtrlEventWaitCommand: - return IocCtrlEventWait(input, output, false, ctrl); - case IoctlCommand::IocCtrlEventWaitAsyncCommand: - return IocCtrlEventWait(input, output, true, ctrl); - case IoctlCommand::IocCtrlEventRegisterCommand: - return IocCtrlEventRegister(input, output); - case IoctlCommand::IocCtrlEventUnregisterCommand: - return IocCtrlEventUnregister(input, output); - case IoctlCommand::IocCtrlEventSignalCommand: - return IocCtrlEventSignal(input, output); +NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1b: + return NvOsGetConfigU32(input, output); + case 0x1c: + return IocCtrlClearEventWait(input, output); + case 0x1d: + return IocCtrlEventWait(input, output, false); + case 0x1e: + return IocCtrlEventWait(input, output, true); + case 0x1f: + return IocCtrlEventRegister(input, output); + case 0x20: + return IocCtrlEventUnregister(input, output); + } + break; default: - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + break; } + + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_outpu) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { IocGetConfigParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(), params.param_str.data()); - return 0x30006; // Returns error on production mode + return NvResult::ConfigVarNotFound; // Returns error on production mode } -u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, - bool is_async, IoctlCtrl& ctrl) { +NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, + bool is_async) { IocCtrlEventWaitParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}", @@ -70,19 +84,33 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& return NvResult::BadParameter; } + if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) { + params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id); + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Success; + } + + if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id); + syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) { + params.value = new_value; + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Success; + } + auto event = events_interface.events[event_id]; auto& gpu = system.GPU(); + // This is mostly to take into account unimplemented features. As synced // gpu is always synced. if (!gpu.IsAsync()) { - event.writable->Signal(); + event.event->GetWritableEvent()->Signal(); return NvResult::Success; } auto lock = gpu.LockSync(); - const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id); + const u32 current_syncpoint_value = event.fence.value; const s32 diff = current_syncpoint_value - params.threshold; if (diff >= 0) { - event.writable->Signal(); + event.event->GetWritableEvent()->Signal(); params.value = current_syncpoint_value; std::memcpy(output.data(), ¶ms, sizeof(params)); return NvResult::Success; @@ -109,14 +137,8 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000; } params.value |= event_id; - event.writable->Clear(); + event.event->GetWritableEvent()->Clear(); gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value); - if (!is_async && ctrl.fresh_call) { - ctrl.must_delay = true; - ctrl.timeout = params.timeout; - ctrl.event_id = event_id; - return NvResult::Timeout; - } std::memcpy(output.data(), ¶ms, sizeof(params)); return NvResult::Timeout; } @@ -124,7 +146,7 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& return NvResult::BadParameter; } -u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) { IocCtrlEventRegisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id & 0x00FF; @@ -133,13 +155,20 @@ u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector< return NvResult::BadParameter; } if (events_interface.registered[event_id]) { - return NvResult::BadParameter; + const auto event_state = events_interface.status[event_id]; + if (event_state != EventState::Free) { + LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event"); + events_interface.UnregisterEvent(event_id); + } else { + return NvResult::BadParameter; + } } events_interface.RegisterEvent(event_id); return NvResult::Success; } -u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, + std::vector<u8>& output) { IocCtrlEventUnregisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id & 0x00FF; @@ -154,24 +183,22 @@ u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vecto return NvResult::Success; } -u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) { IocCtrlEventSignalParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); - // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization - // It is believed from RE to cancel the GPU Event. However, better research is required - u32 event_id = params.user_event_id & 0x00FF; - LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id); + + u32 event_id = params.event_id & 0x00FF; + LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id); + if (event_id >= MaxNvEvents) { return NvResult::BadParameter; } if (events_interface.status[event_id] == EventState::Waiting) { - auto& gpu = system.GPU(); - if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id], - events_interface.assigned_value[event_id])) { - events_interface.LiberateEvent(event_id); - events_interface.events[event_id].writable->Signal(); - } + events_interface.LiberateEvent(event_id); } + + syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id); + return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 9898623de..c5aa1362a 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -14,137 +14,120 @@ namespace Service::Nvidia::Devices { class nvhost_ctrl final : public nvdevice { public: - explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface); + explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface, + SyncpointManager& syncpoint_manager); ~nvhost_ctrl() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSyncptReadCommand = 0xC0080014, - IocSyncptIncrCommand = 0x40040015, - IocSyncptWaitCommand = 0xC00C0016, - IocModuleMutexCommand = 0x40080017, - IocModuleRegRDWRCommand = 0xC0180018, - IocSyncptWaitexCommand = 0xC0100019, - IocSyncptReadMaxCommand = 0xC008001A, - IocGetConfigCommand = 0xC183001B, - IocCtrlEventSignalCommand = 0xC004001C, - IocCtrlEventWaitCommand = 0xC010001D, - IocCtrlEventWaitAsyncCommand = 0xC010001E, - IocCtrlEventRegisterCommand = 0xC004001F, - IocCtrlEventUnregisterCommand = 0xC0040020, - IocCtrlEventKillCommand = 0x40080021, - }; struct IocSyncptReadParams { - u32_le id; - u32_le value; + u32_le id{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size"); struct IocSyncptIncrParams { - u32_le id; + u32_le id{}; }; static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size"); struct IocSyncptWaitParams { - u32_le id; - u32_le thresh; - s32_le timeout; + u32_le id{}; + u32_le thresh{}; + s32_le timeout{}; }; static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size"); struct IocModuleMutexParams { - u32_le id; - u32_le lock; // (0 = unlock and 1 = lock) + u32_le id{}; + u32_le lock{}; // (0 = unlock and 1 = lock) }; static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size"); struct IocModuleRegRDWRParams { - u32_le id; - u32_le num_offsets; - u32_le block_size; - u32_le offsets; - u32_le values; - u32_le write; + u32_le id{}; + u32_le num_offsets{}; + u32_le block_size{}; + u32_le offsets{}; + u32_le values{}; + u32_le write{}; }; static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size"); struct IocSyncptWaitexParams { - u32_le id; - u32_le thresh; - s32_le timeout; - u32_le value; + u32_le id{}; + u32_le thresh{}; + s32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size"); struct IocSyncptReadMaxParams { - u32_le id; - u32_le value; + u32_le id{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size"); struct IocGetConfigParams { - std::array<char, 0x41> domain_str; - std::array<char, 0x41> param_str; - std::array<char, 0x101> config_str; + std::array<char, 0x41> domain_str{}; + std::array<char, 0x41> param_str{}; + std::array<char, 0x101> config_str{}; }; static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size"); struct IocCtrlEventSignalParams { - u32_le user_event_id; + u32_le event_id{}; }; static_assert(sizeof(IocCtrlEventSignalParams) == 4, "IocCtrlEventSignalParams is incorrect size"); struct IocCtrlEventWaitParams { - u32_le syncpt_id; - u32_le threshold; - s32_le timeout; - u32_le value; + u32_le syncpt_id{}; + u32_le threshold{}; + s32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size"); struct IocCtrlEventWaitAsyncParams { - u32_le syncpt_id; - u32_le threshold; - u32_le timeout; - u32_le value; + u32_le syncpt_id{}; + u32_le threshold{}; + u32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16, "IocCtrlEventWaitAsyncParams is incorrect size"); struct IocCtrlEventRegisterParams { - u32_le user_event_id; + u32_le user_event_id{}; }; static_assert(sizeof(IocCtrlEventRegisterParams) == 4, "IocCtrlEventRegisterParams is incorrect size"); struct IocCtrlEventUnregisterParams { - u32_le user_event_id; + u32_le user_event_id{}; }; static_assert(sizeof(IocCtrlEventUnregisterParams) == 4, "IocCtrlEventUnregisterParams is incorrect size"); struct IocCtrlEventKill { - u64_le user_events; + u64_le user_events{}; }; static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size"); - u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); - - u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async, - IoctlCtrl& ctrl); - - u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); - - u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output); - - u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output); + NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async); + NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output); EventInterface& events_interface; + SyncpointManager& syncpoint_manager; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index fba89e7a6..0320d3ae2 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -15,39 +15,66 @@ namespace Service::Nvidia::Devices { nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {} nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; -u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& input2, std::vector<u8>& output, - std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocGetCharacteristicsCommand: - return GetCharacteristics(input, output, output2, version); - case IoctlCommand::IocGetTPCMasksCommand: - return GetTPCMasks(input, output, output2, version); - case IoctlCommand::IocGetActiveSlotMaskCommand: - return GetActiveSlotMask(input, output); - case IoctlCommand::IocZcullGetCtxSizeCommand: - return ZCullGetCtxSize(input, output); - case IoctlCommand::IocZcullGetInfo: - return ZCullGetInfo(input, output); - case IoctlCommand::IocZbcSetTable: - return ZBCSetTable(input, output); - case IoctlCommand::IocZbcQueryTable: - return ZBCQueryTable(input, output); - case IoctlCommand::IocFlushL2: - return FlushL2(input, output); - case IoctlCommand::IocGetGpuTime: - return GetGpuTime(input, output); +NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + switch (command.group) { + case 'G': + switch (command.cmd) { + case 0x1: + return ZCullGetCtxSize(input, output); + case 0x2: + return ZCullGetInfo(input, output); + case 0x3: + return ZBCSetTable(input, output); + case 0x4: + return ZBCQueryTable(input, output); + case 0x5: + return GetCharacteristics(input, output); + case 0x6: + return GetTPCMasks(input, output); + case 0x7: + return FlushL2(input, output); + case 0x14: + return GetActiveSlotMask(input, output); + case 0x1c: + return GetGpuTime(input, output); + default: + break; + } + break; + } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output, std::vector<u8>& inline_output) { + switch (command.group) { + case 'G': + switch (command.cmd) { + case 0x5: + return GetCharacteristics(input, output, inline_output); + case 0x6: + return GetTPCMasks(input, output, inline_output); + default: + break; + } + break; default: - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + break; } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, - std::vector<u8>& output2, IoctlVersion version) { +NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, + std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -88,36 +115,83 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto params.gc.gr_compbit_store_base_hw = 0x0; params.gpu_characteristics_buf_size = 0xA0; params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; +} - if (version == IoctlVersion::Version3) { - std::memcpy(output.data(), input.data(), output.size()); - std::memcpy(output2.data(), ¶ms.gc, output2.size()); - } else { - std::memcpy(output.data(), ¶ms, output.size()); - } - return 0; +NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlCharacteristics params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.gc.arch = 0x120; + params.gc.impl = 0xb; + params.gc.rev = 0xa1; + params.gc.num_gpc = 0x1; + params.gc.l2_cache_size = 0x40000; + params.gc.on_board_video_memory_size = 0x0; + params.gc.num_tpc_per_gpc = 0x2; + params.gc.bus_type = 0x20; + params.gc.big_page_size = 0x20000; + params.gc.compression_page_size = 0x20000; + params.gc.pde_coverage_bit_count = 0x1B; + params.gc.available_big_page_sizes = 0x30000; + params.gc.gpc_mask = 0x1; + params.gc.sm_arch_sm_version = 0x503; + params.gc.sm_arch_spa_version = 0x503; + params.gc.sm_arch_warp_count = 0x80; + params.gc.gpu_va_bit_count = 0x28; + params.gc.reserved = 0x0; + params.gc.flags = 0x55; + params.gc.twod_class = 0x902D; + params.gc.threed_class = 0xB197; + params.gc.compute_class = 0xB1C0; + params.gc.gpfifo_class = 0xB06F; + params.gc.inline_to_memory_class = 0xA140; + params.gc.dma_copy_class = 0xB0B5; + params.gc.max_fbps_count = 0x1; + params.gc.fbp_en_mask = 0x0; + params.gc.max_ltc_per_fbp = 0x2; + params.gc.max_lts_per_ltc = 0x1; + params.gc.max_tex_per_tpc = 0x0; + params.gc.max_gpc_count = 0x1; + params.gc.rop_l2_en_mask_0 = 0x21D70; + params.gc.rop_l2_en_mask_1 = 0x0; + params.gc.chipname = 0x6230326D67; + params.gc.gr_compbit_store_base_hw = 0x0; + params.gpu_characteristics_buf_size = 0xA0; + params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) + + std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(inline_output.data(), ¶ms.gc, inline_output.size()); + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, - std::vector<u8>& output2, IoctlVersion version) { +NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) { IoctlGpuGetTpcMasksArgs params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size); if (params.mask_buffer_size != 0) { params.tcp_mask = 3; } + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; +} - if (version == IoctlVersion::Version3) { - std::memcpy(output.data(), input.data(), output.size()); - std::memcpy(output2.data(), ¶ms.tcp_mask, output2.size()); - } else { - std::memcpy(output.data(), ¶ms, output.size()); +NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + IoctlGpuGetTpcMasksArgs params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size); + if (params.mask_buffer_size != 0) { + params.tcp_mask = 3; } - - return 0; + std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(inline_output.data(), ¶ms.tcp_mask, inline_output.size()); + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlActiveSlotMask params{}; @@ -127,10 +201,10 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector params.slot = 0x07; params.mask = 0x01; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlZcullGetCtxSize params{}; @@ -139,10 +213,10 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u } params.size = 0x1; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlNvgpuGpuZcullGetInfoArgs params{}; @@ -162,47 +236,47 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& params.subregion_height_align_pixels = 0x40; params.subregion_count = 0x10; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcSetTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO(ogniK): What does this even actually do? std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcQueryTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlFlushL2 params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlGetGpuTime params{}; std::memcpy(¶ms, input.data(), input.size()); params.gpu_time = static_cast<u64_le>(system.CoreTiming().GetGlobalTimeNs().count()); std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index ef60f72ce..137b88238 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -16,32 +16,13 @@ public: explicit nvhost_ctrl_gpu(Core::System& system); ~nvhost_ctrl_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocGetCharacteristicsCommand = 0xC0B04705, - IocGetTPCMasksCommand = 0xC0184706, - IocGetActiveSlotMaskCommand = 0x80084714, - IocZcullGetCtxSizeCommand = 0x80044701, - IocZcullGetInfo = 0x80284702, - IocZbcSetTable = 0x402C4703, - IocZbcQueryTable = 0xC0344704, - IocFlushL2 = 0x40084707, - IocInvalICache = 0x4008470D, - IocSetMmudebugMode = 0x4008470E, - IocSetSmDebugMode = 0x4010470F, - IocWaitForPause = 0xC0084710, - IocGetTcpExceptionEnStatus = 0x80084711, - IocNumVsms = 0x80084712, - IocVsmsMapping = 0xC0044713, - IocGetErrorChannelUserData = 0xC008471B, - IocGetGpuTime = 0xC010471C, - IocGetCpuTimeCorrelationInfo = 0xC108471D, - }; - struct IoctlGpuCharacteristics { u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200) u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B) @@ -159,17 +140,21 @@ private: }; static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size"); - u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, - std::vector<u8>& output2, IoctlVersion version); - u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, std::vector<u8>& output2, - IoctlVersion version); - u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); - u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); - u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output); - u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output); - u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output); - u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output); - u32 GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output); + + NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output); + + NvResult GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output); + NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index f1966ac0e..af8b3d9f1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -7,117 +7,148 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/memory.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { -nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) - : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} +nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager{syncpoint_manager} { + channel_fence.id = syncpoint_manager.AllocateSyncpoint(); + channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id); +} + nvhost_gpu::~nvhost_gpu() = default; -u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); - case IoctlCommand::IocSetClientDataCommand: - return SetClientData(input, output); - case IoctlCommand::IocGetClientDataCommand: - return GetClientData(input, output); - case IoctlCommand::IocZCullBind: - return ZCullBind(input, output); - case IoctlCommand::IocSetErrorNotifierCommand: - return SetErrorNotifier(input, output); - case IoctlCommand::IocChannelSetPriorityCommand: - return SetChannelPriority(input, output); - case IoctlCommand::IocAllocGPFIFOEx2Command: - return AllocGPFIFOEx2(input, output); - case IoctlCommand::IocAllocObjCtxCommand: - return AllocateObjectContext(input, output); - case IoctlCommand::IocChannelGetWaitbaseCommand: - return GetWaitbase(input, output); - case IoctlCommand::IocChannelSetTimeoutCommand: - return ChannelSetTimeout(input, output); - case IoctlCommand::IocChannelSetTimeslice: - return ChannelSetTimeslice(input, output); - default: +NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x3: + return GetWaitbase(input, output); + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input, output); + case 0x3: + return ChannelSetTimeout(input, output); + case 0x8: + return SubmitGPFIFOBase(input, output, false); + case 0x9: + return AllocateObjectContext(input, output); + case 0xb: + return ZCullBind(input, output); + case 0xc: + return SetErrorNotifier(input, output); + case 0xd: + return SetChannelPriority(input, output); + case 0x1a: + return AllocGPFIFOEx2(input, output); + case 0x1b: + return SubmitGPFIFOBase(input, output, true); + case 0x1d: + return ChannelSetTimeslice(input, output); + default: + break; + } + break; + case 'G': + switch (command.cmd) { + case 0x14: + return SetClientData(input, output); + case 0x15: + return GetClientData(input, output); + default: + break; + } break; } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +}; - if (command.group == NVGPU_IOCTL_MAGIC) { - if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) { - return SubmitGPFIFO(input, output); - } - if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) { - return KickoffPB(input, output, input2, version); +NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + switch (command.group) { + case 'H': + switch (command.cmd) { + case 0x1b: + return SubmitGPFIFOBase(input, inline_input, output); } + break; } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; -}; +NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} -u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); user_data = params.data; - return 0; + return NvResult::Success; } -u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); params.data = user_data; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) { std::memcpy(&zcull_params, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va, zcull_params.mode); std::memcpy(output.data(), &zcull_params, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetErrorNotifier params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset, params.size, params.mem); std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { std::memcpy(&channel_priority, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocGpfifoEx2 params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, @@ -126,15 +157,15 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, params.unk3); - auto& gpu = system.GPU(); - params.fence_out.id = assigned_syncpoints; - params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints); - assigned_syncpoints++; + channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id); + + params.fence_out = channel_fence; + std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocObjCtx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num, @@ -142,102 +173,149 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector< params.obj_id = 0x0; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) { - if (input.size() < sizeof(IoctlSubmitGpfifo)) { - UNIMPLEMENTED(); +static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) { + return { + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, + Tegra::SubmissionMode::Increasing), + {fence.value}, + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1, + Tegra::SubmissionMode::Increasing), + Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Acquire, fence.id), + }; +} + +static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) { + std::vector<Tegra::CommandHeader> result{ + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, + Tegra::SubmissionMode::Increasing), + {}}; + + for (u32 count = 0; count < add_increment; ++count) { + result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1, + Tegra::SubmissionMode::Increasing)); + result.emplace_back( + Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Increment, fence.id)); } - IoctlSubmitGpfifo params{}; - std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); + + return result; +} + +static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence, + u32 add_increment) { + std::vector<Tegra::CommandHeader> result{ + Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1, + Tegra::SubmissionMode::Increasing), + {}}; + const std::vector<Tegra::CommandHeader> increment{ + BuildIncrementCommandList(fence, add_increment)}; + + result.insert(result.end(), increment.begin(), increment.end()); + + return result; +} + +NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output, + Tegra::CommandList&& entries) { LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, params.num_entries, params.flags.raw); - ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) + - params.num_entries * sizeof(Tegra::CommandListHeader), - "Incorrect input size"); + auto& gpu = system.GPU(); - Tegra::CommandList entries(params.num_entries); - std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)], - params.num_entries * sizeof(Tegra::CommandListHeader)); + params.fence_out.id = channel_fence.id; - UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); - UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); + if (params.flags.add_wait.Value() && + !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) { + gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)}); + } - auto& gpu = system.GPU(); - u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); - if (params.flags.increment.Value()) { - params.fence_out.value += current_syncpoint_value; + if (params.flags.add_increment.Value() || params.flags.increment.Value()) { + const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0; + params.fence_out.value = syncpoint_manager.IncreaseSyncpoint( + params.fence_out.id, params.AddIncrementValue() + increment_value); } else { - params.fence_out.value = current_syncpoint_value; + params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id); } + gpu.PushGPUEntries(std::move(entries)); + if (params.flags.add_increment.Value()) { + if (params.flags.suppress_wfi) { + gpu.PushGPUEntries(Tegra::CommandList{ + BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())}); + } else { + gpu.PushGPUEntries(Tegra::CommandList{ + BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())}); + } + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, - const std::vector<u8>& input2, IoctlVersion version) { +NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output, + bool kickoff) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); + return NvResult::InvalidSize; } IoctlSubmitGpfifo params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); - LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, - params.num_entries, params.flags.raw); - Tegra::CommandList entries(params.num_entries); - if (version == IoctlVersion::Version2) { - std::memcpy(entries.data(), input2.data(), - params.num_entries * sizeof(Tegra::CommandListHeader)); - } else { - system.Memory().ReadBlock(params.address, entries.data(), - params.num_entries * sizeof(Tegra::CommandListHeader)); - } - UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); - UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); - auto& gpu = system.GPU(); - u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); - if (params.flags.increment.Value()) { - params.fence_out.value += current_syncpoint_value; + if (kickoff) { + system.Memory().ReadBlock(params.address, entries.command_lists.data(), + params.num_entries * sizeof(Tegra::CommandListHeader)); } else { - params.fence_out.value = current_syncpoint_value; + std::memcpy(entries.command_lists.data(), &input[sizeof(IoctlSubmitGpfifo)], + params.num_entries * sizeof(Tegra::CommandListHeader)); } - gpu.PushGPUEntries(std::move(entries)); - std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return SubmitGPFIFOImpl(params, output, std::move(entries)); +} + +NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, + const std::vector<u8>& input_inline, + std::vector<u8>& output) { + if (input.size() < sizeof(IoctlSubmitGpfifo)) { + UNIMPLEMENTED(); + return NvResult::InvalidSize; + } + IoctlSubmitGpfifo params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); + Tegra::CommandList entries(params.num_entries); + std::memcpy(entries.command_lists.data(), input_inline.data(), input_inline.size()); + return SubmitGPFIFOImpl(params, output, std::move(entries)); } -u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { IoctlGetWaitbase params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown); params.value = 0; // Seems to be hard coded at 0 std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) { IoctlChannelSetTimeout params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlChannelSetTimeout)); LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetTimeslice params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSetTimeslice)); LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice); channel_timeslice = params.timeslice; - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 2ac74743f..e0298b4fe 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -11,46 +11,28 @@ #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "video_core/dma_pusher.h" + +namespace Service::Nvidia { +class SyncpointManager; +} namespace Service::Nvidia::Devices { class nvmap; -constexpr u32 NVGPU_IOCTL_MAGIC('H'); -constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8); -constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b); - class nvhost_gpu final : public nvdevice { public: - explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); + explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager); ~nvhost_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - IocAllocGPFIFOCommand = 0x40084805, - IocSetClientDataCommand = 0x40084714, - IocGetClientDataCommand = 0x80084715, - IocZCullBind = 0xc010480b, - IocSetErrorNotifierCommand = 0xC018480C, - IocChannelSetPriorityCommand = 0x4004480D, - IocEnableCommand = 0x0000480E, - IocDisableCommand = 0x0000480F, - IocPreemptCommand = 0x00004810, - IocForceResetCommand = 0x00004811, - IocEventIdControlCommand = 0x40084812, - IocGetErrorNotificationCommand = 0xC0104817, - IocAllocGPFIFOExCommand = 0x40204818, - IocAllocGPFIFOEx2Command = 0xC020481A, - IocAllocObjCtxCommand = 0xC0104809, - IocChannelGetWaitbaseCommand = 0xC0080003, - IocChannelSetTimeoutCommand = 0x40044803, - IocChannelSetTimeslice = 0xC004481D, - }; - enum class CtxObjects : u32_le { Ctx2D = 0x902D, Ctx3D = 0xB197, @@ -61,63 +43,63 @@ private: }; struct IoctlSetNvmapFD { - u32_le nvmap_fd; + s32_le nvmap_fd{}; }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); struct IoctlChannelSetTimeout { - u32_le timeout; + u32_le timeout{}; }; static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size"); struct IoctlAllocGPFIFO { - u32_le num_entries; - u32_le flags; + u32_le num_entries{}; + u32_le flags{}; }; static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size"); struct IoctlClientData { - u64_le data; + u64_le data{}; }; static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size"); struct IoctlZCullBind { - u64_le gpu_va; - u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf + u64_le gpu_va{}; + u32_le mode{}; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf INSERT_PADDING_WORDS(1); }; static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size"); struct IoctlSetErrorNotifier { - u64_le offset; - u64_le size; - u32_le mem; // nvmap object handle + u64_le offset{}; + u64_le size{}; + u32_le mem{}; // nvmap object handle INSERT_PADDING_WORDS(1); }; static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size"); struct IoctlChannelSetPriority { - u32_le priority; + u32_le priority{}; }; static_assert(sizeof(IoctlChannelSetPriority) == 4, "IoctlChannelSetPriority is incorrect size"); struct IoctlSetTimeslice { - u32_le timeslice; + u32_le timeslice{}; }; static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size"); struct IoctlEventIdControl { - u32_le cmd; // 0=disable, 1=enable, 2=clear - u32_le id; + u32_le cmd{}; // 0=disable, 1=enable, 2=clear + u32_le id{}; }; static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size"); struct IoctlGetErrorNotification { - u64_le timestamp; - u32_le info32; - u16_le info16; - u16_le status; // always 0xFFFF + u64_le timestamp{}; + u32_le info32{}; + u16_le info16{}; + u16_le status{}; // always 0xFFFF }; static_assert(sizeof(IoctlGetErrorNotification) == 16, "IoctlGetErrorNotification is incorrect size"); @@ -125,80 +107,89 @@ private: static_assert(sizeof(Fence) == 8, "Fence is incorrect size"); struct IoctlAllocGpfifoEx { - u32_le num_entries; - u32_le flags; - u32_le unk0; - u32_le unk1; - u32_le unk2; - u32_le unk3; - u32_le unk4; - u32_le unk5; + u32_le num_entries{}; + u32_le flags{}; + u32_le unk0{}; + u32_le unk1{}; + u32_le unk2{}; + u32_le unk3{}; + u32_le unk4{}; + u32_le unk5{}; }; static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size"); struct IoctlAllocGpfifoEx2 { - u32_le num_entries; // in - u32_le flags; // in - u32_le unk0; // in (1 works) - Fence fence_out; // out - u32_le unk1; // in - u32_le unk2; // in - u32_le unk3; // in + u32_le num_entries{}; // in + u32_le flags{}; // in + u32_le unk0{}; // in (1 works) + Fence fence_out{}; // out + u32_le unk1{}; // in + u32_le unk2{}; // in + u32_le unk3{}; // in }; static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size"); struct IoctlAllocObjCtx { - u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA, - // 0xB06F=channel_gpfifo - u32_le flags; - u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported + u32_le class_num{}; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA, + // 0xB06F=channel_gpfifo + u32_le flags{}; + u64_le obj_id{}; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported }; static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size"); struct IoctlSubmitGpfifo { - u64_le address; // pointer to gpfifo entry structs - u32_le num_entries; // number of fence objects being submitted + u64_le address{}; // pointer to gpfifo entry structs + u32_le num_entries{}; // number of fence objects being submitted union { u32_le raw; BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list BitField<1, 1, u32_le> add_increment; // append an increment to the list - BitField<2, 1, u32_le> new_hw_format; // Mostly ignored + BitField<2, 1, u32_le> new_hw_format; // mostly ignored + BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt BitField<8, 1, u32_le> increment; // increment the returned fence } flags; - Fence fence_out; // returned new fence object for others to wait on + Fence fence_out{}; // returned new fence object for others to wait on + + u32 AddIncrementValue() const { + return flags.add_increment.Value() << 1; + } }; static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence), "IoctlSubmitGpfifo is incorrect size"); struct IoctlGetWaitbase { - u32 unknown; // seems to be ignored? Nintendo added this - u32 value; + u32 unknown{}; // seems to be ignored? Nintendo added this + u32 value{}; }; static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size"); - u32_le nvmap_fd{}; + s32_le nvmap_fd{}; u64_le user_data{}; IoctlZCullBind zcull_params{}; u32_le channel_priority{}; u32_le channel_timeslice{}; - u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); - u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output); - u32 GetClientData(const std::vector<u8>& input, std::vector<u8>& output); - u32 ZCullBind(const std::vector<u8>& input, std::vector<u8>& output); - u32 SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output); - u32 SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output); - u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); - u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); - u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); - u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, - const std::vector<u8>& input2, IoctlVersion version); - u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); - u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); - u32 ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetClientData(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetClientData(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ZCullBind(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output); + NvResult AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); + NvResult AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output, + Tegra::CommandList&& entries); + NvResult SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output, + bool kickoff = false); + NvResult SubmitGPFIFOBase(const std::vector<u8>& input, const std::vector<u8>& input_inline, + std::vector<u8>& output); + NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); + NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output); std::shared_ptr<nvmap> nvmap_dev; - u32 assigned_syncpoints{}; + SyncpointManager& syncpoint_manager; + Fence channel_fence; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index bdae8b887..36970f828 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -2,39 +2,72 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> - #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { -nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {} +nvhost_nvdec::nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {} nvhost_nvdec::~nvhost_nvdec() = default; -u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1: + return Submit(input, output); + case 0x2: + return GetSyncpoint(input, output); + case 0x3: + return GetWaitbase(input, output); + case 0x7: + return SetSubmitTimeout(input, output); + case 0x9: + return MapBuffer(input, output); + case 0xa: { + if (command.length == 0x1c) { + LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); + Tegra::ChCommandHeaderList cmdlist(1); + cmdlist[0] = Tegra::ChCommandHeader{0xDEADB33F}; + system.GPU().PushCommandBuffer(cmdlist); + } + return UnmapBuffer(input, output); + } + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input); + default: + break; + } + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { - IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); +NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - nvmap_fd = params.nvmap_fd; - return 0; +NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index cbdac8069..77ef53cdd 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -4,35 +4,22 @@ #pragma once -#include <vector> -#include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/nvdrv/devices/nvdevice.h" +#include <memory> +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" namespace Service::Nvidia::Devices { -class nvhost_nvdec final : public nvdevice { +class nvhost_nvdec final : public nvhost_nvdec_common { public: - explicit nvhost_nvdec(Core::System& system); + explicit nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager); ~nvhost_nvdec() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - -private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - - struct IoctlSetNvmapFD { - u32_le nvmap_fd; - }; - static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - - u32_le nvmap_fd{}; - - u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp new file mode 100644 index 000000000..4898dc27a --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp @@ -0,0 +1,244 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstring> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" +#include "core/hle/service/nvdrv/devices/nvmap.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" +#include "core/memory.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" + +namespace Service::Nvidia::Devices { + +namespace { +// Splice vectors will copy count amount of type T from the input vector into the dst vector. +template <typename T> +std::size_t SpliceVectors(const std::vector<u8>& input, std::vector<T>& dst, std::size_t count, + std::size_t offset) { + std::memcpy(dst.data(), input.data() + offset, count * sizeof(T)); + offset += count * sizeof(T); + return offset; +} + +// Write vectors will write data to the output buffer +template <typename T> +std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::size_t offset) { + std::memcpy(dst.data() + offset, src.data(), src.size() * sizeof(T)); + offset += src.size() * sizeof(T); + return offset; +} +} // Anonymous namespace + +nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager(syncpoint_manager) {} +nvhost_nvdec_common::~nvhost_nvdec_common() = default; + +NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) { + IoctlSetNvmapFD params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); + LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + + nvmap_fd = params.nvmap_fd; + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSubmit params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); + LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count); + + // Instantiate param buffers + std::size_t offset = sizeof(IoctlSubmit); + std::vector<CommandBuffer> command_buffers(params.cmd_buffer_count); + std::vector<Reloc> relocs(params.relocation_count); + std::vector<u32> reloc_shifts(params.relocation_count); + std::vector<SyncptIncr> syncpt_increments(params.syncpoint_count); + std::vector<SyncptIncr> wait_checks(params.syncpoint_count); + std::vector<Fence> fences(params.fence_count); + + // Splice input into their respective buffers + offset = SpliceVectors(input, command_buffers, params.cmd_buffer_count, offset); + offset = SpliceVectors(input, relocs, params.relocation_count, offset); + offset = SpliceVectors(input, reloc_shifts, params.relocation_count, offset); + offset = SpliceVectors(input, syncpt_increments, params.syncpoint_count, offset); + offset = SpliceVectors(input, wait_checks, params.syncpoint_count, offset); + offset = SpliceVectors(input, fences, params.fence_count, offset); + + auto& gpu = system.GPU(); + if (gpu.UseNvdec()) { + for (std::size_t i = 0; i < syncpt_increments.size(); i++) { + const SyncptIncr& syncpt_incr = syncpt_increments[i]; + fences[i].id = syncpt_incr.id; + fences[i].value = + syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments); + } + } + for (const auto& cmd_buffer : command_buffers) { + auto object = nvmap_dev->GetObject(cmd_buffer.memory_id); + ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;); + const auto map = FindBufferMap(object->dma_map_addr); + if (!map) { + LOG_ERROR(Service_NVDRV, "Tried to submit an invalid offset 0x{:X} dma 0x{:X}", + object->addr, object->dma_map_addr); + return NvResult::Success; + } + Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count); + gpu.MemoryManager().ReadBlock(map->StartAddr() + cmd_buffer.offset, cmdlist.data(), + cmdlist.size() * sizeof(u32)); + gpu.PushCommandBuffer(cmdlist); + } + if (gpu.UseNvdec()) { + + fences[0].value = syncpoint_manager.IncreaseSyncpoint(fences[0].id, 1); + + Tegra::ChCommandHeaderList cmdlist{{(4 << 28) | fences[0].id}}; + gpu.PushCommandBuffer(cmdlist); + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit)); + // Some games expect command_buffers to be written back + offset = sizeof(IoctlSubmit); + offset = WriteVectors(output, command_buffers, offset); + offset = WriteVectors(output, relocs, offset); + offset = WriteVectors(output, reloc_shifts, offset); + offset = WriteVectors(output, syncpt_increments, offset); + offset = WriteVectors(output, wait_checks, offset); + offset = WriteVectors(output, fences, offset); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetSyncpoint params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); + LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param); + + if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) { + device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint(); + } + params.value = device_syncpoints[params.param]; + std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint)); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetWaitbase params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); + params.value = 0; // Seems to be hard coded at 0 + std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase)); + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); + + SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); + + auto& gpu = system.GPU(); + + for (auto& cmf_buff : cmd_buffer_handles) { + auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; + if (!object) { + LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::InvalidState; + } + if (object->dma_map_addr == 0) { + // NVDEC and VIC memory is in the 32-bit address space + // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space + const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size); + object->dma_map_addr = static_cast<u32>(low_addr); + // Ensure that the dma_map_addr is indeed in the lower 32-bit address space. + ASSERT(object->dma_map_addr == low_addr); + } + if (!object->dma_map_addr) { + LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); + } else { + cmf_buff.map_address = object->dma_map_addr; + AddBufferMap(object->dma_map_addr, object->size, object->addr, + object->status == nvmap::Object::Status::Allocated); + } + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); + std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(), + cmd_buffer_handles.size() * sizeof(MapBufferEntry)); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); + SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); + + auto& gpu = system.GPU(); + + for (auto& cmf_buff : cmd_buffer_handles) { + const auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; + if (!object) { + LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::InvalidState; + } + if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) { + gpu.MemoryManager().Unmap(object->dma_map_addr, *size); + } else { + // This occurs quite frequently, however does not seem to impact functionality + LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr, + object->dma_map_addr); + } + object->dma_map_addr = 0; + } + std::memset(output.data(), 0, output.size()); + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input, + std::vector<u8>& output) { + std::memcpy(&submit_timeout, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + return NvResult::Success; +} + +std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap( + GPUVAddr gpu_addr) const { + const auto it = std::find_if( + buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) { + return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr()); + }); + + ASSERT(it != buffer_mappings.end()); + return it->second; +} + +void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) { + buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated}); +} + +std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) { + const auto iter{buffer_mappings.find(gpu_addr)}; + if (iter == buffer_mappings.end()) { + return std::nullopt; + } + std::size_t size = 0; + if (iter->second.IsAllocated()) { + size = iter->second.Size(); + } + buffer_mappings.erase(iter); + return size; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h new file mode 100644 index 000000000..4c9d4ba41 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -0,0 +1,170 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service::Nvidia { +class SyncpointManager; + +namespace Devices { +class nvmap; + +class nvhost_nvdec_common : public nvdevice { +public: + explicit nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager); + ~nvhost_nvdec_common() override; + +protected: + class BufferMap final { + public: + constexpr BufferMap() = default; + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size) + : start_addr{start_addr}, end_addr{start_addr + size} {} + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) + : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr}, + is_allocated{is_allocated} {} + + constexpr VAddr StartAddr() const { + return start_addr; + } + + constexpr VAddr EndAddr() const { + return end_addr; + } + + constexpr std::size_t Size() const { + return end_addr - start_addr; + } + + constexpr VAddr CpuAddr() const { + return cpu_addr; + } + + constexpr bool IsAllocated() const { + return is_allocated; + } + + private: + GPUVAddr start_addr{}; + GPUVAddr end_addr{}; + VAddr cpu_addr{}; + bool is_allocated{}; + }; + + struct IoctlSetNvmapFD { + s32_le nvmap_fd{}; + }; + static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + + struct IoctlSubmitCommandBuffer { + u32_le id{}; + u32_le offset{}; + u32_le count{}; + }; + static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC, + "IoctlSubmitCommandBuffer is incorrect size"); + struct IoctlSubmit { + u32_le cmd_buffer_count{}; + u32_le relocation_count{}; + u32_le syncpoint_count{}; + u32_le fence_count{}; + }; + static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size"); + + struct CommandBuffer { + s32 memory_id{}; + u32 offset{}; + s32 word_count{}; + }; + static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size"); + + struct Reloc { + s32 cmdbuffer_memory{}; + s32 cmdbuffer_offset{}; + s32 target{}; + s32 target_offset{}; + }; + static_assert(sizeof(Reloc) == 0x10, "CommandBuffer has incorrect size"); + + struct SyncptIncr { + u32 id{}; + u32 increments{}; + }; + static_assert(sizeof(SyncptIncr) == 0x8, "CommandBuffer has incorrect size"); + + struct Fence { + u32 id{}; + u32 value{}; + }; + static_assert(sizeof(Fence) == 0x8, "CommandBuffer has incorrect size"); + + struct IoctlGetSyncpoint { + // Input + u32_le param{}; + // Output + u32_le value{}; + }; + static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size"); + + struct IoctlGetWaitbase { + u32_le unknown{}; // seems to be ignored? Nintendo added this + u32_le value{}; + }; + static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size"); + + struct IoctlMapBuffer { + u32_le num_entries{}; + u32_le data_address{}; // Ignored by the driver. + u32_le attach_host_ch_das{}; + }; + static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); + + struct IocGetIdParams { + // Input + u32_le param{}; + // Output + u32_le value{}; + }; + static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); + + // Used for mapping and unmapping command buffers + struct MapBufferEntry { + u32_le map_handle{}; + u32_le map_address{}; + }; + static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); + + /// Ioctl command implementations + NvResult SetNVMAPfd(const std::vector<u8>& input); + NvResult Submit(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); + NvResult MapBuffer(const std::vector<u8>& input, std::vector<u8>& output); + NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output); + + std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const; + void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); + std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr); + + s32_le nvmap_fd{}; + u32_le submit_timeout{}; + std::shared_ptr<nvmap> nvmap_dev; + SyncpointManager& syncpoint_manager; + std::array<u32, MaxSyncPoints> device_syncpoints{}; + // This is expected to be ordered, therefore we must use a map, not unordered_map + std::map<GPUVAddr, BufferMap> buffer_mappings; +}; +}; // namespace Devices +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index 96e7b7dab..2d06955c0 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -13,28 +13,44 @@ namespace Service::Nvidia::Devices { nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} nvhost_nvjpg::~nvhost_nvjpg() = default; -u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + switch (command.group) { + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input, output); + default: + break; + } + break; + default: + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 98dcac52f..43948d18d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -16,23 +16,21 @@ public: explicit nvhost_nvjpg(Core::System& system); ~nvhost_nvjpg() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - struct IoctlSetNvmapFD { - u32_le nvmap_fd; + s32_le nvmap_fd{}; }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - u32_le nvmap_fd{}; + s32_le nvmap_fd{}; - u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index c695b8863..72499654c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -2,39 +2,64 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> - #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_vic.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { +nvhost_vic::nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {} -nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {} nvhost_vic::~nvhost_vic() = default; -u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1: + return Submit(input, output); + case 0x2: + return GetSyncpoint(input, output); + case 0x3: + return GetWaitbase(input, output); + case 0x9: + return MapBuffer(input, output); + case 0xa: + return UnmapBuffer(input, output); + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input); + default: + break; + } + break; + default: + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { - IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); +NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - nvmap_fd = params.nvmap_fd; - return 0; +NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index bec32bea1..f401c61fa 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -4,35 +4,20 @@ #pragma once -#include <vector> -#include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/nvdrv/devices/nvdevice.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" namespace Service::Nvidia::Devices { -class nvhost_vic final : public nvdevice { +class nvhost_vic final : public nvhost_nvdec_common { public: - explicit nvhost_vic(Core::System& system); - ~nvhost_vic() override; - - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - -private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - - struct IoctlSetNvmapFD { - u32_le nvmap_fd; - }; - static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - - u32_le nvmap_fd{}; - - u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + explicit nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev, + SyncpointManager& syncpoint_manager); + ~nvhost_vic(); + + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; }; - } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 9436e16ad..4015a2740 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -11,13 +11,6 @@ namespace Service::Nvidia::Devices { -namespace NvErrCodes { -enum { - OperationNotPermitted = -1, - InvalidValue = -22, -}; -} - nvmap::nvmap(Core::System& system) : nvdevice(system) { // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to // represent this. @@ -26,6 +19,46 @@ nvmap::nvmap(Core::System& system) : nvdevice(system) { nvmap::~nvmap() = default; +NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + switch (command.group) { + case 0x1: + switch (command.cmd) { + case 0x1: + return IocCreate(input, output); + case 0x3: + return IocFromId(input, output); + case 0x4: + return IocAlloc(input, output); + case 0x5: + return IocFree(input, output); + case 0x9: + return IocParam(input, output); + case 0xe: + return IocGetId(input, output); + default: + break; + } + break; + default: + break; + } + + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvmap::Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvmap::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + VAddr nvmap::GetObjectAddress(u32 handle) const { auto object = GetObject(handle); ASSERT(object); @@ -33,28 +66,6 @@ VAddr nvmap::GetObjectAddress(u32 handle) const { return object->addr; } -u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - switch (static_cast<IoctlCommand>(command.raw)) { - case IoctlCommand::Create: - return IocCreate(input, output); - case IoctlCommand::Alloc: - return IocAlloc(input, output); - case IoctlCommand::GetId: - return IocGetId(input, output); - case IoctlCommand::FromId: - return IocFromId(input, output); - case IoctlCommand::Param: - return IocParam(input, output); - case IoctlCommand::Free: - return IocFree(input, output); - } - - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; -} - u32 nvmap::CreateObject(u32 size) { // Create a new nvmap object and obtain a handle to it. auto object = std::make_shared<Object>(); @@ -70,35 +81,35 @@ u32 nvmap::CreateObject(u32 size) { return handle; } -u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { IocCreateParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); if (!params.size) { LOG_ERROR(Service_NVDRV, "Size is 0"); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } params.handle = CreateObject(params.size); std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { IocAllocParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); if (!params.handle) { LOG_ERROR(Service_NVDRV, "Handle is 0"); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if ((params.align - 1) & params.align) { LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } const u32 min_alignment = 0x1000; @@ -109,12 +120,12 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (object->status == Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::OperationNotPermitted); + return NvResult::InsufficientMemory; } object->flags = params.flags; @@ -124,10 +135,10 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { object->status = Object::Status::Allocated; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { IocGetIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -135,22 +146,22 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { if (!params.handle) { LOG_ERROR(Service_NVDRV, "Handle is zero"); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::OperationNotPermitted); + return NvResult::BadValue; } params.id = object->id; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { IocFromIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -160,13 +171,13 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { [&](const auto& entry) { return entry.second->id == params.id; }); if (itr == handles.end()) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } auto& object = itr->second; if (object->status != Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } itr->second->refcount++; @@ -175,10 +186,10 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { params.handle = itr->first; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 }; IocParamParams params; @@ -189,12 +200,12 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (object->status != Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::OperationNotPermitted); + return NvResult::BadValue; } switch (static_cast<ParamTypes>(params.param)) { @@ -216,10 +227,10 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { } std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { // TODO(Subv): These flags are unconfirmed. enum FreeFlags { Freed = 0, @@ -234,14 +245,14 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { auto itr = handles.find(params.handle); if (itr == handles.end()) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (!itr->second->refcount) { LOG_ERROR( Service_NVDRV, "There is no references to this object. The object is already freed. handle={:08X}", params.handle); - return static_cast<u32>(NvErrCodes::InvalidValue); + return NvResult::BadValue; } itr->second->refcount--; @@ -261,7 +272,7 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { handles.erase(params.handle); std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 84624be00..4484bd79f 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -19,13 +19,15 @@ public: explicit nvmap(Core::System& system); ~nvmap() override; + NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; + /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; - u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - /// Represents an nvmap object. struct Object { enum class Status { Created, Allocated }; @@ -37,6 +39,7 @@ public: VAddr addr; Status status; u32 refcount; + u32 dma_map_addr; }; std::shared_ptr<Object> GetObject(u32 handle) const { @@ -57,76 +60,68 @@ private: /// Mapping of currently allocated handles to the objects they represent. std::unordered_map<u32, std::shared_ptr<Object>> handles; - enum class IoctlCommand : u32 { - Create = 0xC0080101, - FromId = 0xC0080103, - Alloc = 0xC0200104, - Free = 0xC0180105, - Param = 0xC00C0109, - GetId = 0xC008010E, - }; struct IocCreateParams { // Input - u32_le size; + u32_le size{}; // Output - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size"); struct IocFromIdParams { // Input - u32_le id; + u32_le id{}; // Output - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size"); struct IocAllocParams { // Input - u32_le handle; - u32_le heap_mask; - u32_le flags; - u32_le align; - u8 kind; + u32_le handle{}; + u32_le heap_mask{}; + u32_le flags{}; + u32_le align{}; + u8 kind{}; INSERT_PADDING_BYTES(7); - u64_le addr; + u64_le addr{}; }; static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size"); struct IocFreeParams { - u32_le handle; + u32_le handle{}; INSERT_PADDING_BYTES(4); - u64_le address; - u32_le size; - u32_le flags; + u64_le address{}; + u32_le size{}; + u32_le flags{}; }; static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size"); struct IocParamParams { // Input - u32_le handle; - u32_le param; + u32_le handle{}; + u32_le param{}; // Output - u32_le result; + u32_le result{}; }; static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size"); struct IocGetIdParams { // Output - u32_le id; + u32_le id{}; // Input - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); u32 CreateObject(u32 size); - u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocFromId(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocParam(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocFree(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index 88fbfa9b0..2e1150867 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -6,10 +6,10 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdrv.h" @@ -23,124 +23,167 @@ void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { void NVDRV::Open(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + const auto& buffer = ctx.ReadBuffer(); - std::string device_name(buffer.begin(), buffer.end()); + const std::string device_name(buffer.begin(), buffer.end()); + DeviceFD fd = nvdrv->Open(device_name); - u32 fd = nvdrv->Open(device_name); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(fd); - rb.Push<u32>(0); + rb.Push<DeviceFD>(fd); + rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed); +} + +void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(result); } -void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) { +void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop<u32>(); - u32 command = rp.Pop<u32>(); - - /// Ioctl 3 has 2 outputs, first in the input params, second is the result - std::vector<u8> output(ctx.GetWriteBufferSize(0)); - std::vector<u8> output2; - if (version == IoctlVersion::Version3) { - output2.resize((ctx.GetWriteBufferSize(1))); + const auto fd = rp.Pop<DeviceFD>(); + const auto command = rp.PopRaw<Ioctl>(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; } - /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer. - /// KickOfPB uses this - auto input = ctx.ReadBuffer(0); + // Check device + std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); + const auto input_buffer = ctx.ReadBuffer(0); - std::vector<u8> input2; - if (version == IoctlVersion::Version2) { - input2 = ctx.ReadBuffer(1); + const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); } - IoctlCtrl ctrl{}; - - u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version); - - if (ctrl.must_delay) { - ctrl.fresh_call = false; - ctx.SleepClientThread( - "NVServices::DelayedResponse", ctrl.timeout, - [=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_, - Kernel::ThreadWakeupReason reason) { - IoctlCtrl ctrl2{ctrl}; - std::vector<u8> tmp_output = output; - std::vector<u8> tmp_output2 = output2; - const u32 ioctl_result = nvdrv->Ioctl(fd, command, input, input2, tmp_output, - tmp_output2, ctrl2, version); - ctx_.WriteBuffer(tmp_output, 0); - if (version == IoctlVersion::Version3) { - ctx_.WriteBuffer(tmp_output2, 1); - } - IPC::ResponseBuilder rb{ctx_, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(ioctl_result); - }, - nvdrv->GetEventWriteable(ctrl.event_id)); - } else { - ctx.WriteBuffer(output); - if (version == IoctlVersion::Version3) { - ctx.WriteBuffer(output2, 1); - } - } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(result); -} - -void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version1); + rb.PushEnum(nv_result); } void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version2); + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop<DeviceFD>(); + const auto command = rp.PopRaw<Ioctl>(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + const auto input_inlined_buffer = ctx.ReadBuffer(1); + std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); + + const auto nv_result = + nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(nv_result); } void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version3); + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop<DeviceFD>(); + const auto command = rp.PopRaw<Ioctl>(); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); + std::vector<u8> output_buffer_inline(ctx.GetWriteBufferSize(1)); + + const auto nv_result = + nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer, 0); + ctx.WriteBuffer(output_buffer_inline, 1); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(nv_result); } void NVDRV::Close(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); - IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop<u32>(); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } - auto result = nvdrv->Close(fd); + IPC::RequestParser rp{ctx}; + const auto fd = rp.Pop<DeviceFD>(); + const auto result = nvdrv->Close(fd); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(result); } void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + is_initialized = true; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + rb.PushEnum(NvResult::Success); } void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop<u32>(); - // TODO(Blinkhawk): Figure the meaning of the flag at bit 16 - u32 event_id = rp.Pop<u32>() & 0x000000FF; + const auto fd = rp.Pop<DeviceFD>(); + const auto event_id = rp.Pop<u32>() & 0x00FF; LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); - IPC::ResponseBuilder rb{ctx, 3, 1}; - rb.Push(RESULT_SUCCESS); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto nv_result = nvdrv->VerifyFD(fd); + if (nv_result != NvResult::Success) { + LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd); + ServiceError(ctx, nv_result); + return; + } + if (event_id < MaxNvEvents) { + IPC::ResponseBuilder rb{ctx, 3, 1}; + rb.Push(RESULT_SUCCESS); auto event = nvdrv->GetEvent(event_id); event->Clear(); rb.PushCopyObjects(event); - rb.Push<u32>(NvResult::Success); + rb.PushEnum(NvResult::Success); } else { - rb.Push<u32>(0); - rb.Push<u32>(NvResult::BadParameter); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(NvResult::BadParameter); } } @@ -151,7 +194,7 @@ void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + rb.PushEnum(NvResult::Success); } void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) { @@ -164,8 +207,9 @@ void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ct void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.PushEnum(NvResult::Success); } void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { @@ -177,11 +221,11 @@ void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) - : ServiceFramework(name), nvdrv(std::move(nvdrv)) { +NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name) + : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} { static const FunctionInfo functions[] = { {0, &NVDRV::Open, "Open"}, - {1, &NVDRV::Ioctl, "Ioctl"}, + {1, &NVDRV::Ioctl1, "Ioctl"}, {2, &NVDRV::Close, "Close"}, {3, &NVDRV::Initialize, "Initialize"}, {4, &NVDRV::QueryEvent, "QueryEvent"}, diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 72e17a728..0e764c53f 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -9,21 +9,21 @@ #include "core/hle/service/service.h" namespace Kernel { -class WritableEvent; +class KWritableEvent; } namespace Service::Nvidia { class NVDRV final : public ServiceFramework<NVDRV> { public: - NVDRV(std::shared_ptr<Module> nvdrv, const char* name); + explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name); ~NVDRV() override; - void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value); + void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value); private: void Open(Kernel::HLERequestContext& ctx); - void Ioctl(Kernel::HLERequestContext& ctx); + void Ioctl1(Kernel::HLERequestContext& ctx); void Ioctl2(Kernel::HLERequestContext& ctx); void Ioctl3(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); @@ -33,11 +33,13 @@ private: void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx); void GetStatus(Kernel::HLERequestContext& ctx); void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); - void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version); + + void ServiceError(Kernel::HLERequestContext& ctx, NvResult result); std::shared_ptr<Module> nvdrv; u64 pid{}; + bool is_initialized{}; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h index 529b03471..3294bc0e7 100644 --- a/src/core/hle/service/nvdrv/nvdata.h +++ b/src/core/hle/service/nvdrv/nvdata.h @@ -1,12 +1,16 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_types.h" namespace Service::Nvidia { constexpr u32 MaxSyncPoints = 192; constexpr u32 MaxNvEvents = 64; +using DeviceFD = s32; + +constexpr DeviceFD INVALID_NVDRV_FD = -1; struct Fence { s32 id; @@ -20,11 +24,61 @@ struct MultiFence { std::array<Fence, 4> fences; }; -enum NvResult : u32 { - Success = 0, - BadParameter = 4, - Timeout = 5, - ResourceError = 15, +enum class NvResult : u32 { + Success = 0x0, + NotImplemented = 0x1, + NotSupported = 0x2, + NotInitialized = 0x3, + BadParameter = 0x4, + Timeout = 0x5, + InsufficientMemory = 0x6, + ReadOnlyAttribute = 0x7, + InvalidState = 0x8, + InvalidAddress = 0x9, + InvalidSize = 0xA, + BadValue = 0xB, + AlreadyAllocated = 0xD, + Busy = 0xE, + ResourceError = 0xF, + CountMismatch = 0x10, + OverFlow = 0x11, + InsufficientTransferMemory = 0x1000, + InsufficientVideoMemory = 0x10000, + BadSurfaceColorScheme = 0x10001, + InvalidSurface = 0x10002, + SurfaceNotSupported = 0x10003, + DispInitFailed = 0x20000, + DispAlreadyAttached = 0x20001, + DispTooManyDisplays = 0x20002, + DispNoDisplaysAttached = 0x20003, + DispModeNotSupported = 0x20004, + DispNotFound = 0x20005, + DispAttachDissallowed = 0x20006, + DispTypeNotSupported = 0x20007, + DispAuthenticationFailed = 0x20008, + DispNotAttached = 0x20009, + DispSamePwrState = 0x2000A, + DispEdidFailure = 0x2000B, + DispDsiReadAckError = 0x2000C, + DispDsiReadInvalidResp = 0x2000D, + FileWriteFailed = 0x30000, + FileReadFailed = 0x30001, + EndOfFile = 0x30002, + FileOperationFailed = 0x30003, + DirOperationFailed = 0x30004, + EndOfDirList = 0x30005, + ConfigVarNotFound = 0x30006, + InvalidConfigVar = 0x30007, + LibraryNotFound = 0x30008, + SymbolNotFound = 0x30009, + MemoryMapFailed = 0x3000A, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + DeviceNotFound = 0x30011, + KernelDriverNotFound = 0x30012, + FileNotFound = 0x30013, + PathAlreadyExists = 0x30014, + ModuleNotPresent = 0xA000E, }; enum class EventState { @@ -34,21 +88,13 @@ enum class EventState { Busy = 3, }; -enum class IoctlVersion : u32 { - Version1, - Version2, - Version3, -}; - -struct IoctlCtrl { - // First call done to the servioce for services that call itself again after a call. - bool fresh_call{true}; - // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep - bool must_delay{}; - // Timeout for the delay - s64 timeout{}; - // NV Event Id - s32 event_id{-1}; +union Ioctl { + u32_le raw; + BitField<0, 8, u32> cmd; + BitField<8, 8, u32> group; + BitField<16, 14, u32> length; + BitField<30, 1, u32> is_in; + BitField<31, 1, u32> is_out; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 197c77db0..abba80112 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -5,9 +5,11 @@ #include <utility> #include <fmt/format.h> +#include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" @@ -21,6 +23,7 @@ #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvmemp.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/hle/service/nvflinger/nvflinger.h" namespace Service::Nvidia { @@ -28,66 +31,136 @@ namespace Service::Nvidia { void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, Core::System& system) { auto module_ = std::make_shared<Module>(system); - std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager); - std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager); - std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager); - std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager); - std::make_shared<NVMEMP>()->InstallAsService(service_manager); + std::make_shared<NVDRV>(system, module_, "nvdrv")->InstallAsService(service_manager); + std::make_shared<NVDRV>(system, module_, "nvdrv:a")->InstallAsService(service_manager); + std::make_shared<NVDRV>(system, module_, "nvdrv:s")->InstallAsService(service_manager); + std::make_shared<NVDRV>(system, module_, "nvdrv:t")->InstallAsService(service_manager); + std::make_shared<NVMEMP>(system)->InstallAsService(service_manager); nvflinger.SetNVDrvInstance(module_); } -Module::Module(Core::System& system) { +Module::Module(Core::System& system) : syncpoint_manager{system.GPU()} { auto& kernel = system.Kernel(); for (u32 i = 0; i < MaxNvEvents; i++) { std::string event_label = fmt::format("NVDRV::NvEvent_{}", i); - events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(kernel, event_label); + events_interface.events[i] = {Kernel::KEvent::Create(kernel, std::move(event_label))}; + events_interface.events[i].event->Initialize(); events_interface.status[i] = EventState::Free; events_interface.registered[i] = false; } auto nvmap_dev = std::make_shared<Devices::nvmap>(system); devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev); - devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev); + devices["/dev/nvhost-gpu"] = + std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager); devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system); devices["/dev/nvmap"] = nvmap_dev; devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev); - devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface); - devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system); + devices["/dev/nvhost-ctrl"] = + std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager); + devices["/dev/nvhost-nvdec"] = + std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager); devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system); - devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system); + devices["/dev/nvhost-vic"] = + std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager); } Module::~Module() = default; -u32 Module::Open(const std::string& device_name) { - ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}", - device_name); +NvResult Module::VerifyFD(DeviceFD fd) const { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + if (open_files.find(fd) == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return NvResult::Success; +} + +DeviceFD Module::Open(const std::string& device_name) { + if (devices.find(device_name) == devices.end()) { + LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name); + return INVALID_NVDRV_FD; + } auto device = devices[device_name]; - const u32 fd = next_fd++; + const DeviceFD fd = next_fd++; open_files[fd] = std::move(device); return fd; } -u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - auto itr = open_files.find(fd); - ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); +NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } - auto& device = itr->second; - return device->ioctl({command}, input, input2, output, output2, ctrl, version); + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl1(command, input, output); } -ResultCode Module::Close(u32 fd) { - auto itr = open_files.find(fd); - ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); +NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl2(command, input, inline_input, output); +} + +NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output, std::vector<u8>& inline_output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl3(command, input, output, inline_output); +} + +NvResult Module::Close(DeviceFD fd) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } open_files.erase(itr); - // TODO(flerovium): return correct result code if operation failed. - return RESULT_SUCCESS; + return NvResult::Success; } void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) { @@ -95,17 +168,17 @@ void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) { if (events_interface.assigned_syncpt[i] == syncpoint_id && events_interface.assigned_value[i] == value) { events_interface.LiberateEvent(i); - events_interface.events[i].writable->Signal(); + events_interface.events[i].event->GetWritableEvent()->Signal(); } } } -std::shared_ptr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const { - return events_interface.events[event_id].readable; +std::shared_ptr<Kernel::KReadableEvent> Module::GetEvent(const u32 event_id) const { + return events_interface.events[event_id].event->GetReadableEvent(); } -std::shared_ptr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const { - return events_interface.events[event_id].writable; +std::shared_ptr<Kernel::KWritableEvent> Module::GetEventWriteable(const u32 event_id) const { + return events_interface.events[event_id].event->GetWritableEvent(); } } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index d7a1bef91..53719aadd 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -7,30 +7,43 @@ #include <memory> #include <unordered_map> #include <vector> + #include "common/common_types.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/hle/service/service.h" namespace Core { class System; } +namespace Kernel { +class KEvent; +} + namespace Service::NVFlinger { class NVFlinger; } namespace Service::Nvidia { +class SyncpointManager; + namespace Devices { class nvdevice; } +/// Represents an Nvidia event +struct NvEvent { + std::shared_ptr<Kernel::KEvent> event; + Fence fence{}; +}; + struct EventInterface { // Mask representing currently busy events u64 events_mask{}; // Each kernel event associated to an NV event - std::array<Kernel::EventPair, MaxNvEvents> events; + std::array<NvEvent, MaxNvEvents> events; // The status of the current NVEvent std::array<EventState, MaxNvEvents> status{}; // Tells if an NVEvent is registered or not @@ -54,7 +67,7 @@ struct EventInterface { } mask = mask >> 1; } - return {}; + return std::nullopt; } void SetEventStatus(const u32 event_id, EventState new_status) { EventState old_status = status[event_id]; @@ -91,7 +104,7 @@ struct EventInterface { class Module final { public: - Module(Core::System& system); + explicit Module(Core::System& system_); ~Module(); /// Returns a pointer to one of the available devices, identified by its name. @@ -103,27 +116,39 @@ public: return std::static_pointer_cast<T>(itr->second); } + NvResult VerifyFD(DeviceFD fd) const; + /// Opens a device node and returns a file descriptor to it. - u32 Open(const std::string& device_name); + DeviceFD Open(const std::string& device_name); + /// Sends an ioctl command to the specified file descriptor. - u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, - std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, - IoctlVersion version); + NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output); + + NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& inline_input, std::vector<u8>& output); + + NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + std::vector<u8>& output, std::vector<u8>& inline_output); + /// Closes a device file descriptor and returns operation success. - ResultCode Close(u32 fd); + NvResult Close(DeviceFD fd); void SignalSyncpt(const u32 syncpoint_id, const u32 value); - std::shared_ptr<Kernel::ReadableEvent> GetEvent(u32 event_id) const; + std::shared_ptr<Kernel::KReadableEvent> GetEvent(u32 event_id) const; - std::shared_ptr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const; + std::shared_ptr<Kernel::KWritableEvent> GetEventWriteable(u32 event_id) const; private: + /// Manages syncpoints on the host + SyncpointManager syncpoint_manager; + /// Id to use for the next open file descriptor. - u32 next_fd = 1; + DeviceFD next_fd = 1; /// Mapping of file descriptors to the devices they reference. - std::unordered_map<u32, std::shared_ptr<Devices::nvdevice>> open_files; + std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files; /// Mapping of device node names to their implementation. std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices; diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp index 73b37e805..331c02243 100644 --- a/src/core/hle/service/nvdrv/nvmemp.cpp +++ b/src/core/hle/service/nvdrv/nvmemp.cpp @@ -8,7 +8,7 @@ namespace Service::Nvidia { -NVMEMP::NVMEMP() : ServiceFramework("nvmemp") { +NVMEMP::NVMEMP(Core::System& system_) : ServiceFramework{system_, "nvmemp"} { static const FunctionInfo functions[] = { {0, &NVMEMP::Open, "Open"}, {1, &NVMEMP::GetAruid, "GetAruid"}, diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h index c453ee4db..724c27ef9 100644 --- a/src/core/hle/service/nvdrv/nvmemp.h +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Nvidia { class NVMEMP final : public ServiceFramework<NVMEMP> { public: - NVMEMP(); + explicit NVMEMP(Core::System& system_); ~NVMEMP() override; private: diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp new file mode 100644 index 000000000..0151a03b7 --- /dev/null +++ b/src/core/hle/service/nvdrv/syncpoint_manager.cpp @@ -0,0 +1,39 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" +#include "video_core/gpu.h" + +namespace Service::Nvidia { + +SyncpointManager::SyncpointManager(Tegra::GPU& gpu) : gpu{gpu} {} + +SyncpointManager::~SyncpointManager() = default; + +u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) { + syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id); + return GetSyncpointMin(syncpoint_id); +} + +u32 SyncpointManager::AllocateSyncpoint() { + for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) { + if (!syncpoints[syncpoint_id].is_allocated) { + syncpoints[syncpoint_id].is_allocated = true; + return syncpoint_id; + } + } + UNREACHABLE_MSG("No more available syncpoints!"); + return {}; +} + +u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) { + for (u32 index = 0; index < value; ++index) { + syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed); + } + + return GetSyncpointMax(syncpoint_id); +} + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h new file mode 100644 index 000000000..d395c5d0b --- /dev/null +++ b/src/core/hle/service/nvdrv/syncpoint_manager.h @@ -0,0 +1,85 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <atomic> + +#include "common/common_types.h" +#include "core/hle/service/nvdrv/nvdata.h" + +namespace Tegra { +class GPU; +} + +namespace Service::Nvidia { + +class SyncpointManager final { +public: + explicit SyncpointManager(Tegra::GPU& gpu); + ~SyncpointManager(); + + /** + * Returns true if the specified syncpoint is expired for the given value. + * @param syncpoint_id Syncpoint ID to check. + * @param value Value to check against the specified syncpoint. + * @returns True if the specified syncpoint is expired for the given value, otherwise False. + */ + bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const { + return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value); + } + + /** + * Gets the lower bound for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to get the lower bound for. + * @returns The lower bound for the specified syncpoint. + */ + u32 GetSyncpointMin(u32 syncpoint_id) const { + return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed); + } + + /** + * Gets the uper bound for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to get the upper bound for. + * @returns The upper bound for the specified syncpoint. + */ + u32 GetSyncpointMax(u32 syncpoint_id) const { + return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed); + } + + /** + * Refreshes the minimum value for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to be refreshed. + * @returns The new syncpoint minimum value. + */ + u32 RefreshSyncpoint(u32 syncpoint_id); + + /** + * Allocates a new syncoint. + * @returns The syncpoint ID for the newly allocated syncpoint. + */ + u32 AllocateSyncpoint(); + + /** + * Increases the maximum value for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to be increased. + * @param value Value to increase the specified syncpoint by. + * @returns The new syncpoint maximum value. + */ + u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value); + +private: + struct Syncpoint { + std::atomic<u32> min; + std::atomic<u32> max; + std::atomic<bool> is_allocated; + }; + + std::array<Syncpoint, MaxSyncPoints> syncpoints{}; + + Tegra::GPU& gpu; +}; + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 637b310d7..7842a82ed 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -7,151 +7,194 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvflinger/buffer_queue.h" namespace Service::NVFlinger { BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id, u64 layer_id) : id(id), layer_id(layer_id) { - buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, "BufferQueue NativeHandle"); + buffer_wait_event = Kernel::KEvent::Create(kernel, "BufferQueue:WaitEvent"); + buffer_wait_event->Initialize(); } BufferQueue::~BufferQueue() = default; void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { + ASSERT(slot < buffer_slots); LOG_WARNING(Service, "Adding graphics buffer {}", slot); - free_buffers.push_back(slot); - queue.push_back({ + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); + + buffers[slot] = { .slot = slot, .status = Buffer::Status::Free, .igbp_buffer = igbp_buffer, - }); + .transform = {}, + .crop_rect = {}, + .swap_interval = 0, + .multi_fence = {}, + }; - buffer_wait_event.writable->Signal(); + buffer_wait_event->GetWritableEvent()->Signal(); } std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, u32 height) { + // Wait for first request before trying to dequeue + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers_condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; }); + } - if (free_buffers.empty()) { + if (!is_connect) { + // Buffer was disconnected while the thread was blocked, this is most likely due to + // emulation being stopped return std::nullopt; } + std::unique_lock lock{free_buffers_mutex}; + auto f_itr = free_buffers.begin(); - auto itr = queue.end(); + auto slot = buffers.size(); while (f_itr != free_buffers.end()) { - auto slot = *f_itr; - itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { - // Only consider free buffers. Buffers become free once again after they've been - // Acquired and Released by the compositor, see the NVFlinger::Compose method. - if (buffer.status != Buffer::Status::Free) { - return false; - } - - if (buffer.slot != slot) { - return false; - } - - // Make sure that the parameters match. - return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height; - }); - - if (itr != queue.end()) { + const Buffer& buffer = buffers[*f_itr]; + if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width && + buffer.igbp_buffer.height == height) { + slot = *f_itr; free_buffers.erase(f_itr); break; } ++f_itr; } - - if (itr == queue.end()) { + if (slot == buffers.size()) { return std::nullopt; } - - itr->status = Buffer::Status::Dequeued; - return {{itr->slot, &itr->multi_fence}}; + buffers[slot].status = Buffer::Status::Dequeued; + return {{buffers[slot].slot, &buffers[slot].multi_fence}}; } const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - return itr->igbp_buffer; + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Dequeued); + ASSERT(buffers[slot].slot == slot); + + return buffers[slot].igbp_buffer; } void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform, const Common::Rectangle<int>& crop_rect, u32 swap_interval, Service::Nvidia::MultiFence& multi_fence) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - itr->status = Buffer::Status::Queued; - itr->transform = transform; - itr->crop_rect = crop_rect; - itr->swap_interval = swap_interval; - itr->multi_fence = multi_fence; + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Dequeued); + ASSERT(buffers[slot].slot == slot); + + buffers[slot].status = Buffer::Status::Queued; + buffers[slot].transform = transform; + buffers[slot].crop_rect = crop_rect; + buffers[slot].swap_interval = swap_interval; + buffers[slot].multi_fence = multi_fence; + std::unique_lock lock{queue_sequence_mutex}; queue_sequence.push_back(slot); } +void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) { + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status != Buffer::Status::Free); + ASSERT(buffers[slot].slot == slot); + + buffers[slot].status = Buffer::Status::Free; + buffers[slot].multi_fence = multi_fence; + buffers[slot].swap_interval = 0; + + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); + + buffer_wait_event->GetWritableEvent()->Signal(); +} + std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() { - auto itr = queue.end(); + std::unique_lock lock{queue_sequence_mutex}; + std::size_t buffer_slot = buffers.size(); // Iterate to find a queued buffer matching the requested slot. - while (itr == queue.end() && !queue_sequence.empty()) { - const u32 slot = queue_sequence.front(); - itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) { - return buffer.status == Buffer::Status::Queued && buffer.slot == slot; - }); + while (buffer_slot == buffers.size() && !queue_sequence.empty()) { + const auto slot = static_cast<std::size_t>(queue_sequence.front()); + ASSERT(slot < buffers.size()); + if (buffers[slot].status == Buffer::Status::Queued) { + ASSERT(buffers[slot].slot == slot); + buffer_slot = slot; + } queue_sequence.pop_front(); } - if (itr == queue.end()) { + if (buffer_slot == buffers.size()) { return std::nullopt; } - itr->status = Buffer::Status::Acquired; - return *itr; + buffers[buffer_slot].status = Buffer::Status::Acquired; + return {{buffers[buffer_slot]}}; } void BufferQueue::ReleaseBuffer(u32 slot) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Acquired); - itr->status = Buffer::Status::Free; - free_buffers.push_back(slot); - - buffer_wait_event.writable->Signal(); + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Acquired); + ASSERT(buffers[slot].slot == slot); + + buffers[slot].status = Buffer::Status::Free; + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); + + buffer_wait_event->GetWritableEvent()->Signal(); } -void BufferQueue::Disconnect() { - queue.clear(); +void BufferQueue::Connect() { + std::unique_lock lock{queue_sequence_mutex}; queue_sequence.clear(); - id = 1; - layer_id = 1; + is_connect = true; +} + +void BufferQueue::Disconnect() { + buffers.fill({}); + { + std::unique_lock lock{queue_sequence_mutex}; + queue_sequence.clear(); + } + buffer_wait_event->GetWritableEvent()->Signal(); + is_connect = false; + free_buffers_condition.notify_one(); } u32 BufferQueue::Query(QueryType type) { - LOG_WARNING(Service, "(STUBBED) called type={}", static_cast<u32>(type)); + LOG_WARNING(Service, "(STUBBED) called type={}", type); switch (type) { case QueryType::NativeWindowFormat: return static_cast<u32>(PixelFormat::RGBA8888); + case QueryType::NativeWindowWidth: + case QueryType::NativeWindowHeight: + break; } - - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented query type={}", type); return 0; } -std::shared_ptr<Kernel::WritableEvent> BufferQueue::GetWritableBufferWaitEvent() const { - return buffer_wait_event.writable; +std::shared_ptr<Kernel::KWritableEvent> BufferQueue::GetWritableBufferWaitEvent() const { + return buffer_wait_event->GetWritableEvent(); } -std::shared_ptr<Kernel::ReadableEvent> BufferQueue::GetBufferWaitEvent() const { - return buffer_wait_event.readable; +std::shared_ptr<Kernel::KReadableEvent> BufferQueue::GetBufferWaitEvent() const { + return buffer_wait_event->GetReadableEvent(); } } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 8a837e5aa..163fa4c54 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -4,7 +4,9 @@ #pragma once +#include <condition_variable> #include <list> +#include <mutex> #include <optional> #include <vector> @@ -12,15 +14,18 @@ #include "common/math_util.h" #include "common/swap.h" #include "core/hle/kernel/object.h" -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/nvdata.h" namespace Kernel { class KernelCore; -} +class KEvent; +class KReadableEvent; +class KWritableEvent; +} // namespace Kernel namespace Service::NVFlinger { +constexpr u32 buffer_slots = 0x40; struct IGBPBuffer { u32_le magic; u32_le width; @@ -95,8 +100,10 @@ public: void QueueBuffer(u32 slot, BufferTransformFlags transform, const Common::Rectangle<int>& crop_rect, u32 swap_interval, Service::Nvidia::MultiFence& multi_fence); + void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence); std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer(); void ReleaseBuffer(u32 slot); + void Connect(); void Disconnect(); u32 Query(QueryType type); @@ -104,18 +111,30 @@ public: return id; } - std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const; + bool IsConnected() const { + return is_connect; + } + + std::shared_ptr<Kernel::KWritableEvent> GetWritableBufferWaitEvent() const; - std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetBufferWaitEvent() const; private: - u32 id; - u64 layer_id; + BufferQueue(const BufferQueue&) = delete; + + u32 id{}; + u64 layer_id{}; + std::atomic_bool is_connect{}; std::list<u32> free_buffers; - std::vector<Buffer> queue; + std::array<Buffer, buffer_slots> buffers; std::list<u32> queue_sequence; - Kernel::EventPair buffer_wait_event; + std::shared_ptr<Kernel::KEvent> buffer_wait_event; + + std::mutex free_buffers_mutex; + std::condition_variable free_buffers_condition; + + std::mutex queue_sequence_mutex; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index f644a460d..ac2906e5b 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -14,8 +14,8 @@ #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/hardware_properties.h" +#include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/readable_event.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" @@ -38,6 +38,10 @@ void NVFlinger::SplitVSync() { system.RegisterHostThread(); std::string name = "yuzu:VSyncThread"; MicroProfileOnThreadCreate(name.c_str()); + + // Cleanup + SCOPE_EXIT({ MicroProfileOnThreadExit(); }); + Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); s64 delay = 0; @@ -88,6 +92,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) { } NVFlinger::~NVFlinger() { + for (auto& buffer_queue : buffer_queues) { + buffer_queue->Disconnect(); + } + if (system.IsMulticore()) { is_running = false; wait_event->Set(); @@ -104,6 +112,8 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) { } std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) { + const auto guard = Lock(); + LOG_DEBUG(Service, "Opening \"{}\" display", name); // TODO(Subv): Currently we only support the Default display. @@ -114,43 +124,49 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) { [&](const VI::Display& display) { return display.GetName() == name; }); if (itr == displays.end()) { - return {}; + return std::nullopt; } return itr->GetID(); } std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { + const auto guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { - return {}; + return std::nullopt; } const u64 layer_id = next_layer_id++; const u32 buffer_queue_id = next_buffer_queue_id++; - buffer_queues.emplace_back(system.Kernel(), buffer_queue_id, layer_id); - display->CreateLayer(layer_id, buffer_queues.back()); + buffer_queues.emplace_back( + std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id)); + display->CreateLayer(layer_id, *buffer_queues.back()); return layer_id; } void NVFlinger::CloseLayer(u64 layer_id) { + const auto guard = Lock(); + for (auto& display : displays) { display.CloseLayer(layer_id); } } std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const { + const auto guard = Lock(); const auto* const layer = FindLayer(display_id, layer_id); if (layer == nullptr) { - return {}; + return std::nullopt; } return layer->GetBufferQueue().GetId(); } -std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const { +std::shared_ptr<Kernel::KReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const { + const auto guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { @@ -160,20 +176,16 @@ std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) return display->GetVSyncEvent(); } -BufferQueue& NVFlinger::FindBufferQueue(u32 id) { +BufferQueue* NVFlinger::FindBufferQueue(u32 id) { + const auto guard = Lock(); const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [id](const auto& queue) { return queue.GetId() == id; }); + [id](const auto& queue) { return queue->GetId() == id; }); - ASSERT(itr != buffer_queues.end()); - return *itr; -} - -const BufferQueue& NVFlinger::FindBufferQueue(u32 id) const { - const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [id](const auto& queue) { return queue.GetId() == id; }); + if (itr == buffer_queues.end()) { + return nullptr; + } - ASSERT(itr != buffer_queues.end()); - return *itr; + return itr->get(); } VI::Display* NVFlinger::FindDisplay(u64 display_id) { @@ -242,6 +254,10 @@ void NVFlinger::Compose() { const auto& igbp_buffer = buffer->get().igbp_buffer; + if (!system.IsPoweredOn()) { + return; // We are likely shutting down + } + auto& gpu = system.GPU(); const auto& multi_fence = buffer->get().multi_fence; guard->unlock(); diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 1ebe949c0..6fe2c7f2a 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -26,8 +26,8 @@ struct EventType; } // namespace Core::Timing namespace Kernel { -class ReadableEvent; -class WritableEvent; +class KReadableEvent; +class KWritableEvent; } // namespace Kernel namespace Service::Nvidia { @@ -72,13 +72,10 @@ public: /// Gets the vsync event for the specified display. /// /// If an invalid display ID is provided, then nullptr is returned. - [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const; + [[nodiscard]] std::shared_ptr<Kernel::KReadableEvent> FindVsyncEvent(u64 display_id) const; /// Obtains a buffer queue identified by the ID. - [[nodiscard]] BufferQueue& FindBufferQueue(u32 id); - - /// Obtains a buffer queue identified by the ID. - [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const; + [[nodiscard]] BufferQueue* FindBufferQueue(u32 id); /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. @@ -86,11 +83,11 @@ public: [[nodiscard]] s64 GetNextTicks() const; +private: [[nodiscard]] std::unique_lock<std::mutex> Lock() const { return std::unique_lock{*guard}; } -private: /// Finds the display identified by the specified ID. [[nodiscard]] VI::Display* FindDisplay(u64 display_id); @@ -110,7 +107,7 @@ private: std::shared_ptr<Nvidia::Module> nvdrv; std::vector<VI::Display> displays; - std::vector<BufferQueue> buffer_queues; + std::vector<std::unique_ptr<BufferQueue>> buffer_queues; /// Id to use for the next layer that is created, this counter is shared among all displays. u64 next_layer_id = 1; diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp new file mode 100644 index 000000000..e2ac71fa1 --- /dev/null +++ b/src/core/hle/service/olsc/olsc.cpp @@ -0,0 +1,80 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/olsc/olsc.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" + +namespace Service::OLSC { + +class OLSC final : public ServiceFramework<OLSC> { +public: + explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &OLSC::Initialize, "Initialize"}, + {10, nullptr, "VerifySaveDataBackupLicenseAsync"}, + {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"}, + {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, + {15, nullptr, "SetCustomData"}, + {16, nullptr, "DeleteSaveDataBackupSetting"}, + {18, nullptr, "GetSaveDataBackupInfoCache"}, + {19, nullptr, "UpdateSaveDataBackupInfoCacheAsync"}, + {22, nullptr, "DeleteSaveDataBackupAsync"}, + {25, nullptr, "ListDownloadableSaveDataBackupInfoAsync"}, + {26, nullptr, "DownloadSaveDataBackupAsync"}, + {9010, nullptr, "VerifySaveDataBackupLicenseAsyncForDebug"}, + {9013, nullptr, "GetSaveDataBackupSettingForDebug"}, + {9014, nullptr, "SetSaveDataBackupSettingEnabledForDebug"}, + {9015, nullptr, "SetCustomDataForDebug"}, + {9016, nullptr, "DeleteSaveDataBackupSettingForDebug"}, + {9018, nullptr, "GetSaveDataBackupInfoCacheForDebug"}, + {9019, nullptr, "UpdateSaveDataBackupInfoCacheAsyncForDebug"}, + {9022, nullptr, "DeleteSaveDataBackupAsyncForDebug"}, + {9025, nullptr, "ListDownloadableSaveDataBackupInfoAsyncForDebug"}, + {9026, nullptr, "DownloadSaveDataBackupAsyncForDebug"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + initialized = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetSaveDataBackupSetting(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + // backup_setting is set to 0 since real value is unknown + constexpr u64 backup_setting = 0; + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(backup_setting); + } + + void SetSaveDataBackupSettingEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + bool initialized{}; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<OLSC>(system)->InstallAsService(service_manager); +} + +} // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h new file mode 100644 index 000000000..24f24ca6b --- /dev/null +++ b/src/core/hle/service/olsc/olsc.h @@ -0,0 +1,20 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::OLSC { + +/// Registers all SSL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); + +} // namespace Service::OLSC diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp index c568a0adc..f6686fc4d 100644 --- a/src/core/hle/service/pcie/pcie.cpp +++ b/src/core/hle/service/pcie/pcie.cpp @@ -12,7 +12,7 @@ namespace Service::PCIe { class ISession final : public ServiceFramework<ISession> { public: - explicit ISession() : ServiceFramework{"ISession"} { + explicit ISession(Core::System& system_) : ServiceFramework{system_, "ISession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "QueryFunctions"}, @@ -48,7 +48,7 @@ public: class PCIe final : public ServiceFramework<PCIe> { public: - explicit PCIe() : ServiceFramework{"pcie"} { + explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RegisterClassDriver"}, @@ -60,8 +60,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<PCIe>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<PCIe>(system)->InstallAsService(sm); } } // namespace Service::PCIe diff --git a/src/core/hle/service/pcie/pcie.h b/src/core/hle/service/pcie/pcie.h index 59c22ca45..e5709a72f 100644 --- a/src/core/hle/service/pcie/pcie.h +++ b/src/core/hle/service/pcie/pcie.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PCIe { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PCIe diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index caf14ed61..f9089bf2f 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -11,7 +11,8 @@ namespace Service::PCTL { class IParentalControlService final : public ServiceFramework<IParentalControlService> { public: - IParentalControlService() : ServiceFramework("IParentalControlService") { + explicit IParentalControlService(Core::System& system_) + : ServiceFramework{system_, "IParentalControlService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, @@ -49,11 +50,11 @@ public: {1046, nullptr, "DisableFeaturesForReset"}, {1047, nullptr, "NotifyApplicationDownloadStarted"}, {1048, nullptr, "NotifyNetworkProfileCreated"}, - {1061, nullptr, "ConfirmStereoVisionRestrictionConfigurable"}, - {1062, nullptr, "GetStereoVisionRestriction"}, - {1063, nullptr, "SetStereoVisionRestriction"}, - {1064, nullptr, "ResetConfirmedStereoVisionPermission"}, - {1065, nullptr, "IsStereoVisionPermitted"}, + {1061, &IParentalControlService::ConfirmStereoVisionRestrictionConfigurable, "ConfirmStereoVisionRestrictionConfigurable"}, + {1062, &IParentalControlService::GetStereoVisionRestriction, "GetStereoVisionRestriction"}, + {1063, &IParentalControlService::SetStereoVisionRestriction, "SetStereoVisionRestriction"}, + {1064, &IParentalControlService::ResetConfirmedStereoVisionPermission, "ResetConfirmedStereoVisionPermission"}, + {1065, &IParentalControlService::IsStereoVisionPermitted, "IsStereoVisionPermitted"}, {1201, nullptr, "UnlockRestrictionTemporarily"}, {1202, nullptr, "UnlockSystemSettingsRestriction"}, {1203, nullptr, "SetPinCode"}, @@ -113,6 +114,7 @@ public: {2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"}, {2016, nullptr, "RequestUpdateExemptionListAsync"}, }; + // clang-format on RegisterHandlers(functions); } @@ -130,6 +132,49 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(true); + } + + void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto can_use = rp.Pop<bool>(); + LOG_WARNING(Service_PCTL, "(STUBBED) called, can_use={}", can_use); + + can_use_stereo_vision = can_use; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(can_use_stereo_vision); + } + + void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + bool can_use_stereo_vision = true; }; void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { @@ -137,7 +182,7 @@ void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IParentalControlService>(); + rb.PushIpcInterface<IParentalControlService>(system); } void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { @@ -145,20 +190,21 @@ void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IParentalControlService>(); + rb.PushIpcInterface<IParentalControlService>(system); } -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<PCTL>(module, "pctl")->InstallAsService(service_manager); - std::make_shared<PCTL>(module, "pctl:a")->InstallAsService(service_manager); - std::make_shared<PCTL>(module, "pctl:r")->InstallAsService(service_manager); - std::make_shared<PCTL>(module, "pctl:s")->InstallAsService(service_manager); + std::make_shared<PCTL>(system, module, "pctl")->InstallAsService(service_manager); + std::make_shared<PCTL>(system, module, "pctl:a")->InstallAsService(service_manager); + std::make_shared<PCTL>(system, module, "pctl:r")->InstallAsService(service_manager); + std::make_shared<PCTL>(system, module, "pctl:s")->InstallAsService(service_manager); } } // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h index 3e449110d..4c7e09a3b 100644 --- a/src/core/hle/service/pctl/module.h +++ b/src/core/hle/service/pctl/module.h @@ -6,13 +6,18 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::PCTL { class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(Core::System& system_, std::shared_ptr<Module> module_, + const char* name); ~Interface() override; void CreateService(Kernel::HLERequestContext& ctx); @@ -24,6 +29,6 @@ public: }; /// Registers all PCTL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp index af9d1433a..16dd34f90 100644 --- a/src/core/hle/service/pctl/pctl.cpp +++ b/src/core/hle/service/pctl/pctl.cpp @@ -6,8 +6,8 @@ namespace Service::PCTL { -PCTL::PCTL(std::shared_ptr<Module> module, const char* name) - : Module::Interface(std::move(module), name) { +PCTL::PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name) + : Interface{system_, std::move(module_), name} { static const FunctionInfo functions[] = { {0, &PCTL::CreateService, "CreateService"}, {1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"}, diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index c33ea80b6..275d23007 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -6,11 +6,15 @@ #include "core/hle/service/pctl/module.h" +namespace Core { +class System; +} + namespace Service::PCTL { class PCTL final : public Module::Interface { public: - explicit PCTL(std::shared_ptr<Module> module, const char* name); + explicit PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name); ~PCTL() override; }; diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp index 8bfc0276e..68b2c4178 100644 --- a/src/core/hle/service/pcv/pcv.cpp +++ b/src/core/hle/service/pcv/pcv.cpp @@ -12,7 +12,7 @@ namespace Service::PCV { class PCV final : public ServiceFramework<PCV> { public: - explicit PCV() : ServiceFramework{"pcv"} { + explicit PCV(Core::System& system_) : ServiceFramework{system_, "pcv"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetPowerEnabled"}, @@ -54,7 +54,7 @@ public: class PCV_ARB final : public ServiceFramework<PCV_ARB> { public: - explicit PCV_ARB() : ServiceFramework{"pcv:arb"} { + explicit PCV_ARB(Core::System& system_) : ServiceFramework{system_, "pcv:arb"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ReleaseControl"}, @@ -67,7 +67,7 @@ public: class PCV_IMM final : public ServiceFramework<PCV_IMM> { public: - explicit PCV_IMM() : ServiceFramework{"pcv:imm"} { + explicit PCV_IMM(Core::System& system_) : ServiceFramework{system_, "pcv:imm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetClockRate"}, @@ -78,10 +78,10 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<PCV>()->InstallAsService(sm); - std::make_shared<PCV_ARB>()->InstallAsService(sm); - std::make_shared<PCV_IMM>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<PCV>(system)->InstallAsService(sm); + std::make_shared<PCV_ARB>(system)->InstallAsService(sm); + std::make_shared<PCV_IMM>(system)->InstallAsService(sm); } } // namespace Service::PCV diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h index 219a893c3..c61a0b591 100644 --- a/src/core/hle/service/pcv/pcv.h +++ b/src/core/hle/service/pcv/pcv.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PCV { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PCV diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index f43122ad2..68736c40c 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -43,7 +44,7 @@ void GetApplicationPidGeneric(Kernel::HLERequestContext& ctx, class BootMode final : public ServiceFramework<BootMode> { public: - explicit BootMode() : ServiceFramework{"pm:bm"} { + explicit BootMode(Core::System& system_) : ServiceFramework{system_, "pm:bm"} { static const FunctionInfo functions[] = { {0, &BootMode::GetBootMode, "GetBootMode"}, {1, &BootMode::SetMaintenanceBoot, "SetMaintenanceBoot"}, @@ -74,8 +75,8 @@ private: class DebugMonitor final : public ServiceFramework<DebugMonitor> { public: - explicit DebugMonitor(const Kernel::KernelCore& kernel) - : ServiceFramework{"pm:dmnt"}, kernel(kernel) { + explicit DebugMonitor(Core::System& system_) + : ServiceFramework{system_, "pm:dmnt"}, kernel{system_.Kernel()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetJitDebugProcessIdList"}, @@ -124,8 +125,9 @@ private: class Info final : public ServiceFramework<Info> { public: - explicit Info(const std::vector<std::shared_ptr<Kernel::Process>>& process_list) - : ServiceFramework{"pm:info"}, process_list(process_list) { + explicit Info(Core::System& system_, + const std::vector<std::shared_ptr<Kernel::Process>>& process_list_) + : ServiceFramework{system_, "pm:info"}, process_list{process_list_} { static const FunctionInfo functions[] = { {0, &Info::GetTitleId, "GetTitleId"}, }; @@ -159,8 +161,8 @@ private: class Shell final : public ServiceFramework<Shell> { public: - explicit Shell(const Kernel::KernelCore& kernel) - : ServiceFramework{"pm:shell"}, kernel(kernel) { + explicit Shell(Core::System& system_) + : ServiceFramework{system_, "pm:shell"}, kernel{system_.Kernel()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LaunchProgram"}, @@ -189,11 +191,11 @@ private: }; void InstallInterfaces(Core::System& system) { - std::make_shared<BootMode>()->InstallAsService(system.ServiceManager()); - std::make_shared<DebugMonitor>(system.Kernel())->InstallAsService(system.ServiceManager()); - std::make_shared<Info>(system.Kernel().GetProcessList()) + std::make_shared<BootMode>(system)->InstallAsService(system.ServiceManager()); + std::make_shared<DebugMonitor>(system)->InstallAsService(system.ServiceManager()); + std::make_shared<Info>(system, system.Kernel().GetProcessList()) ->InstallAsService(system.ServiceManager()); - std::make_shared<Shell>(system.Kernel())->InstallAsService(system.ServiceManager()); + std::make_shared<Shell>(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::PM diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index cde3312da..d5b3b17a5 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -4,6 +4,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" #include "core/hle/service/acc/profile_manager.h" @@ -15,19 +16,18 @@ namespace Service::PlayReport { class PlayReport final : public ServiceFramework<PlayReport> { public: - explicit PlayReport(const char* name, Core::System& system) - : ServiceFramework{name}, system(system) { + explicit PlayReport(const char* name, Core::System& system_) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"}, {10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"}, {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"}, {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"}, - {10104, nullptr, "SaveReport"}, - {10105, nullptr, "SaveReportWithUser"}, - {10200, nullptr, "RequestImmediateTransmission"}, - {10300, nullptr, "GetTransmissionStatus"}, - {10400, nullptr, "GetSystemSessionId"}, + {10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"}, + {10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"}, + {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"}, + {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"}, + {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"}, {20100, &PlayReport::SaveSystemReport, "SaveSystemReport"}, {20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"}, {20200, nullptr, "SetOperationMode"}, @@ -59,16 +59,22 @@ private: IPC::RequestParser rp{ctx}; const auto process_id = rp.PopRaw<u64>(); - std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)}; - if constexpr (Type == Core::Reporter::PlayReportType::Old2) { - data.emplace_back(ctx.ReadBuffer(1)); - } + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = [ctx] { + if (ctx.CanReadBuffer(1)) { + return ctx.ReadBuffer(1); + } - LOG_DEBUG(Service_PREPO, "called, type={:02X}, process_id={:016X}, data1_size={:016X}", - static_cast<u8>(Type), process_id, data[0].size()); + return std::vector<u8>{}; + }(); + + LOG_DEBUG(Service_PREPO, + "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}", + Type, process_id, data1.size(), data2.size()); const auto& reporter{system.GetReporter()}; - reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id); + reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, + process_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -79,35 +85,67 @@ private: IPC::RequestParser rp{ctx}; const auto user_id = rp.PopRaw<u128>(); const auto process_id = rp.PopRaw<u64>(); - std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)}; - if constexpr (Type == Core::Reporter::PlayReportType::Old2) { - const auto read_buffer_count = - ctx.BufferDescriptorX().size() + ctx.BufferDescriptorA().size(); - if (read_buffer_count > 1) { - data.emplace_back(ctx.ReadBuffer(1)); + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = [ctx] { + if (ctx.CanReadBuffer(1)) { + return ctx.ReadBuffer(1); } - } - LOG_DEBUG( - Service_PREPO, - "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}", - static_cast<u8>(Type), user_id[1], user_id[0], process_id, data[0].size()); + return std::vector<u8>{}; + }(); + + LOG_DEBUG(Service_PREPO, + "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, " + "data1_size={:016X}, data2_size={:016X}", + Type, user_id[1], user_id[0], process_id, data1.size(), data2.size()); const auto& reporter{system.GetReporter()}; - reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id, - user_id); + reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, + process_id, user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + void RequestImmediateTransmission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PREPO, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetTransmissionStatus(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PREPO, "(STUBBED) called"); + + constexpr s32 status = 0; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(status); + } + + void GetSystemSessionId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PREPO, "(STUBBED) called"); + + constexpr u64 system_session_id = 0; + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(system_session_id); + } + void SaveSystemReport(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto title_id = rp.PopRaw<u64>(); const auto data1 = ctx.ReadBuffer(0); - const auto data2 = ctx.ReadBuffer(1); + const auto data2 = [ctx] { + if (ctx.CanReadBuffer(1)) { + return ctx.ReadBuffer(1); + } + + return std::vector<u8>{}; + }(); LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", title_id, data1.size(), data2.size()); @@ -125,7 +163,13 @@ private: const auto title_id = rp.PopRaw<u64>(); const auto data1 = ctx.ReadBuffer(0); - const auto data2 = ctx.ReadBuffer(1); + const auto data2 = [ctx] { + if (ctx.CanReadBuffer(1)) { + return ctx.ReadBuffer(1); + } + + return std::vector<u8>{}; + }(); LOG_DEBUG(Service_PREPO, "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, " @@ -139,8 +183,6 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index a5682ee26..395b57ead 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::PlayReport { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp index 99e1c9042..5a52b2b05 100644 --- a/src/core/hle/service/psc/psc.cpp +++ b/src/core/hle/service/psc/psc.cpp @@ -14,7 +14,7 @@ namespace Service::PSC { class PSC_C final : public ServiceFramework<PSC_C> { public: - explicit PSC_C() : ServiceFramework{"psc:c"} { + explicit PSC_C(Core::System& system_) : ServiceFramework{system_, "psc:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -35,7 +35,7 @@ public: class IPmModule final : public ServiceFramework<IPmModule> { public: - explicit IPmModule() : ServiceFramework{"IPmModule"} { + explicit IPmModule(Core::System& system_) : ServiceFramework{system_, "IPmModule"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -52,7 +52,7 @@ public: class PSC_M final : public ServiceFramework<PSC_M> { public: - explicit PSC_M() : ServiceFramework{"psc:m"} { + explicit PSC_M(Core::System& system_) : ServiceFramework{system_, "psc:m"} { // clang-format off static const FunctionInfo functions[] = { {0, &PSC_M::GetPmModule, "GetPmModule"}, @@ -68,13 +68,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IPmModule>(); + rb.PushIpcInterface<IPmModule>(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<PSC_C>()->InstallAsService(sm); - std::make_shared<PSC_M>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<PSC_C>(system)->InstallAsService(sm); + std::make_shared<PSC_M>(system)->InstallAsService(sm); } } // namespace Service::PSC diff --git a/src/core/hle/service/psc/psc.h b/src/core/hle/service/psc/psc.h index 5052eb02c..89344f32d 100644 --- a/src/core/hle/service/psc/psc.h +++ b/src/core/hle/service/psc/psc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PSC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PSC diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp index 6d9e6bd09..26ed52273 100644 --- a/src/core/hle/service/ptm/psm.cpp +++ b/src/core/hle/service/ptm/psm.cpp @@ -5,16 +5,121 @@ #include <memory> #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/service/ptm/psm.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" namespace Service::PSM { +class IPsmSession final : public ServiceFramework<IPsmSession> { +public: + explicit IPsmSession(Core::System& system_) : ServiceFramework{system_, "IPsmSession"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IPsmSession::BindStateChangeEvent, "BindStateChangeEvent"}, + {1, &IPsmSession::UnbindStateChangeEvent, "UnbindStateChangeEvent"}, + {2, &IPsmSession::SetChargerTypeChangeEventEnabled, "SetChargerTypeChangeEventEnabled"}, + {3, &IPsmSession::SetPowerSupplyChangeEventEnabled, "SetPowerSupplyChangeEventEnabled"}, + {4, &IPsmSession::SetBatteryVoltageStateChangeEventEnabled, "SetBatteryVoltageStateChangeEventEnabled"}, + }; + // clang-format on + + RegisterHandlers(functions); + + state_change_event = + Kernel::KEvent::Create(system_.Kernel(), "IPsmSession::state_change_event"); + state_change_event->Initialize(); + } + + ~IPsmSession() override = default; + + void SignalChargerTypeChanged() { + if (should_signal && should_signal_charger_type) { + state_change_event->GetWritableEvent()->Signal(); + } + } + + void SignalPowerSupplyChanged() { + if (should_signal && should_signal_power_supply) { + state_change_event->GetWritableEvent()->Signal(); + } + } + + void SignalBatteryVoltageStateChanged() { + if (should_signal && should_signal_battery_voltage) { + state_change_event->GetWritableEvent()->Signal(); + } + } + +private: + void BindStateChangeEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PSM, "called"); + + should_signal = true; + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(state_change_event->GetReadableEvent()); + } + + void UnbindStateChangeEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PSM, "called"); + + should_signal = false; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetChargerTypeChangeEventEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto state = rp.Pop<bool>(); + LOG_DEBUG(Service_PSM, "called, state={}", state); + + should_signal_charger_type = state; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetPowerSupplyChangeEventEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto state = rp.Pop<bool>(); + LOG_DEBUG(Service_PSM, "called, state={}", state); + + should_signal_power_supply = state; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetBatteryVoltageStateChangeEventEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto state = rp.Pop<bool>(); + LOG_DEBUG(Service_PSM, "called, state={}", state); + + should_signal_battery_voltage = state; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + bool should_signal_charger_type{}; + bool should_signal_power_supply{}; + bool should_signal_battery_voltage{}; + bool should_signal{}; + std::shared_ptr<Kernel::KEvent> state_change_event; +}; + class PSM final : public ServiceFramework<PSM> { public: - explicit PSM() : ServiceFramework{"psm"} { + explicit PSM(Core::System& system_) : ServiceFramework{system_, "psm"} { // clang-format off static const FunctionInfo functions[] = { {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"}, @@ -24,7 +129,7 @@ public: {4, nullptr, "IsBatteryChargingEnabled"}, {5, nullptr, "AcquireControllerPowerSupply"}, {6, nullptr, "ReleaseControllerPowerSupply"}, - {7, nullptr, "OpenSession"}, + {7, &PSM::OpenSession, "OpenSession"}, {8, nullptr, "EnableEnoughPowerChargeEmulation"}, {9, nullptr, "DisableEnoughPowerChargeEmulation"}, {10, nullptr, "EnableFastBatteryCharging"}, @@ -61,6 +166,14 @@ private: rb.PushEnum(charger_type); } + void OpenSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PSM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IPsmSession>(system); + } + enum class ChargerType : u32 { Unplugged = 0, RegularCharger = 1, @@ -72,8 +185,8 @@ private: ChargerType charger_type{ChargerType::RegularCharger}; }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<PSM>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<PSM>(system)->InstallAsService(sm); } } // namespace Service::PSM diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h index a286793ae..2930ce26a 100644 --- a/src/core/hle/service/ptm/psm.h +++ b/src/core/hle/service/ptm/psm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PSM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PSM diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index fa5347af9..1da56bc27 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -11,10 +11,10 @@ #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" -#include "core/hle/kernel/thread.h" #include "core/hle/service/acc/acc.h" #include "core/hle/service/am/am.h" #include "core/hle/service/aoc/aoc_u.h" @@ -51,6 +51,7 @@ #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/olsc/olsc.h" #include "core/hle/service/pcie/pcie.h" #include "core/hle/service/pctl/module.h" #include "core/hle/service/pcv/pcv.h" @@ -89,15 +90,19 @@ namespace Service { return function_string; } -//////////////////////////////////////////////////////////////////////////////////////////////////// +ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, + u32 max_sessions_, InvokerFn* handler_invoker_) + : system{system_}, service_name{service_name_}, max_sessions{max_sessions_}, + handler_invoker{handler_invoker_} {} -ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, - InvokerFn* handler_invoker) - : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} - -ServiceFrameworkBase::~ServiceFrameworkBase() = default; +ServiceFrameworkBase::~ServiceFrameworkBase() { + // Wait for other threads to release access before destroying + const auto guard = LockService(); +} void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { + const auto guard = LockService(); + ASSERT(!port_installed); auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); @@ -105,10 +110,11 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) port_installed = true; } -void ServiceFrameworkBase::InstallAsNamedPort() { +void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) { + const auto guard = LockService(); + ASSERT(!port_installed); - auto& kernel = Core::System::GetInstance().Kernel(); auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); server_port->SetHleHandler(shared_from_this()); @@ -116,18 +122,6 @@ void ServiceFrameworkBase::InstallAsNamedPort() { port_installed = true; } -std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { - ASSERT(!port_installed); - - auto& kernel = Core::System::GetInstance().Kernel(); - auto [server_port, client_port] = - Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); - auto port = MakeResult(std::move(server_port)).Unwrap(); - port->SetHleHandler(shared_from_this()); - port_installed = true; - return client_port; -} - void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) { handlers.reserve(handlers.size() + n); for (std::size_t i = 0; i < n; ++i) { @@ -149,8 +143,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext } buf.push_back('}'); - Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport( - ctx, ctx.GetCommand(), function_name, service_name); + system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, + service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); } @@ -166,6 +160,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { } ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) { + const auto guard = LockService(); + switch (context.GetCommandType()) { case IPC::CommandType::Close: { IPC::ResponseBuilder rb{context, 2}; @@ -174,7 +170,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co } case IPC::CommandType::ControlWithContext: case IPC::CommandType::Control: { - Core::System::GetInstance().ServiceManager().InvokeControlRequest(context); + system.ServiceManager().InvokeControlRequest(context); break; } case IPC::CommandType::RequestWithContext: @@ -183,82 +179,82 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co break; } default: - UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType())); + UNIMPLEMENTED_MSG("command_type={}", context.GetCommandType()); } - context.WriteToOutgoingCommandBuffer(context.GetThread()); + // If emulation was shutdown, we are closing service threads, do not write the response back to + // memory that may be shutting down as well. + if (system.IsPoweredOn()) { + context.WriteToOutgoingCommandBuffer(context.GetThread()); + } return RESULT_SUCCESS; } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Module interface +/// Initialize Services +Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) + : nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system)} { -/// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. - auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false); - SM::ServiceManager::InstallInterfaces(sm, system.Kernel()); + SM::ServiceManager::InstallInterfaces(sm, system); Account::InstallInterfaces(system); - AM::InstallInterfaces(*sm, nv_flinger, system); + AM::InstallInterfaces(*sm, *nv_flinger, system); AOC::InstallInterfaces(*sm, system); APM::InstallInterfaces(system); Audio::InstallInterfaces(*sm, system); BCAT::InstallInterfaces(system); - BPC::InstallInterfaces(*sm); + BPC::InstallInterfaces(*sm, system); BtDrv::InstallInterfaces(*sm, system); BTM::InstallInterfaces(*sm, system); - Capture::InstallInterfaces(*sm); - ERPT::InstallInterfaces(*sm); - ES::InstallInterfaces(*sm); - EUPLD::InstallInterfaces(*sm); + Capture::InstallInterfaces(*sm, system); + ERPT::InstallInterfaces(*sm, system); + ES::InstallInterfaces(*sm, system); + EUPLD::InstallInterfaces(*sm, system); Fatal::InstallInterfaces(*sm, system); - FGM::InstallInterfaces(*sm); + FGM::InstallInterfaces(*sm, system); FileSystem::InstallInterfaces(system); Friend::InstallInterfaces(*sm, system); Glue::InstallInterfaces(system); - GRC::InstallInterfaces(*sm); + GRC::InstallInterfaces(*sm, system); HID::InstallInterfaces(*sm, system); - LBL::InstallInterfaces(*sm); - LDN::InstallInterfaces(*sm); + LBL::InstallInterfaces(*sm, system); + LDN::InstallInterfaces(*sm, system); LDR::InstallInterfaces(*sm, system); LM::InstallInterfaces(system); - Migration::InstallInterfaces(*sm); - Mii::InstallInterfaces(*sm); - MM::InstallInterfaces(*sm); - NCM::InstallInterfaces(*sm); - NFC::InstallInterfaces(*sm); + Migration::InstallInterfaces(*sm, system); + Mii::InstallInterfaces(*sm, system); + MM::InstallInterfaces(*sm, system); + NCM::InstallInterfaces(*sm, system); + NFC::InstallInterfaces(*sm, system); NFP::InstallInterfaces(*sm, system); NIFM::InstallInterfaces(*sm, system); NIM::InstallInterfaces(*sm, system); - NPNS::InstallInterfaces(*sm); + NPNS::InstallInterfaces(*sm, system); NS::InstallInterfaces(*sm, system); Nvidia::InstallInterfaces(*sm, *nv_flinger, system); - PCIe::InstallInterfaces(*sm); - PCTL::InstallInterfaces(*sm); - PCV::InstallInterfaces(*sm); + OLSC::InstallInterfaces(*sm, system); + PCIe::InstallInterfaces(*sm, system); + PCTL::InstallInterfaces(*sm, system); + PCV::InstallInterfaces(*sm, system); PlayReport::InstallInterfaces(*sm, system); PM::InstallInterfaces(system); - PSC::InstallInterfaces(*sm); - PSM::InstallInterfaces(*sm); - Set::InstallInterfaces(*sm); - Sockets::InstallInterfaces(*sm); - SPL::InstallInterfaces(*sm); - SSL::InstallInterfaces(*sm); + PSC::InstallInterfaces(*sm, system); + PSM::InstallInterfaces(*sm, system); + Set::InstallInterfaces(*sm, system); + Sockets::InstallInterfaces(*sm, system); + SPL::InstallInterfaces(*sm, system); + SSL::InstallInterfaces(*sm, system); Time::InstallInterfaces(system); - USB::InstallInterfaces(*sm); - VI::InstallInterfaces(*sm, nv_flinger); - WLAN::InstallInterfaces(*sm); - - LOG_DEBUG(Service, "initialized OK"); + USB::InstallInterfaces(*sm, system); + VI::InstallInterfaces(*sm, system, *nv_flinger); + WLAN::InstallInterfaces(*sm, system); } -/// Shutdown ServiceManager -void Shutdown() { - LOG_DEBUG(Service, "shutdown OK"); -} +Services::~Services() = default; + } // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 022d885b6..916445517 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -5,9 +5,11 @@ #pragma once #include <cstddef> +#include <mutex> #include <string> #include <boost/container/flat_map.hpp> #include "common/common_types.h" +#include "common/spin_lock.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/object.h" @@ -29,7 +31,11 @@ namespace Service { namespace FileSystem { class FileSystemController; -} // namespace FileSystem +} + +namespace NVFlinger { +class NVFlinger; +} namespace SM { class ServiceManager; @@ -63,12 +69,10 @@ public: /// Creates a port pair and registers this service with the given ServiceManager. void InstallAsService(SM::ServiceManager& service_manager); /// Creates a port pair and registers it on the kernel's global port registry. - void InstallAsNamedPort(); - /// Creates and returns an unregistered port for the service. - std::shared_ptr<Kernel::ClientPort> CreatePort(); - + void InstallAsNamedPort(Kernel::KernelCore& kernel); + /// Invokes a service request routine. void InvokeRequest(Kernel::HLERequestContext& ctx); - + /// Handles a synchronization request for the service. ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) override; protected: @@ -76,6 +80,14 @@ protected: template <typename Self> using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); + /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. + [[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() { + return std::scoped_lock{lock_service}; + } + + /// System context that the service operates under. + Core::System& system; + private: template <typename T> friend class ServiceFramework; @@ -89,7 +101,8 @@ private: using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member, Kernel::HLERequestContext& ctx); - ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker); + explicit ServiceFrameworkBase(Core::System& system_, const char* service_name_, + u32 max_sessions_, InvokerFn* handler_invoker_); ~ServiceFrameworkBase() override; void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n); @@ -107,6 +120,9 @@ private: /// Function used to safely up-cast pointers to the derived class before invoking a handler. InvokerFn* handler_invoker; boost::container::flat_map<u32, FunctionInfoBase> handlers; + + /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. + Common::SpinLock lock_service; }; /** @@ -147,11 +163,15 @@ protected: /** * Initializes the handler with no functions installed. - * @param max_sessions Maximum number of sessions that can be - * connected to this service at the same time. + * + * @param system_ The system context to construct this service under. + * @param service_name_ Name of the service. + * @param max_sessions_ Maximum number of sessions that can be + * connected to this service at the same time. */ - explicit ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions) - : ServiceFrameworkBase(service_name, max_sessions, Invoker) {} + explicit ServiceFramework(Core::System& system_, const char* service_name_, + u32 max_sessions_ = DefaultMaxSessions) + : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} /// Registers handlers in the service. template <std::size_t N> @@ -181,10 +201,17 @@ private: } }; -/// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system); +/** + * The purpose of this class is to own any objects that need to be shared across the other service + * implementations. Will be torn down when the global system instance is shutdown. + */ +class Services final { +public: + explicit Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system); + ~Services(); -/// Shutdown ServiceManager -void Shutdown(); +private: + std::unique_ptr<NVFlinger::NVFlinger> nv_flinger; +}; } // namespace Service diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index e64777668..d953b4303 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -188,7 +188,7 @@ void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) { GetKeyCodeMapImpl(ctx); } -SET::SET() : ServiceFramework("set") { +SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} { // clang-format off static const FunctionInfo functions[] = { {0, &SET::GetLanguageCode, "GetLanguageCode"}, @@ -202,6 +202,7 @@ SET::SET() : ServiceFramework("set") { {8, &SET::GetQuestFlag, "GetQuestFlag"}, {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"}, {10, nullptr, "GetFirmwareVersionForDebug"}, + {11, nullptr, "GetDeviceNickName"}, }; // clang-format on diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 8ac9c169d..d5bd7828d 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { /// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. @@ -32,7 +36,7 @@ LanguageCode GetLanguageCodeFromIndex(std::size_t idx); class SET final : public ServiceFramework<SET> { public: - explicit SET(); + explicit SET(Core::System& system_); ~SET() override; private: diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp index 3fbfecc9e..b2aa7bc0c 100644 --- a/src/core/hle/service/set/set_cal.cpp +++ b/src/core/hle/service/set/set_cal.cpp @@ -6,7 +6,7 @@ namespace Service::Set { -SET_CAL::SET_CAL() : ServiceFramework("set:cal") { +SET_CAL::SET_CAL(Core::System& system_) : ServiceFramework{system_, "set:cal"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetBluetoothBdAddress"}, diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h index a0677e815..a29fc3ddd 100644 --- a/src/core/hle/service/set/set_cal.h +++ b/src/core/hle/service/set/set_cal.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_CAL final : public ServiceFramework<SET_CAL> { public: - explicit SET_CAL(); + explicit SET_CAL(Core::System& system_); ~SET_CAL() override; }; diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp index 565882a31..f04dc5047 100644 --- a/src/core/hle/service/set/set_fd.cpp +++ b/src/core/hle/service/set/set_fd.cpp @@ -6,7 +6,7 @@ namespace Service::Set { -SET_FD::SET_FD() : ServiceFramework("set:fd") { +SET_FD::SET_FD(Core::System& system_) : ServiceFramework{system_, "set:fd"} { // clang-format off static const FunctionInfo functions[] = { {2, nullptr, "SetSettingsItemValue"}, diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h index 216e65f1f..c28cb301e 100644 --- a/src/core/hle/service/set/set_fd.h +++ b/src/core/hle/service/set/set_fd.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_FD final : public ServiceFramework<SET_FD> { public: - explicit SET_FD(); + explicit SET_FD(Core::System& system_); ~SET_FD() override; }; diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index 8bd4c7e79..b58b2c8c5 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -34,9 +34,9 @@ void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionTy // consistence (currently reports as 5.1.0-0.0) const auto archive = FileSys::SystemArchive::SystemVersion(); - const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) { + const auto early_exit_failure = [&ctx](std::string_view desc, ResultCode code) { LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", - desc.c_str()); + desc); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(code); }; @@ -103,7 +103,7 @@ void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -SET_SYS::SET_SYS() : ServiceFramework("set:sys") { +SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetLanguageCode"}, @@ -300,6 +300,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") { {198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"}, {199, nullptr, "GetButtonConfigRegisteredSettings"}, {200, nullptr, "SetButtonConfigRegisteredSettings"}, + {201, nullptr, "GetFieldTestingFlag"}, + {202, nullptr, "SetFieldTestingFlag"}, }; // clang-format on diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h index 13ee2cf46..edb185a68 100644 --- a/src/core/hle/service/set/set_sys.h +++ b/src/core/hle/service/set/set_sys.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_SYS final : public ServiceFramework<SET_SYS> { public: - explicit SET_SYS(); + explicit SET_SYS(Core::System& system_); ~SET_SYS() override; private: diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp index cf5541ca8..212ebc427 100644 --- a/src/core/hle/service/set/settings.cpp +++ b/src/core/hle/service/set/settings.cpp @@ -7,14 +7,15 @@ #include "core/hle/service/set/set_fd.h" #include "core/hle/service/set/set_sys.h" #include "core/hle/service/set/settings.h" +#include "core/hle/service/sm/sm.h" namespace Service::Set { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<SET>()->InstallAsService(service_manager); - std::make_shared<SET_CAL>()->InstallAsService(service_manager); - std::make_shared<SET_FD>()->InstallAsService(service_manager); - std::make_shared<SET_SYS>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<SET>(system)->InstallAsService(service_manager); + std::make_shared<SET_CAL>(system)->InstallAsService(service_manager); + std::make_shared<SET_FD>(system)->InstallAsService(service_manager); + std::make_shared<SET_SYS>(system)->InstallAsService(service_manager); } } // namespace Service::Set diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h index 6606ce776..7a6950dd0 100644 --- a/src/core/hle/service/set/settings.h +++ b/src/core/hle/service/set/settings.h @@ -4,11 +4,17 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} namespace Service::Set { /// Registers all Settings services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Set diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 972aaa6d9..916177efd 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -48,7 +48,7 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { } // https://switchbrew.org/wiki/IPC_Marshalling -Controller::Controller() : ServiceFramework("IpcController") { +Controller::Controller(Core::System& system_) : ServiceFramework{system_, "IpcController"} { static const FunctionInfo functions[] = { {0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"}, {1, nullptr, "CopyFromCurrentDomain"}, diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h index 180c6da50..7494f898d 100644 --- a/src/core/hle/service/sm/controller.h +++ b/src/core/hle/service/sm/controller.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SM { class Controller final : public ServiceFramework<Controller> { public: - Controller(); + explicit Controller(Core::System& system_); ~Controller() override; private: diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index d872de16c..2b91a89d1 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -19,7 +19,7 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4); constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); -ServiceManager::ServiceManager() = default; +ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {} ServiceManager::~ServiceManager() = default; void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { @@ -27,29 +27,28 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { } static ResultCode ValidateServiceName(const std::string& name) { - if (name.size() <= 0 || name.size() > 8) { + if (name.empty() || name.size() > 8) { LOG_ERROR(Service_SM, "Invalid service name! service={}", name); return ERR_INVALID_NAME; } - if (name.find('\0') != std::string::npos) { + if (name.rfind('\0') != std::string::npos) { LOG_ERROR(Service_SM, "A non null terminated service was passed"); return ERR_INVALID_NAME; } return RESULT_SUCCESS; } -void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self, - Kernel::KernelCore& kernel) { +void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self, Core::System& system) { ASSERT(self->sm_interface.expired()); - auto sm = std::make_shared<SM>(self, kernel); - sm->InstallAsNamedPort(); + auto sm = std::make_shared<SM>(self, system); + sm->InstallAsNamedPort(system.Kernel()); self->sm_interface = sm; - self->controller_interface = std::make_unique<Controller>(); + self->controller_interface = std::make_unique<Controller>(system); } -ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService( - std::string name, unsigned int max_sessions) { +ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(std::string name, + u32 max_sessions) { CASCADE_CODE(ValidateServiceName(name)); @@ -58,7 +57,6 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService( return ERR_ALREADY_REGISTERED; } - auto& kernel = Core::System::GetInstance().Kernel(); auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name); @@ -141,9 +139,6 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { server_port->AppendPendingSession(server); } - // Wake the threads waiting on the ServerPort - server_port->Signal(); - LOG_DEBUG(Service_SM, "called service={} -> session={}", name, client->GetObjectId()); IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(RESULT_SUCCESS); @@ -191,8 +186,9 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) { rb.Push(service_manager->UnregisterService(name)); } -SM::SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel) - : ServiceFramework{"sm:", 4}, service_manager{std::move(service_manager)}, kernel{kernel} { +SM::SM(std::shared_ptr<ServiceManager> service_manager_, Core::System& system_) + : ServiceFramework{system_, "sm:", 4}, + service_manager{std::move(service_manager_)}, kernel{system_.Kernel()} { static const FunctionInfo functions[] = { {0x00000000, &SM::Initialize, "Initialize"}, {0x00000001, &SM::GetService, "GetService"}, diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index aabf166b7..3f46ae44f 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -16,6 +16,10 @@ #include "core/hle/result.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class ClientPort; class ClientSession; @@ -31,7 +35,7 @@ class Controller; /// Interface to "sm:" service class SM final : public ServiceFramework<SM> { public: - explicit SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel); + explicit SM(std::shared_ptr<ServiceManager> service_manager_, Core::System& system_); ~SM() override; private: @@ -46,13 +50,13 @@ private: class ServiceManager { public: - static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel); + static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Core::System& system); - ServiceManager(); + explicit ServiceManager(Kernel::KernelCore& kernel_); ~ServiceManager(); ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name, - unsigned int max_sessions); + u32 max_sessions); ResultCode UnregisterService(const std::string& name); ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name); ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name); @@ -79,6 +83,9 @@ private: /// Map of registered services, retrieved using GetServicePort or ConnectToService. std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services; + + /// Kernel context + Kernel::KernelCore& kernel; }; } // namespace Service::SM diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 8d4952c0e..78e9cd708 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -2,18 +2,138 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <fmt/format.h> + +#include "common/microprofile.h" +#include "common/thread.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/network/network.h" +#include "core/network/sockets.h" namespace Service::Sockets { +namespace { + +bool IsConnectionBased(Type type) { + switch (type) { + case Type::STREAM: + return true; + case Type::DGRAM: + return false; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return false; + } +} + +} // Anonymous namespace + +void BSD::PollWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout); +} + +void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::AcceptWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer); +} + +void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); + rb.Push<u32>(static_cast<u32>(write_buffer.size())); +} + +void BSD::ConnectWork::Execute(BSD* bsd) { + bsd_errno = bsd->ConnectImpl(fd, addr); +} + +void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(bsd_errno); +} + +void BSD::RecvWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message); +} + +void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(message); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::RecvFromWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr); +} + +void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(message, 0); + if (!addr.empty()) { + ctx.WriteBuffer(addr, 1); + } + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); + rb.Push<u32>(static_cast<u32>(addr.size())); +} + +void BSD::SendWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message); +} + +void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::SendToWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr); +} + +void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); +} + void BSD::RegisterClient(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // bsd errno + rb.Push<s32>(0); // bsd errno } void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { @@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { void BSD::Socket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + const u32 domain = rp.Pop<u32>(); + const u32 type = rp.Pop<u32>(); + const u32 protocol = rp.Pop<u32>(); - u32 domain = rp.Pop<u32>(); - u32 type = rp.Pop<u32>(); - u32 protocol = rp.Pop<u32>(); - - LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol); + LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); - u32 fd = next_fd++; + const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type), + static_cast<Protocol>(protocol)); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(fd); - rb.Push<u32>(0); // bsd errno + rb.Push<s32>(fd); + rb.PushEnum(bsd_errno); } void BSD::Select(Kernel::HLERequestContext& ctx) { @@ -52,67 +171,659 @@ void BSD::Select(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } +void BSD::Poll(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 nfds = rp.Pop<s32>(); + const s32 timeout = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout); + + ExecuteWork(ctx, PollWork{ + .nfds = nfds, + .timeout = timeout, + .read_buffer = ctx.ReadBuffer(), + .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()), + }); +} + +void BSD::Accept(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + ExecuteWork(ctx, AcceptWork{ + .fd = fd, + .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()), + }); +} + void BSD::Bind(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer())); } void BSD::Connect(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); + + ExecuteWork(ctx, ConnectWork{ + .fd = fd, + .addr = ctx.ReadBuffer(), + }); +} + +void BSD::GetPeerName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + LOG_DEBUG(Service, "called. fd={}", fd); + + std::vector<u8> write_buffer(ctx.GetWriteBufferSize()); + const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer); + + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0); + rb.PushEnum(bsd_errno); + rb.Push<u32>(static_cast<u32>(write_buffer.size())); +} + +void BSD::GetSockName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + std::vector<u8> write_buffer(ctx.GetWriteBufferSize()); + const Errno bsd_errno = GetSockNameImpl(fd, write_buffer); + + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0); + rb.PushEnum(bsd_errno); + rb.Push<u32>(static_cast<u32>(write_buffer.size())); +} + +void BSD::GetSockOpt(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + const u32 level = rp.Pop<u32>(); + const auto optname = static_cast<OptName>(rp.Pop<u32>()); + + LOG_WARNING(Service, "(STUBBED) called. fd={} level={} optname=0x{:x}", fd, level, optname); + + std::vector<u8> optval(ctx.GetWriteBufferSize()); + + ctx.WriteBuffer(optval); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push<s32>(-1); + rb.PushEnum(Errno::NOTCONN); + rb.Push<u32>(static_cast<u32>(optval.size())); } void BSD::Listen(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + const s32 backlog = rp.Pop<s32>(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog); + BuildErrnoResponse(ctx, ListenImpl(fd, backlog)); +} + +void BSD::Fcntl(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + const s32 cmd = rp.Pop<s32>(); + const s32 arg = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg); + + const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg); + + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + rb.Push<s32>(ret); + rb.PushEnum(bsd_errno); } void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; - IPC::ResponseBuilder rb{ctx, 4}; + const s32 fd = rp.Pop<s32>(); + const u32 level = rp.Pop<u32>(); + const OptName optname = static_cast<OptName>(rp.Pop<u32>()); - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + const std::vector<u8> buffer = ctx.ReadBuffer(); + const u8* optval = buffer.empty() ? nullptr : buffer.data(); + size_t optlen = buffer.size(); + + std::array<u64, 2> values; + if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) { + std::memcpy(values.data(), buffer.data(), sizeof(values)); + optlen = sizeof(values); + optval = reinterpret_cast<const u8*>(values.data()); + } + + LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level, + static_cast<u32>(optname), optlen); + + BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval)); +} + +void BSD::Shutdown(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop<s32>(); + const s32 how = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. fd={} how={}", fd, how); + + BuildErrnoResponse(ctx, ShutdownImpl(fd, how)); +} + +void BSD::Recv(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop<s32>(); + const u32 flags = rp.Pop<u32>(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize()); + + ExecuteWork(ctx, RecvWork{ + .fd = fd, + .flags = flags, + .message = std::vector<u8>(ctx.GetWriteBufferSize()), + }); +} + +void BSD::RecvFrom(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop<s32>(); + const u32 flags = rp.Pop<u32>(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags, + ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); + + ExecuteWork(ctx, RecvFromWork{ + .fd = fd, + .flags = flags, + .message = std::vector<u8>(ctx.GetWriteBufferSize(0)), + .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)), + }); +} + +void BSD::Send(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop<s32>(); + const u32 flags = rp.Pop<u32>(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize()); + + ExecuteWork(ctx, SendWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(), + }); } void BSD::SendTo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + const u32 flags = rp.Pop<u32>(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags, + ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1)); + + ExecuteWork(ctx, SendToWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(0), + .addr = ctx.ReadBuffer(1), + }); +} - IPC::ResponseBuilder rb{ctx, 4}; +void BSD::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize()); + + ExecuteWork(ctx, SendWork{ + .fd = fd, + .flags = 0, + .message = ctx.ReadBuffer(), + }); } void BSD::Close(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + BuildErrnoResponse(ctx, CloseImpl(fd)); +} + +void BSD::EventFd(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 initval = rp.Pop<u64>(); + const u32 flags = rp.Pop<u32>(); + + LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags); + + BuildErrnoResponse(ctx, Errno::SUCCESS); +} + +template <typename Work> +void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, Work work) { + work.Execute(this); + work.Response(ctx); +} +std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { + if (type == Type::SEQPACKET) { + UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management"); + } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) { + UNIMPLEMENTED_MSG("SOCK_RAW errno management"); + } + + [[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0; + UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type"); + type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000); + + const s32 fd = FindFreeFileDescriptorHandle(); + if (fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return {-1, Errno::MFILE}; + } + + file_descriptors[fd] = FileDescriptor{}; + FileDescriptor& descriptor = *file_descriptors[fd]; + // ENONMEM might be thrown here + + LOG_INFO(Service, "New socket fd={}", fd); + + descriptor.socket = std::make_unique<Network::Socket>(); + descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); + descriptor.is_connection_based = IsConnectionBased(type); + + return {fd, Errno::SUCCESS}; +} + +std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer, + s32 nfds, s32 timeout) { + if (write_buffer.size() < nfds * sizeof(PollFD)) { + return {-1, Errno::INVAL}; + } + + if (nfds == 0) { + // When no entries are provided, -1 is returned with errno zero + return {-1, Errno::SUCCESS}; + } + + const size_t length = std::min(read_buffer.size(), write_buffer.size()); + std::vector<PollFD> fds(nfds); + std::memcpy(fds.data(), read_buffer.data(), length); + + if (timeout >= 0) { + const s64 seconds = timeout / 1000; + const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000); + + if (seconds < 0) { + return {-1, Errno::INVAL}; + } + if (nanoseconds > 999'999'999) { + return {-1, Errno::INVAL}; + } + } else if (timeout != -1) { + return {-1, Errno::INVAL}; + } + + for (PollFD& pollfd : fds) { + ASSERT(False(pollfd.revents)); + + if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) { + LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); + pollfd.revents = PollEvents{}; + return {0, Errno::SUCCESS}; + } + + const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd]; + if (!descriptor) { + LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd); + pollfd.revents = PollEvents::Nval; + return {0, Errno::SUCCESS}; + } + } + + std::vector<Network::PollFD> host_pollfds(fds.size()); + std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) { + Network::PollFD result; + result.socket = file_descriptors[pollfd.fd]->socket.get(); + result.events = TranslatePollEventsToHost(pollfd.events); + result.revents = Network::PollEvents{}; + return result; + }); + + const auto result = Network::Poll(host_pollfds, timeout); + + const size_t num = host_pollfds.size(); + for (size_t i = 0; i < num; ++i) { + fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents); + } + std::memcpy(write_buffer.data(), fds.data(), length); + + return Translate(result); +} + +std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + const s32 new_fd = FindFreeFileDescriptorHandle(); + if (new_fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return {-1, Errno::MFILE}; + } + + FileDescriptor& descriptor = *file_descriptors[fd]; + auto [result, bsd_errno] = descriptor.socket->Accept(); + if (bsd_errno != Network::Errno::SUCCESS) { + return {-1, Translate(bsd_errno)}; + } + + file_descriptors[new_fd] = FileDescriptor{}; + FileDescriptor& new_descriptor = *file_descriptors[new_fd]; + new_descriptor.socket = std::move(result.socket); + new_descriptor.is_connection_based = descriptor.is_connection_based; + + ASSERT(write_buffer.size() == sizeof(SockAddrIn)); + const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); + std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in)); + + return {new_fd, Errno::SUCCESS}; +} + +Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + ASSERT(addr.size() == sizeof(SockAddrIn)); + SockAddrIn addr_in; + std::memcpy(&addr_in, addr.data(), sizeof(addr_in)); + + return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); +} + +Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn)); + SockAddrIn addr_in; + std::memcpy(&addr_in, addr.data(), sizeof(addr_in)); + + return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); +} + +Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); + if (bsd_errno != Network::Errno::SUCCESS) { + return Translate(bsd_errno); + } + const SockAddrIn guest_addrin = Translate(addr_in); + + ASSERT(write_buffer.size() == sizeof(guest_addrin)); + std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); + return Translate(bsd_errno); +} + +Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); + if (bsd_errno != Network::Errno::SUCCESS) { + return Translate(bsd_errno); + } + const SockAddrIn guest_addrin = Translate(addr_in); + + ASSERT(write_buffer.size() == sizeof(guest_addrin)); + std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); + return Translate(bsd_errno); +} + +Errno BSD::ListenImpl(s32 fd, s32 backlog) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + return Translate(file_descriptors[fd]->socket->Listen(backlog)); +} + +std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + FileDescriptor& descriptor = *file_descriptors[fd]; + + switch (cmd) { + case FcntlCmd::GETFL: + ASSERT(arg == 0); + return {descriptor.flags, Errno::SUCCESS}; + case FcntlCmd::SETFL: { + const bool enable = (arg & FLAG_O_NONBLOCK) != 0; + const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); + if (bsd_errno != Errno::SUCCESS) { + return {-1, bsd_errno}; + } + descriptor.flags = arg; + return {0, Errno::SUCCESS}; + } + default: + UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd); + return {-1, Errno::SUCCESS}; + } +} + +Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { + UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET + + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + Network::Socket* const socket = file_descriptors[fd]->socket.get(); + + if (optname == OptName::LINGER) { + ASSERT(optlen == sizeof(Linger)); + Linger linger; + std::memcpy(&linger, optval, sizeof(linger)); + ASSERT(linger.onoff == 0 || linger.onoff == 1); + + return Translate(socket->SetLinger(linger.onoff != 0, linger.linger)); + } + + ASSERT(optlen == sizeof(u32)); + u32 value; + std::memcpy(&value, optval, sizeof(value)); + + switch (optname) { + case OptName::REUSEADDR: + ASSERT(value == 0 || value == 1); + return Translate(socket->SetReuseAddr(value != 0)); + case OptName::BROADCAST: + ASSERT(value == 0 || value == 1); + return Translate(socket->SetBroadcast(value != 0)); + case OptName::SNDBUF: + return Translate(socket->SetSndBuf(value)); + case OptName::RCVBUF: + return Translate(socket->SetRcvBuf(value)); + case OptName::SNDTIMEO: + return Translate(socket->SetSndTimeo(value)); + case OptName::RCVTIMEO: + return Translate(socket->SetRcvTimeo(value)); + default: + UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); + return Errno::SUCCESS; + } +} + +Errno BSD::ShutdownImpl(s32 fd, s32 how) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how)); + return Translate(file_descriptors[fd]->socket->Shutdown(host_how)); +} + +std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + return Translate(file_descriptors[fd]->socket->Recv(flags, message)); +} + +std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, + std::vector<u8>& addr) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + FileDescriptor& descriptor = *file_descriptors[fd]; + + Network::SockAddrIn addr_in{}; + Network::SockAddrIn* p_addr_in = nullptr; + if (descriptor.is_connection_based) { + // Connection based file descriptors (e.g. TCP) zero addr + addr.clear(); + } else { + p_addr_in = &addr_in; + } + + // Apply flags + if ((flags & FLAG_MSG_DONTWAIT) != 0) { + flags &= ~FLAG_MSG_DONTWAIT; + if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { + descriptor.socket->SetNonBlock(true); + } + } + + const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in)); + + // Restore original state + if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { + descriptor.socket->SetNonBlock(false); + } + + if (p_addr_in) { + if (ret < 0) { + addr.clear(); + } else { + ASSERT(addr.size() == sizeof(SockAddrIn)); + const SockAddrIn result = Translate(addr_in); + std::memcpy(addr.data(), &result, sizeof(result)); + } + } + + return {ret, bsd_errno}; +} + +std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + return Translate(file_descriptors[fd]->socket->Send(message, flags)); +} + +std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message, + const std::vector<u8>& addr) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + Network::SockAddrIn addr_in; + Network::SockAddrIn* p_addr_in = nullptr; + if (!addr.empty()) { + ASSERT(addr.size() == sizeof(SockAddrIn)); + SockAddrIn guest_addr_in; + std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in)); + addr_in = Translate(guest_addr_in); + p_addr_in = &addr_in; + } + + return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); +} + +Errno BSD::CloseImpl(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); + if (bsd_errno != Errno::SUCCESS) { + return bsd_errno; + } + + LOG_INFO(Service, "Close socket fd={}", fd); + + file_descriptors[fd].reset(); + return bsd_errno; +} + +s32 BSD::FindFreeFileDescriptorHandle() noexcept { + for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) { + if (!file_descriptors[fd]) { + return fd; + } + } + return -1; +} + +bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { + if (fd > static_cast<s32>(MAX_FD) || fd < 0) { + LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); + return false; + } + if (!file_descriptors[fd]) { + LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd); + return false; + } + return true; +} + +void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // ret - rb.Push<u32>(0); // bsd errno + rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(bsd_errno); } -BSD::BSD(const char* name) : ServiceFramework(name) { +BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -121,32 +832,32 @@ BSD::BSD(const char* name) : ServiceFramework(name) { {3, nullptr, "SocketExempt"}, {4, nullptr, "Open"}, {5, &BSD::Select, "Select"}, - {6, nullptr, "Poll"}, + {6, &BSD::Poll, "Poll"}, {7, nullptr, "Sysctl"}, - {8, nullptr, "Recv"}, - {9, nullptr, "RecvFrom"}, - {10, nullptr, "Send"}, + {8, &BSD::Recv, "Recv"}, + {9, &BSD::RecvFrom, "RecvFrom"}, + {10, &BSD::Send, "Send"}, {11, &BSD::SendTo, "SendTo"}, - {12, nullptr, "Accept"}, + {12, &BSD::Accept, "Accept"}, {13, &BSD::Bind, "Bind"}, {14, &BSD::Connect, "Connect"}, - {15, nullptr, "GetPeerName"}, - {16, nullptr, "GetSockName"}, - {17, nullptr, "GetSockOpt"}, + {15, &BSD::GetPeerName, "GetPeerName"}, + {16, &BSD::GetSockName, "GetSockName"}, + {17, &BSD::GetSockOpt, "GetSockOpt"}, {18, &BSD::Listen, "Listen"}, {19, nullptr, "Ioctl"}, - {20, nullptr, "Fcntl"}, + {20, &BSD::Fcntl, "Fcntl"}, {21, &BSD::SetSockOpt, "SetSockOpt"}, - {22, nullptr, "Shutdown"}, + {22, &BSD::Shutdown, "Shutdown"}, {23, nullptr, "ShutdownAllSockets"}, - {24, nullptr, "Write"}, + {24, &BSD::Write, "Write"}, {25, nullptr, "Read"}, {26, &BSD::Close, "Close"}, {27, nullptr, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, {29, nullptr, "RecvMMsg"}, {30, nullptr, "SendMMsg"}, - {31, nullptr, "EventFd"}, + {31, &BSD::EventFd, "EventFd"}, {32, nullptr, "RegisterResourceStatisticsName"}, {33, nullptr, "Initialize2"}, }; @@ -157,7 +868,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) { BSD::~BSD() = default; -BSDCFG::BSDCFG() : ServiceFramework{"bsdcfg"} { +BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetIfUp"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 3098e3baf..1d2df9c61 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -4,35 +4,174 @@ #pragma once +#include <memory> +#include <string_view> +#include <vector> + +#include "common/common_types.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +#include "core/hle/service/sockets/sockets.h" + +namespace Core { +class System; +} + +namespace Network { +class Socket; +} namespace Service::Sockets { class BSD final : public ServiceFramework<BSD> { public: - explicit BSD(const char* name); + explicit BSD(Core::System& system_, const char* name); ~BSD() override; private: + /// Maximum number of file descriptors + static constexpr size_t MAX_FD = 128; + + struct FileDescriptor { + std::unique_ptr<Network::Socket> socket; + s32 flags = 0; + bool is_connection_based = false; + }; + + struct PollWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 nfds; + s32 timeout; + std::vector<u8> read_buffer; + std::vector<u8> write_buffer; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct AcceptWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + std::vector<u8> write_buffer; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct ConnectWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + std::vector<u8> addr; + Errno bsd_errno{}; + }; + + struct RecvWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector<u8> message; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct RecvFromWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector<u8> message; + std::vector<u8> addr; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct SendWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector<u8> message; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct SendToWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector<u8> message; + std::vector<u8> addr; + s32 ret{}; + Errno bsd_errno{}; + }; + void RegisterClient(Kernel::HLERequestContext& ctx); void StartMonitoring(Kernel::HLERequestContext& ctx); void Socket(Kernel::HLERequestContext& ctx); void Select(Kernel::HLERequestContext& ctx); + void Poll(Kernel::HLERequestContext& ctx); + void Accept(Kernel::HLERequestContext& ctx); void Bind(Kernel::HLERequestContext& ctx); void Connect(Kernel::HLERequestContext& ctx); + void GetPeerName(Kernel::HLERequestContext& ctx); + void GetSockName(Kernel::HLERequestContext& ctx); + void GetSockOpt(Kernel::HLERequestContext& ctx); void Listen(Kernel::HLERequestContext& ctx); + void Fcntl(Kernel::HLERequestContext& ctx); void SetSockOpt(Kernel::HLERequestContext& ctx); + void Shutdown(Kernel::HLERequestContext& ctx); + void Recv(Kernel::HLERequestContext& ctx); + void RecvFrom(Kernel::HLERequestContext& ctx); + void Send(Kernel::HLERequestContext& ctx); void SendTo(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); + void EventFd(Kernel::HLERequestContext& ctx); + + template <typename Work> + void ExecuteWork(Kernel::HLERequestContext& ctx, Work work); + + std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol); + std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer, + s32 nfds, s32 timeout); + std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer); + Errno BindImpl(s32 fd, const std::vector<u8>& addr); + Errno ConnectImpl(s32 fd, const std::vector<u8>& addr); + Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer); + Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer); + Errno ListenImpl(s32 fd, s32 backlog); + std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg); + Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval); + Errno ShutdownImpl(s32 fd, s32 how); + std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message); + std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, + std::vector<u8>& addr); + std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message); + std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message, + const std::vector<u8>& addr); + Errno CloseImpl(s32 fd); + + s32 FindFreeFileDescriptorHandle() noexcept; + bool IsFileDescriptorValid(s32 fd) const noexcept; + + void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; - /// Id to use for the next open file descriptor. - u32 next_fd = 1; + std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; }; class BSDCFG final : public ServiceFramework<BSDCFG> { public: - explicit BSDCFG(); + explicit BSDCFG(Core::System& system_); ~BSDCFG() override; }; diff --git a/src/core/hle/service/sockets/ethc.cpp b/src/core/hle/service/sockets/ethc.cpp index abbeb4c50..05681ca2d 100644 --- a/src/core/hle/service/sockets/ethc.cpp +++ b/src/core/hle/service/sockets/ethc.cpp @@ -6,7 +6,7 @@ namespace Service::Sockets { -ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} { +ETHC_C::ETHC_C(Core::System& system_) : ServiceFramework{system_, "ethc:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -23,7 +23,7 @@ ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} { ETHC_C::~ETHC_C() = default; -ETHC_I::ETHC_I() : ServiceFramework{"ethc:i"} { +ETHC_I::ETHC_I(Core::System& system_) : ServiceFramework{system_, "ethc:i"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetReadableHandle"}, diff --git a/src/core/hle/service/sockets/ethc.h b/src/core/hle/service/sockets/ethc.h index da2c7f741..71884182e 100644 --- a/src/core/hle/service/sockets/ethc.h +++ b/src/core/hle/service/sockets/ethc.h @@ -6,17 +6,21 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class ETHC_C final : public ServiceFramework<ETHC_C> { public: - explicit ETHC_C(); + explicit ETHC_C(Core::System& system_); ~ETHC_C() override; }; class ETHC_I final : public ServiceFramework<ETHC_I> { public: - explicit ETHC_I(); + explicit ETHC_I(Core::System& system_); ~ETHC_I() override; }; diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 40d781124..51c3739bb 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -6,7 +6,7 @@ namespace Service::Sockets { -NSD::NSD(const char* name) : ServiceFramework(name) { +NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {10, nullptr, "GetSettingName"}, diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index d842e3232..becf93125 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -4,14 +4,17 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class NSD final : public ServiceFramework<NSD> { public: - explicit NSD(const char* name); + explicit NSD(Core::System& system_, const char* name); ~NSD() override; }; diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index e3017451f..3a6329f56 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -7,25 +7,7 @@ namespace Service::Sockets { -void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { - struct Parameters { - u8 use_nsd_resolve; - u32 unknown; - u64 process_id; - }; - - IPC::RequestParser rp{ctx}; - const auto parameters = rp.PopRaw<Parameters>(); - - LOG_WARNING(Service, - "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}", - parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { +SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} { static const FunctionInfo functions[] = { {0, nullptr, "SetDnsAddressesPrivate"}, {1, nullptr, "GetDnsAddressPrivate"}, @@ -49,4 +31,22 @@ SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { SFDNSRES::~SFDNSRES() = default; +void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { + struct Parameters { + u8 use_nsd_resolve; + u32 unknown; + u64 process_id; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw<Parameters>(); + + LOG_WARNING(Service, + "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}", + parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index acd3647bb..faa6b7d0d 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -7,11 +7,15 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class SFDNSRES final : public ServiceFramework<SFDNSRES> { public: - explicit SFDNSRES(); + explicit SFDNSRES(Core::System& system_); ~SFDNSRES() override; private: diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index 08d2d306a..96f73bce3 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -10,18 +10,18 @@ namespace Service::Sockets { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager); - std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager); - std::make_shared<BSDCFG>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager); + std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager); + std::make_shared<BSDCFG>(system)->InstallAsService(service_manager); - std::make_shared<ETHC_C>()->InstallAsService(service_manager); - std::make_shared<ETHC_I>()->InstallAsService(service_manager); + std::make_shared<ETHC_C>(system)->InstallAsService(service_manager); + std::make_shared<ETHC_I>(system)->InstallAsService(service_manager); - std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager); - std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager); + std::make_shared<NSD>(system, "nsd:a")->InstallAsService(service_manager); + std::make_shared<NSD>(system, "nsd:u")->InstallAsService(service_manager); - std::make_shared<SFDNSRES>()->InstallAsService(service_manager); + std::make_shared<SFDNSRES>(system)->InstallAsService(service_manager); } } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index ca8a6a7e0..5a65ed2a9 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -4,11 +4,99 @@ #pragma once +#include "common/common_types.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { +enum class Errno : u32 { + SUCCESS = 0, + BADF = 9, + AGAIN = 11, + INVAL = 22, + MFILE = 24, + NOTCONN = 107, +}; + +enum class Domain : u32 { + INET = 2, +}; + +enum class Type : u32 { + STREAM = 1, + DGRAM = 2, + RAW = 3, + SEQPACKET = 5, +}; + +enum class Protocol : u32 { + UNSPECIFIED = 0, + ICMP = 1, + TCP = 6, + UDP = 17, +}; + +enum class OptName : u32 { + REUSEADDR = 0x4, + BROADCAST = 0x20, + LINGER = 0x80, + SNDBUF = 0x1001, + RCVBUF = 0x1002, + SNDTIMEO = 0x1005, + RCVTIMEO = 0x1006, +}; + +enum class ShutdownHow : s32 { + RD = 0, + WR = 1, + RDWR = 2, +}; + +enum class FcntlCmd : s32 { + GETFL = 3, + SETFL = 4, +}; + +struct SockAddrIn { + u8 len; + u8 family; + u16 portno; + std::array<u8, 4> ip; + std::array<u8, 8> zeroes; +}; + +enum class PollEvents : u16 { + // Using Pascal case because IN is a macro on Windows. + In = 1 << 0, + Pri = 1 << 1, + Out = 1 << 2, + Err = 1 << 3, + Hup = 1 << 4, + Nval = 1 << 5, +}; + +DECLARE_ENUM_FLAG_OPERATORS(PollEvents); + +struct PollFD { + s32 fd; + PollEvents events; + PollEvents revents; +}; + +struct Linger { + u32 onoff; + u32 linger; +}; + +constexpr u32 FLAG_MSG_DONTWAIT = 0x80; + +constexpr u32 FLAG_O_NONBLOCK = 0x800; + /// Registers all Sockets services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp new file mode 100644 index 000000000..ca61d72ca --- /dev/null +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -0,0 +1,166 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <utility> + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/network/network.h" + +namespace Service::Sockets { + +Errno Translate(Network::Errno value) { + switch (value) { + case Network::Errno::SUCCESS: + return Errno::SUCCESS; + case Network::Errno::BADF: + return Errno::BADF; + case Network::Errno::AGAIN: + return Errno::AGAIN; + case Network::Errno::INVAL: + return Errno::INVAL; + case Network::Errno::MFILE: + return Errno::MFILE; + case Network::Errno::NOTCONN: + return Errno::NOTCONN; + default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", value); + return Errno::SUCCESS; + } +} + +std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) { + return {value.first, Translate(value.second)}; +} + +Network::Domain Translate(Domain domain) { + switch (domain) { + case Domain::INET: + return Network::Domain::INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); + return {}; + } +} + +Domain Translate(Network::Domain domain) { + switch (domain) { + case Network::Domain::INET: + return Domain::INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); + return {}; + } +} + +Network::Type Translate(Type type) { + switch (type) { + case Type::STREAM: + return Network::Type::STREAM; + case Type::DGRAM: + return Network::Type::DGRAM; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return Network::Type{}; + } +} + +Network::Protocol Translate(Type type, Protocol protocol) { + switch (protocol) { + case Protocol::UNSPECIFIED: + LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type"); + switch (type) { + case Type::DGRAM: + return Network::Protocol::UDP; + case Type::STREAM: + return Network::Protocol::TCP; + default: + return Network::Protocol::TCP; + } + case Protocol::TCP: + return Network::Protocol::TCP; + case Protocol::UDP: + return Network::Protocol::UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); + return Network::Protocol::TCP; + } +} + +Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { + Network::PollEvents result{}; + const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) { + if (True(flags & from)) { + flags &= ~from; + result |= to; + } + }; + translate(PollEvents::In, Network::PollEvents::In); + translate(PollEvents::Pri, Network::PollEvents::Pri); + translate(PollEvents::Out, Network::PollEvents::Out); + translate(PollEvents::Err, Network::PollEvents::Err); + translate(PollEvents::Hup, Network::PollEvents::Hup); + translate(PollEvents::Nval, Network::PollEvents::Nval); + + UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); + return result; +} + +PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { + PollEvents result{}; + const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) { + if (True(flags & from)) { + flags &= ~from; + result |= to; + } + }; + + translate(Network::PollEvents::In, PollEvents::In); + translate(Network::PollEvents::Pri, PollEvents::Pri); + translate(Network::PollEvents::Out, PollEvents::Out); + translate(Network::PollEvents::Err, PollEvents::Err); + translate(Network::PollEvents::Hup, PollEvents::Hup); + translate(Network::PollEvents::Nval, PollEvents::Nval); + + UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); + return result; +} + +Network::SockAddrIn Translate(SockAddrIn value) { + ASSERT(value.len == 0 || value.len == sizeof(value)); + + return { + .family = Translate(static_cast<Domain>(value.family)), + .ip = value.ip, + .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8), + }; +} + +SockAddrIn Translate(Network::SockAddrIn value) { + return { + .len = sizeof(SockAddrIn), + .family = static_cast<u8>(Translate(value.family)), + .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8), + .ip = value.ip, + .zeroes = {}, + }; +} + +Network::ShutdownHow Translate(ShutdownHow how) { + switch (how) { + case ShutdownHow::RD: + return Network::ShutdownHow::RD; + case ShutdownHow::WR: + return Network::ShutdownHow::WR; + case ShutdownHow::RDWR: + return Network::ShutdownHow::RDWR; + default: + UNIMPLEMENTED_MSG("Unimplemented how={}", how); + return {}; + } +} + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h new file mode 100644 index 000000000..057d1ff22 --- /dev/null +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -0,0 +1,48 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <utility> + +#include "common/common_types.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/network/network.h" + +namespace Service::Sockets { + +/// Translate abstract errno to guest errno +Errno Translate(Network::Errno value); + +/// Translate abstract return value errno pair to guest return value errno pair +std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value); + +/// Translate guest domain to abstract domain +Network::Domain Translate(Domain domain); + +/// Translate abstract domain to guest domain +Domain Translate(Network::Domain domain); + +/// Translate guest type to abstract type +Network::Type Translate(Type type); + +/// Translate guest protocol to abstract protocol +Network::Protocol Translate(Type type, Protocol protocol); + +/// Translate abstract poll event flags to guest poll event flags +Network::PollEvents TranslatePollEventsToHost(PollEvents flags); + +/// Translate guest poll event flags to abstract poll event flags +PollEvents TranslatePollEventsToGuest(Network::PollEvents flags); + +/// Translate guest socket address structure to abstract socket address structure +Network::SockAddrIn Translate(SockAddrIn value); + +/// Translate abstract socket address structure to guest socket address structure +SockAddrIn Translate(Network::SockAddrIn value); + +/// Translate guest shutdown mode to abstract shutdown mode +Network::ShutdownHow Translate(ShutdownHow how); + +} // namespace Service::Sockets diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp index 674928798..1beca417c 100644 --- a/src/core/hle/service/spl/csrng.cpp +++ b/src/core/hle/service/spl/csrng.cpp @@ -6,7 +6,8 @@ namespace Service::SPL { -CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") { +CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_) + : Interface(system_, std::move(module_), "csrng") { static const FunctionInfo functions[] = { {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, }; diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h index 764d5ceb0..5c0bd2199 100644 --- a/src/core/hle/service/spl/csrng.h +++ b/src/core/hle/service/spl/csrng.h @@ -6,11 +6,15 @@ #include "core/hle/service/spl/module.h" +namespace Core { +class System; +} + namespace Service::SPL { class CSRNG final : public Module::Interface { public: - explicit CSRNG(std::shared_ptr<Module> module); + explicit CSRNG(Core::System& system_, std::shared_ptr<Module> module_); ~CSRNG() override; }; diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index 865ed3b91..dea6b0fe0 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -17,8 +17,9 @@ namespace Service::SPL { -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)), +Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)}, rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} Module::Interface::~Interface() = default; @@ -38,10 +39,10 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<CSRNG>(module)->InstallAsService(service_manager); - std::make_shared<SPL>(module)->InstallAsService(service_manager); + std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager); + std::make_shared<SPL>(system, module)->InstallAsService(service_manager); } } // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h index afa1f0295..71855c1bf 100644 --- a/src/core/hle/service/spl/module.h +++ b/src/core/hle/service/spl/module.h @@ -7,13 +7,18 @@ #include <random> #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SPL { class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(Core::System& system_, std::shared_ptr<Module> module_, + const char* name); ~Interface() override; void GetRandomBytes(Kernel::HLERequestContext& ctx); @@ -27,6 +32,6 @@ public: }; /// Registers all SPL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index 773551464..3fabc2c79 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -6,7 +6,8 @@ namespace Service::SPL { -SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") { +SPL::SPL(Core::System& system_, std::shared_ptr<Module> module_) + : Interface(system_, std::move(module_), "spl:") { static const FunctionInfo functions[] = { {0, nullptr, "GetConfig"}, {1, nullptr, "ModularExponentiate"}, diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h index 3637d1623..d27d16b86 100644 --- a/src/core/hle/service/spl/spl.h +++ b/src/core/hle/service/spl/spl.h @@ -6,11 +6,15 @@ #include "core/hle/service/spl/module.h" +namespace Core { +class System; +} + namespace Service::SPL { class SPL final : public Module::Interface { public: - explicit SPL(std::shared_ptr<Module> module); + explicit SPL(Core::System& system_, std::shared_ptr<Module> module_); ~SPL() override; }; diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 1ba8c19a0..dc2baca4a 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -12,7 +12,7 @@ namespace Service::SSL { class ISslConnection final : public ServiceFramework<ISslConnection> { public: - ISslConnection() : ServiceFramework("ISslConnection") { + explicit ISslConnection(Core::System& system_) : ServiceFramework{system_, "ISslConnection"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetSocketDescriptor"}, @@ -52,7 +52,7 @@ public: class ISslContext final : public ServiceFramework<ISslContext> { public: - ISslContext() : ServiceFramework("ISslContext") { + explicit ISslContext(Core::System& system_) : ServiceFramework{system_, "ISslContext"} { static const FunctionInfo functions[] = { {0, &ISslContext::SetOption, "SetOption"}, {1, nullptr, "GetOption"}, @@ -92,13 +92,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISslConnection>(); + rb.PushIpcInterface<ISslConnection>(system); } }; class SSL final : public ServiceFramework<SSL> { public: - explicit SSL() : ServiceFramework{"ssl"} { + explicit SSL(Core::System& system_) : ServiceFramework{system_, "ssl"} { // clang-format off static const FunctionInfo functions[] = { {0, &SSL::CreateContext, "CreateContext"}, @@ -123,7 +123,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISslContext>(); + rb.PushIpcInterface<ISslContext>(system); } void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { @@ -137,8 +137,8 @@ private: } }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<SSL>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<SSL>(system)->InstallAsService(service_manager); } } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h index 5cb04c3b9..a3aa4b4b5 100644 --- a/src/core/hle/service/ssl/ssl.h +++ b/src/core/hle/service/ssl/ssl.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::SSL { /// Registers all SSL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::SSL diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h index 72e1921ec..b78892223 100644 --- a/src/core/hle/service/time/clock_types.h +++ b/src/core/hle/service/time/clock_types.h @@ -73,19 +73,19 @@ struct TimeSpanType { static_assert(sizeof(TimeSpanType) == 8, "TimeSpanType is incorrect size"); struct ClockSnapshot { - SystemClockContext user_context{}; - SystemClockContext network_context{}; - s64 user_time{}; - s64 network_time{}; - TimeZone::CalendarTime user_calendar_time{}; - TimeZone::CalendarTime network_calendar_time{}; - TimeZone::CalendarAdditionalInfo user_calendar_additional_time{}; - TimeZone::CalendarAdditionalInfo network_calendar_additional_time{}; - SteadyClockTimePoint steady_clock_time_point{}; - TimeZone::LocationName location_name{}; - u8 is_automatic_correction_enabled{}; - u8 type{}; - INSERT_PADDING_BYTES(0x2); + SystemClockContext user_context; + SystemClockContext network_context; + s64 user_time; + s64 network_time; + TimeZone::CalendarTime user_calendar_time; + TimeZone::CalendarTime network_calendar_time; + TimeZone::CalendarAdditionalInfo user_calendar_additional_time; + TimeZone::CalendarAdditionalInfo network_calendar_additional_time; + SteadyClockTimePoint steady_clock_time_point; + TimeZone::LocationName location_name; + u8 is_automatic_correction_enabled; + u8 type; + INSERT_PADDING_BYTES_NOINIT(0x2); static ResultCode GetCurrentTime(s64& current_time, const SteadyClockTimePoint& steady_clock_time_point, diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index ba8fd6152..a01d9e0ff 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -7,7 +7,7 @@ namespace Service::Time { Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* name) - : Module::Interface(std::move(module), system, name) { + : Interface(std::move(module), system, name) { // clang-format off static const FunctionInfo functions[] = { {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp index 8af17091c..b9faa474e 100644 --- a/src/core/hle/service/time/standard_user_system_clock_core.cpp +++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp @@ -4,7 +4,7 @@ #include "common/assert.h" #include "core/core.h" -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_event.h" #include "core/hle/service/time/standard_local_system_clock_core.h" #include "core/hle/service/time/standard_network_system_clock_core.h" #include "core/hle/service/time/standard_user_system_clock_core.h" @@ -18,8 +18,10 @@ StandardUserSystemClockCore::StandardUserSystemClockCore( local_system_clock_core{local_system_clock_core}, network_system_clock_core{network_system_clock_core}, auto_correction_enabled{}, auto_correction_time{SteadyClockTimePoint::GetRandom()}, - auto_correction_event{Kernel::WritableEvent::CreateEventPair( - system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {} + auto_correction_event{Kernel::KEvent::Create( + system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} { + auto_correction_event->Initialize(); +} ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system, bool value) { diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h index ef3d468b7..aac44d72f 100644 --- a/src/core/hle/service/time/standard_user_system_clock_core.h +++ b/src/core/hle/service/time/standard_user_system_clock_core.h @@ -4,7 +4,6 @@ #pragma once -#include "core/hle/kernel/writable_event.h" #include "core/hle/service/time/clock_types.h" #include "core/hle/service/time/system_clock_core.h" @@ -12,6 +11,10 @@ namespace Core { class System; } +namespace Kernel { +class KEvent; +} + namespace Service::Time::Clock { class StandardLocalSystemClockCore; @@ -51,7 +54,7 @@ private: StandardNetworkSystemClockCore& network_system_clock_core; bool auto_correction_enabled{}; SteadyClockTimePoint auto_correction_time; - Kernel::EventPair auto_correction_event; + std::shared_ptr<Kernel::KEvent> auto_correction_event; }; } // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp index 5cdb80703..bca7d869e 100644 --- a/src/core/hle/service/time/system_clock_context_update_callback.cpp +++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/time/errors.h" #include "core/hle/service/time/system_clock_context_update_callback.h" @@ -21,7 +21,7 @@ bool SystemClockContextUpdateCallback::NeedUpdate(const SystemClockContext& valu } void SystemClockContextUpdateCallback::RegisterOperationEvent( - std::shared_ptr<Kernel::WritableEvent>&& writable_event) { + std::shared_ptr<Kernel::KWritableEvent>&& writable_event) { operation_event_list.emplace_back(std::move(writable_event)); } diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h index 2b0fa7e75..797954958 100644 --- a/src/core/hle/service/time/system_clock_context_update_callback.h +++ b/src/core/hle/service/time/system_clock_context_update_callback.h @@ -9,7 +9,7 @@ #include "core/hle/service/time/clock_types.h" namespace Kernel { -class WritableEvent; +class KWritableEvent; } namespace Service::Time::Clock { @@ -24,7 +24,7 @@ public: bool NeedUpdate(const SystemClockContext& value) const; - void RegisterOperationEvent(std::shared_ptr<Kernel::WritableEvent>&& writable_event); + void RegisterOperationEvent(std::shared_ptr<Kernel::KWritableEvent>&& writable_event); void BroadcastOperationEvent(); @@ -37,7 +37,7 @@ protected: private: bool has_context{}; - std::vector<std::shared_ptr<Kernel::WritableEvent>> operation_event_list; + std::vector<std::shared_ptr<Kernel::KWritableEvent>> operation_event_list; }; } // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index ee4fa4b48..18629dd7e 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -10,7 +10,8 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" #include "core/hle/service/time/time_sharedmemory.h" @@ -20,8 +21,8 @@ namespace Service::Time { class ISystemClock final : public ServiceFramework<ISystemClock> { public: - explicit ISystemClock(Clock::SystemClockCore& clock_core, Core::System& system) - : ServiceFramework("ISystemClock"), clock_core{clock_core}, system{system} { + explicit ISystemClock(Clock::SystemClockCore& clock_core_, Core::System& system_) + : ServiceFramework{system_, "ISystemClock"}, clock_core{clock_core_} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, @@ -81,13 +82,12 @@ private: } Clock::SystemClockCore& clock_core; - Core::System& system; }; class ISteadyClock final : public ServiceFramework<ISteadyClock> { public: - explicit ISteadyClock(Clock::SteadyClockCore& clock_core, Core::System& system) - : ServiceFramework("ISteadyClock"), clock_core{clock_core}, system{system} { + explicit ISteadyClock(Clock::SteadyClockCore& clock_core_, Core::System& system_) + : ServiceFramework{system_, "ISteadyClock"}, clock_core{clock_core_} { static const FunctionInfo functions[] = { {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, {2, nullptr, "GetTestOffset"}, @@ -118,14 +118,13 @@ private: } Clock::SteadyClockCore& clock_core; - Core::System& system; }; ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal( - Kernel::Thread* thread, Clock::SystemClockContext user_context, + Kernel::KThread* thread, Clock::SystemClockContext user_context, Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) { - auto& time_manager{module->GetTimeManager()}; + auto& time_manager{system.GetTimeManager()}; clock_snapshot.is_automatic_correction_enabled = time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled(); @@ -182,7 +181,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore(), + rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardUserSystemClockCore(), system); } @@ -190,7 +189,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore(), + rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardNetworkSystemClockCore(), system); } @@ -198,29 +197,29 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore(), - system); + rb.PushIpcInterface<ISteadyClock>(system.GetTimeManager().GetStandardSteadyClockCore(), system); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager()); + rb.PushIpcInterface<ITimeZoneService>(system, + system.GetTimeManager().GetTimeZoneContentManager()); } void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardLocalSystemClockCore(), + rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardLocalSystemClockCore(), system); } void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient( Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()}; + auto& clock_core{system.GetTimeManager().GetStandardNetworkSystemClockCore()}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system)); @@ -229,7 +228,7 @@ void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient( void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()}; + auto& steady_clock_core{system.GetTimeManager().GetStandardSteadyClockCore()}; if (!steady_clock_core.IsInitialized()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_UNINITIALIZED_CLOCK); @@ -262,8 +261,8 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { Clock::SystemClockContext user_context{}; if (const ResultCode result{ - module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext( - system, user_context)}; + system.GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(system, + user_context)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -271,7 +270,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { } Clock::SystemClockContext network_context{}; if (const ResultCode result{ - module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( + system.GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( system, network_context)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; @@ -372,16 +371,17 @@ void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& c LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder()); + rb.PushCopyObjects(SharedFrom(&system.Kernel().GetTimeSharedMem())); } -Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) - : ServiceFramework(name), module{std::move(module)}, system{system} {} +Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; void InstallInterfaces(Core::System& system) { - auto module{std::make_shared<Module>(system)}; + auto module{std::make_shared<Module>()}; std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager()); std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager()); std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager()); diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index 41f3002e9..4154c7ee9 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -16,11 +16,12 @@ namespace Service::Time { class Module final { public: - Module(Core::System& system) : time_manager{system} {} + Module() = default; class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr<Module> module_, Core::System& system_, + const char* name); ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); @@ -38,21 +39,13 @@ public: private: ResultCode GetClockSnapshotFromSystemClockContextInternal( - Kernel::Thread* thread, Clock::SystemClockContext user_context, + Kernel::KThread* thread, Clock::SystemClockContext user_context, Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& cloc_snapshot); protected: std::shared_ptr<Module> module; - Core::System& system; }; - - TimeManager& GetTimeManager() { - return time_manager; - } - -private: - TimeManager time_manager; }; /// Registers all Time services with the specified service manager. diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp index b4dfe45e5..858623e2b 100644 --- a/src/core/hle/service/time/time_manager.cpp +++ b/src/core/hle/service/time/time_manager.cpp @@ -22,125 +22,282 @@ static std::chrono::seconds GetSecondsSinceEpoch() { Settings::values.custom_rtc_differential; } -static s64 GetExternalTimeZoneOffset() { - // With "auto" timezone setting, we use the external system's timezone offset - if (Settings::GetTimeZoneString() == "auto") { - return Common::TimeZone::GetCurrentOffsetSeconds().count(); - } - return 0; -} - static s64 GetExternalRtcValue() { - return GetSecondsSinceEpoch().count() + GetExternalTimeZoneOffset(); -} - -TimeManager::TimeManager(Core::System& system) - : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core}, - standard_network_system_clock_core{standard_steady_clock_core}, - standard_user_system_clock_core{standard_local_system_clock_core, - standard_network_system_clock_core, system}, - ephemeral_network_system_clock_core{tick_based_steady_clock_core}, - local_system_clock_context_writer{ - std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)}, - network_system_clock_context_writer{ - std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)}, - ephemeral_network_system_clock_context_writer{ - std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()}, - time_zone_content_manager{*this, system} { - - const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())}; - SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {}); - SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds()); - SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy); - SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom()); - SetupEphemeralNetworkSystemClock(); + return GetSecondsSinceEpoch().count() + TimeManager::GetExternalTimeZoneOffset(); } -TimeManager::~TimeManager() = default; +struct TimeManager::Impl final { + explicit Impl(Core::System& system) + : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core}, + standard_network_system_clock_core{standard_steady_clock_core}, + standard_user_system_clock_core{standard_local_system_clock_core, + standard_network_system_clock_core, system}, + ephemeral_network_system_clock_core{tick_based_steady_clock_core}, + local_system_clock_context_writer{ + std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)}, + network_system_clock_context_writer{ + std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)}, + ephemeral_network_system_clock_context_writer{ + std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()}, + time_zone_content_manager{system} { -void TimeManager::SetupTimeZoneManager(std::string location_name, - Clock::SteadyClockTimePoint time_zone_updated_time_point, - std::size_t total_location_name_count, - u128 time_zone_rule_version, - FileSys::VirtualFile& vfs_file) { - if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( - location_name, vfs_file) != RESULT_SUCCESS) { - UNREACHABLE(); - return; - } - - time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point); - time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount( - total_location_name_count); - time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version); - time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized(); -} - -void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, - Clock::TimeSpanType setup_value, - Clock::TimeSpanType internal_offset, - bool is_rtc_reset_detected) { - standard_steady_clock_core.SetClockSourceId(clock_source_id); - standard_steady_clock_core.SetSetupValue(setup_value); - standard_steady_clock_core.SetInternalOffset(internal_offset); - standard_steady_clock_core.MarkAsInitialized(); - - const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)}; - shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point); -} - -void TimeManager::SetupStandardLocalSystemClock(Core::System& system, - Clock::SystemClockContext clock_context, - s64 posix_time) { - standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer); - - const auto current_time_point{ - standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)}; - if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) { - standard_local_system_clock_core.SetSystemClockContext(clock_context); - } else { - if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) { + const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())}; + SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {}); + SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds()); + SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy); + SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom()); + SetupEphemeralNetworkSystemClock(); + } + + ~Impl() = default; + + Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() { + return standard_steady_clock_core; + } + + const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const { + return standard_steady_clock_core; + } + + Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() { + return standard_local_system_clock_core; + } + + const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const { + return standard_local_system_clock_core; + } + + Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() { + return standard_network_system_clock_core; + } + + const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const { + return standard_network_system_clock_core; + } + + Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() { + return standard_user_system_clock_core; + } + + const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const { + return standard_user_system_clock_core; + } + + TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() { + return time_zone_content_manager; + } + + const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const { + return time_zone_content_manager; + } + + SharedMemory& GetSharedMemory() { + return shared_memory; + } + + const SharedMemory& GetSharedMemory() const { + return shared_memory; + } + + void SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file) { + if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( + location_name, vfs_file) != RESULT_SUCCESS) { UNREACHABLE(); return; } + + time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point); + time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount( + total_location_name_count); + time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion( + time_zone_rule_version); + time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized(); } - standard_local_system_clock_core.MarkAsInitialized(); -} + static s64 GetExternalTimeZoneOffset() { + // With "auto" timezone setting, we use the external system's timezone offset + if (Settings::GetTimeZoneString() == "auto") { + return Common::TimeZone::GetCurrentOffsetSeconds().count(); + } + return 0; + } -void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, - Clock::TimeSpanType sufficient_accuracy) { - standard_network_system_clock_core.SetUpdateCallbackInstance( - network_system_clock_context_writer); + void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, + Clock::TimeSpanType setup_value, + Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected) { + standard_steady_clock_core.SetClockSourceId(clock_source_id); + standard_steady_clock_core.SetSetupValue(setup_value); + standard_steady_clock_core.SetInternalOffset(internal_offset); + standard_steady_clock_core.MarkAsInitialized(); - if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) { - UNREACHABLE(); - return; + const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)}; + shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point); } - standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy( - sufficient_accuracy); - standard_network_system_clock_core.MarkAsInitialized(); -} + void SetupStandardLocalSystemClock(Core::System& system, + Clock::SystemClockContext clock_context, s64 posix_time) { + standard_local_system_clock_core.SetUpdateCallbackInstance( + local_system_clock_context_writer); + + const auto current_time_point{ + standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)}; + if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) { + standard_local_system_clock_core.SetSystemClockContext(clock_context); + } else { + if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != + RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + } + + standard_local_system_clock_core.MarkAsInitialized(); + } + + void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, + Clock::TimeSpanType sufficient_accuracy) { + standard_network_system_clock_core.SetUpdateCallbackInstance( + network_system_clock_context_writer); -void TimeManager::SetupStandardUserSystemClock( - Core::System& system, bool is_automatic_correction_enabled, - Clock::SteadyClockTimePoint steady_clock_time_point) { - if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled( - system, is_automatic_correction_enabled) != RESULT_SUCCESS) { - UNREACHABLE(); - return; + if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != + RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy( + sufficient_accuracy); + standard_network_system_clock_core.MarkAsInitialized(); } - standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point); - standard_user_system_clock_core.MarkAsInitialized(); - shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled); + void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled, + Clock::SteadyClockTimePoint steady_clock_time_point) { + if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled( + system, is_automatic_correction_enabled) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point); + standard_user_system_clock_core.MarkAsInitialized(); + shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled); + } + + void SetupEphemeralNetworkSystemClock() { + ephemeral_network_system_clock_core.SetUpdateCallbackInstance( + ephemeral_network_system_clock_context_writer); + ephemeral_network_system_clock_core.MarkAsInitialized(); + } + + void UpdateLocalSystemClockTime(Core::System& system, s64 posix_time) { + const auto timespan{Service::Time::Clock::TimeSpanType::FromSeconds(posix_time)}; + if (GetStandardLocalSystemClockCore() + .SetCurrentTime(system, timespan.ToSeconds()) + .IsError()) { + UNREACHABLE(); + return; + } + } + + SharedMemory shared_memory; + + Clock::StandardSteadyClockCore standard_steady_clock_core; + Clock::TickBasedSteadyClockCore tick_based_steady_clock_core; + Clock::StandardLocalSystemClockCore standard_local_system_clock_core; + Clock::StandardNetworkSystemClockCore standard_network_system_clock_core; + Clock::StandardUserSystemClockCore standard_user_system_clock_core; + Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core; + + std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer; + std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer; + std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter> + ephemeral_network_system_clock_context_writer; + + TimeZone::TimeZoneContentManager time_zone_content_manager; +}; + +TimeManager::TimeManager(Core::System& system) : system{system} {} + +TimeManager::~TimeManager() = default; + +void TimeManager::Initialize() { + impl = std::make_unique<Impl>(system); + + // Time zones can only be initialized after impl is valid + impl->time_zone_content_manager.Initialize(*this); +} + +Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() { + return impl->standard_steady_clock_core; +} + +const Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() const { + return impl->standard_steady_clock_core; +} + +Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() { + return impl->standard_local_system_clock_core; +} + +const Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() const { + return impl->standard_local_system_clock_core; +} + +Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore() { + return impl->standard_network_system_clock_core; } -void TimeManager::SetupEphemeralNetworkSystemClock() { - ephemeral_network_system_clock_core.SetUpdateCallbackInstance( - ephemeral_network_system_clock_context_writer); - ephemeral_network_system_clock_core.MarkAsInitialized(); +const Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore() + const { + return impl->standard_network_system_clock_core; +} + +Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() { + return impl->standard_user_system_clock_core; +} + +const Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() const { + return impl->standard_user_system_clock_core; +} + +TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() { + return impl->time_zone_content_manager; +} + +const TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() const { + return impl->time_zone_content_manager; +} + +SharedMemory& TimeManager::GetSharedMemory() { + return impl->shared_memory; +} + +const SharedMemory& TimeManager::GetSharedMemory() const { + return impl->shared_memory; +} + +void TimeManager::UpdateLocalSystemClockTime(s64 posix_time) { + impl->UpdateLocalSystemClockTime(system, posix_time); +} + +void TimeManager::SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, + u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file) { + impl->SetupTimeZoneManager(location_name, time_zone_updated_time_point, + total_location_name_count, time_zone_rule_version, vfs_file); +} + +/*static*/ s64 TimeManager::GetExternalTimeZoneOffset() { + // With "auto" timezone setting, we use the external system's timezone offset + if (Settings::GetTimeZoneString() == "auto") { + return Common::TimeZone::GetCurrentOffsetSeconds().count(); + } + return 0; } } // namespace Service::Time diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h index 8e65f0d22..993c7c288 100644 --- a/src/core/hle/service/time/time_manager.h +++ b/src/core/hle/service/time/time_manager.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "common/time_zone.h" #include "core/file_sys/vfs_types.h" #include "core/hle/service/time/clock_types.h" #include "core/hle/service/time/ephemeral_network_system_clock_core.h" @@ -32,86 +33,46 @@ public: explicit TimeManager(Core::System& system); ~TimeManager(); - Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() { - return standard_steady_clock_core; - } + void Initialize(); - const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const { - return standard_steady_clock_core; - } + Clock::StandardSteadyClockCore& GetStandardSteadyClockCore(); - Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() { - return standard_local_system_clock_core; - } + const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const; - const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const { - return standard_local_system_clock_core; - } + Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore(); - Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() { - return standard_network_system_clock_core; - } + const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const; - const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const { - return standard_network_system_clock_core; - } + Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore(); - Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() { - return standard_user_system_clock_core; - } + const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const; - const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const { - return standard_user_system_clock_core; - } + Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore(); - TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() { - return time_zone_content_manager; - } + const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const; - const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const { - return time_zone_content_manager; - } + TimeZone::TimeZoneContentManager& GetTimeZoneContentManager(); - SharedMemory& GetSharedMemory() { - return shared_memory; - } + const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const; - const SharedMemory& GetSharedMemory() const { - return shared_memory; - } + void UpdateLocalSystemClockTime(s64 posix_time); + + SharedMemory& GetSharedMemory(); + + const SharedMemory& GetSharedMemory() const; void SetupTimeZoneManager(std::string location_name, Clock::SteadyClockTimePoint time_zone_updated_time_point, std::size_t total_location_name_count, u128 time_zone_rule_version, FileSys::VirtualFile& vfs_file); + static s64 GetExternalTimeZoneOffset(); + private: - void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, - Clock::TimeSpanType setup_value, - Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected); - void SetupStandardLocalSystemClock(Core::System& system, - Clock::SystemClockContext clock_context, s64 posix_time); - void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, - Clock::TimeSpanType sufficient_accuracy); - void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled, - Clock::SteadyClockTimePoint steady_clock_time_point); - void SetupEphemeralNetworkSystemClock(); - - SharedMemory shared_memory; - - Clock::StandardSteadyClockCore standard_steady_clock_core; - Clock::TickBasedSteadyClockCore tick_based_steady_clock_core; - Clock::StandardLocalSystemClockCore standard_local_system_clock_core; - Clock::StandardNetworkSystemClockCore standard_network_system_clock_core; - Clock::StandardUserSystemClockCore standard_user_system_clock_core; - Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core; - - std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer; - std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer; - std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter> - ephemeral_network_system_clock_context_writer; - - TimeZone::TimeZoneContentManager time_zone_content_manager; + Core::System& system; + + struct Impl; + std::unique_ptr<Impl> impl; }; } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h index 5976b2046..e0c3e63da 100644 --- a/src/core/hle/service/time/time_sharedmemory.h +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -6,8 +6,8 @@ #include "common/common_types.h" #include "common/uuid.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/shared_memory.h" -#include "core/hle/kernel/thread.h" #include "core/hle/service/time/clock_types.h" namespace Service::Time { diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp index 320672add..4177d0a41 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -68,9 +68,10 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) { return location_name_cache; } -TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system) - : system{system}, location_name_cache{BuildLocationNameCache(system)} { +TimeZoneContentManager::TimeZoneContentManager(Core::System& system) + : system{system}, location_name_cache{BuildLocationNameCache(system)} {} +void TimeZoneContentManager::Initialize(TimeManager& time_manager) { std::string location_name; const auto timezone_setting = Settings::GetTimeZoneString(); if (timezone_setting == "auto" || timezone_setting == "default") { diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h index 4f302c3b9..52dd1a020 100644 --- a/src/core/hle/service/time/time_zone_content_manager.h +++ b/src/core/hle/service/time/time_zone_content_manager.h @@ -21,7 +21,9 @@ namespace Service::Time::TimeZone { class TimeZoneContentManager final { public: - TimeZoneContentManager(TimeManager& time_manager, Core::System& system); + explicit TimeZoneContentManager(Core::System& system); + + void Initialize(TimeManager& time_manager); TimeZoneManager& GetTimeZoneManager() { return time_zone_manager; diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index 69152d0ac..bdf0439f2 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -820,7 +820,10 @@ static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, Calend const ResultCode result{ ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)}; calendar.time.year = static_cast<s16>(calendar_time.year); - calendar.time.month = calendar_time.month + 1; // Internal impl. uses 0-indexed month + + // Internal impl. uses 0-indexed month + calendar.time.month = static_cast<s8>(calendar_time.month + 1); + calendar.time.day = calendar_time.day; calendar.time.hour = calendar_time.hour; calendar.time.minute = calendar_time.minute; @@ -872,13 +875,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time, s64& posix_time) const { posix_time = 0; - CalendarTimeInternal internal_time{}; - internal_time.year = calendar_time.year; - internal_time.month = calendar_time.month - 1; // Internal impl. uses 0-indexed month - internal_time.day = calendar_time.day; - internal_time.hour = calendar_time.hour; - internal_time.minute = calendar_time.minute; - internal_time.second = calendar_time.second; + CalendarTimeInternal internal_time{ + .year = calendar_time.year, + // Internal impl. uses 0-indexed month + .month = static_cast<s8>(calendar_time.month - 1), + .day = calendar_time.day, + .hour = calendar_time.hour, + .minute = calendar_time.minute, + .second = calendar_time.second, + }; s32 hour{internal_time.hour}; s32 minute{internal_time.minute}; diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index ff3a10b3e..25cecbc83 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -10,8 +10,9 @@ namespace Service::Time { -ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_content_manager) - : ServiceFramework("ITimeZoneService"), time_zone_content_manager{time_zone_content_manager} { +ITimeZoneService ::ITimeZoneService(Core::System& system_, + TimeZone::TimeZoneContentManager& time_zone_manager_) + : ServiceFramework{system_, "ITimeZoneService"}, time_zone_content_manager{time_zone_manager_} { static const FunctionInfo functions[] = { {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, {1, nullptr, "SetDeviceLocationName"}, diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h index cb495748b..2c9b97603 100644 --- a/src/core/hle/service/time/time_zone_service.h +++ b/src/core/hle/service/time/time_zone_service.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Time { namespace TimeZone { @@ -14,7 +18,8 @@ class TimeZoneContentManager; class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { public: - explicit ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_manager); + explicit ITimeZoneService(Core::System& system_, + TimeZone::TimeZoneContentManager& time_zone_manager_); private: void GetDeviceLocationName(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h index 9be15b53e..4a57e036d 100644 --- a/src/core/hle/service/time/time_zone_types.h +++ b/src/core/hle/service/time/time_zone_types.h @@ -45,23 +45,23 @@ static_assert(sizeof(TimeZoneRule) == 0x4000, "TimeZoneRule is incorrect size"); /// https://switchbrew.org/wiki/Glue_services#CalendarAdditionalInfo struct CalendarAdditionalInfo { - u32 day_of_week{}; - u32 day_of_year{}; + u32 day_of_week; + u32 day_of_year; std::array<char, 8> timezone_name; - u32 is_dst{}; - s32 gmt_offset{}; + u32 is_dst; + s32 gmt_offset; }; static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo is incorrect size"); /// https://switchbrew.org/wiki/Glue_services#CalendarTime struct CalendarTime { - s16 year{}; - s8 month{}; - s8 day{}; - s8 hour{}; - s8 minute{}; - s8 second{}; - INSERT_PADDING_BYTES(1); + s16 year; + s8 month; + s8 day; + s8 hour; + s8 minute; + s8 second; + INSERT_PADDING_BYTES_NOINIT(1); }; static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime is incorrect size"); diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp index d033f8603..579de83e4 100644 --- a/src/core/hle/service/usb/usb.cpp +++ b/src/core/hle/service/usb/usb.cpp @@ -15,7 +15,7 @@ namespace Service::USB { class IDsInterface final : public ServiceFramework<IDsInterface> { public: - explicit IDsInterface() : ServiceFramework{"IDsInterface"} { + explicit IDsInterface(Core::System& system_) : ServiceFramework{system_, "IDsInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetDsEndpoint"}, @@ -40,7 +40,7 @@ public: class USB_DS final : public ServiceFramework<USB_DS> { public: - explicit USB_DS() : ServiceFramework{"usb:ds"} { + explicit USB_DS(Core::System& system_) : ServiceFramework{system_, "usb:ds"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "BindDevice"}, @@ -65,7 +65,8 @@ public: class IClientEpSession final : public ServiceFramework<IClientEpSession> { public: - explicit IClientEpSession() : ServiceFramework{"IClientEpSession"} { + explicit IClientEpSession(Core::System& system_) + : ServiceFramework{system_, "IClientEpSession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Open"}, @@ -86,7 +87,8 @@ public: class IClientIfSession final : public ServiceFramework<IClientIfSession> { public: - explicit IClientIfSession() : ServiceFramework{"IClientIfSession"} { + explicit IClientIfSession(Core::System& system_) + : ServiceFramework{system_, "IClientIfSession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown0"}, @@ -108,7 +110,7 @@ public: class USB_HS final : public ServiceFramework<USB_HS> { public: - explicit USB_HS() : ServiceFramework{"usb:hs"} { + explicit USB_HS(Core::System& system_) : ServiceFramework{system_, "usb:hs"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "BindClientProcess"}, @@ -129,7 +131,7 @@ public: class IPdSession final : public ServiceFramework<IPdSession> { public: - explicit IPdSession() : ServiceFramework{"IPdSession"} { + explicit IPdSession(Core::System& system_) : ServiceFramework{system_, "IPdSession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "BindNoticeEvent"}, @@ -148,7 +150,7 @@ public: class USB_PD final : public ServiceFramework<USB_PD> { public: - explicit USB_PD() : ServiceFramework{"usb:pd"} { + explicit USB_PD(Core::System& system_) : ServiceFramework{system_, "usb:pd"} { // clang-format off static const FunctionInfo functions[] = { {0, &USB_PD::GetPdSession, "GetPdSession"}, @@ -164,13 +166,14 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IPdSession>(); + rb.PushIpcInterface<IPdSession>(system); } }; class IPdCradleSession final : public ServiceFramework<IPdCradleSession> { public: - explicit IPdCradleSession() : ServiceFramework{"IPdCradleSession"} { + explicit IPdCradleSession(Core::System& system_) + : ServiceFramework{system_, "IPdCradleSession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "VdmUserWrite"}, @@ -191,7 +194,7 @@ public: class USB_PD_C final : public ServiceFramework<USB_PD_C> { public: - explicit USB_PD_C() : ServiceFramework{"usb:pd:c"} { + explicit USB_PD_C(Core::System& system_) : ServiceFramework{system_, "usb:pd:c"} { // clang-format off static const FunctionInfo functions[] = { {0, &USB_PD_C::GetPdCradleSession, "GetPdCradleSession"}, @@ -205,7 +208,7 @@ private: void GetPdCradleSession(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IPdCradleSession>(); + rb.PushIpcInterface<IPdCradleSession>(system); LOG_DEBUG(Service_USB, "called"); } @@ -213,7 +216,7 @@ private: class USB_PM final : public ServiceFramework<USB_PM> { public: - explicit USB_PM() : ServiceFramework{"usb:pm"} { + explicit USB_PM(Core::System& system_) : ServiceFramework{system_, "usb:pm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown0"}, @@ -229,12 +232,12 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<USB_DS>()->InstallAsService(sm); - std::make_shared<USB_HS>()->InstallAsService(sm); - std::make_shared<USB_PD>()->InstallAsService(sm); - std::make_shared<USB_PD_C>()->InstallAsService(sm); - std::make_shared<USB_PM>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<USB_DS>(system)->InstallAsService(sm); + std::make_shared<USB_HS>(system)->InstallAsService(sm); + std::make_shared<USB_PD>(system)->InstallAsService(sm); + std::make_shared<USB_PD_C>(system)->InstallAsService(sm); + std::make_shared<USB_PM>(system)->InstallAsService(sm); } } // namespace Service::USB diff --git a/src/core/hle/service/usb/usb.h b/src/core/hle/service/usb/usb.h index 970a11fe8..fc366df34 100644 --- a/src/core/hle/service/usb/usb.h +++ b/src/core/hle/service/usb/usb.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::USB { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::USB diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index 5a202ac81..7f42aa4a0 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -9,7 +9,9 @@ #include "common/assert.h" #include "core/core.h" -#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/vi/display/vi_display.h" #include "core/hle/service/vi/layer/vi_layer.h" @@ -17,8 +19,8 @@ namespace Service::VI { Display::Display(u64 id, std::string name, Core::System& system) : id{id}, name{std::move(name)} { auto& kernel = system.Kernel(); - vsync_event = - Kernel::WritableEvent::CreateEventPair(kernel, fmt::format("Display VSync Event {}", id)); + vsync_event = Kernel::KEvent::Create(kernel, fmt::format("Display VSync Event {}", id)); + vsync_event->Initialize(); } Display::~Display() = default; @@ -31,12 +33,12 @@ const Layer& Display::GetLayer(std::size_t index) const { return *layers.at(index); } -std::shared_ptr<Kernel::ReadableEvent> Display::GetVSyncEvent() const { - return vsync_event.readable; +std::shared_ptr<Kernel::KReadableEvent> Display::GetVSyncEvent() const { + return vsync_event->GetReadableEvent(); } void Display::SignalVSyncEvent() { - vsync_event.writable->Signal(); + vsync_event->GetWritableEvent()->Signal(); } void Display::CreateLayer(u64 id, NVFlinger::BufferQueue& buffer_queue) { diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index a3855d8cd..931c898f6 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -9,7 +9,10 @@ #include <vector> #include "common/common_types.h" -#include "core/hle/kernel/writable_event.h" + +namespace Kernel { +class KEvent; +} namespace Service::NVFlinger { class BufferQueue; @@ -58,7 +61,7 @@ public: const Layer& GetLayer(std::size_t index) const; /// Gets the readable vsync event. - std::shared_ptr<Kernel::ReadableEvent> GetVSyncEvent() const; + std::shared_ptr<Kernel::KReadableEvent> GetVSyncEvent() const; /// Signals the internal vsync event. void SignalVSyncEvent(); @@ -99,7 +102,7 @@ private: std::string name; std::vector<std::shared_ptr<Layer>> layers; - Kernel::EventPair vsync_event; + std::shared_ptr<Kernel::KEvent> vsync_event; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 480d34725..8661895ae 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -18,9 +18,9 @@ #include "common/swap.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/writable_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/k_writable_event.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" @@ -159,7 +159,7 @@ public: header.data_size = static_cast<u32_le>(write_index - sizeof(Header)); header.data_offset = sizeof(Header); header.objects_size = 4; - header.objects_offset = sizeof(Header) + header.data_size; + header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size); std::memcpy(buffer.data(), &header, sizeof(Header)); return buffer; @@ -215,10 +215,9 @@ public: explicit IGBPConnectRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPConnectRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } @@ -279,23 +278,28 @@ public: : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPSetPreallocatedBufferRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); data = Read<Data>(); - buffer = Read<NVFlinger::IGBPBuffer>(); + if (data.contains_object != 0) { + buffer_container = Read<BufferContainer>(); + } } struct Data { u32_le slot; - INSERT_PADDING_WORDS(1); + u32_le contains_object; + }; + + struct BufferContainer { u32_le graphic_buffer_length; INSERT_PADDING_WORDS(1); + NVFlinger::IGBPBuffer buffer{}; }; - Data data; - NVFlinger::IGBPBuffer buffer; + Data data{}; + BufferContainer buffer_container{}; }; class IGBPSetPreallocatedBufferResponseParcel : public Parcel { @@ -306,15 +310,40 @@ protected: } }; +class IGBPCancelBufferRequestParcel : public Parcel { +public: + explicit IGBPCancelBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { + Deserialize(); + } + + void DeserializeData() override { + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); + data = Read<Data>(); + } + + struct Data { + u32_le slot; + Service::Nvidia::MultiFence multi_fence; + }; + + Data data; +}; + +class IGBPCancelBufferResponseParcel : public Parcel { +protected: + void SerializeData() override { + Write<u32>(0); // Success + } +}; + class IGBPDequeueBufferRequestParcel : public Parcel { public: explicit IGBPDequeueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPDequeueBufferRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } @@ -333,7 +362,6 @@ class IGBPDequeueBufferResponseParcel : public Parcel { public: explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence) : slot(slot), multi_fence(multi_fence) {} - ~IGBPDequeueBufferResponseParcel() override = default; protected: void SerializeData() override { @@ -352,10 +380,9 @@ public: explicit IGBPRequestBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPRequestBufferRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); slot = Read<u32_le>(); } @@ -384,10 +411,9 @@ public: explicit IGBPQueueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPQueueBufferRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } @@ -447,10 +473,9 @@ public: explicit IGBPQueryRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) { Deserialize(); } - ~IGBPQueryRequestParcel() override = default; void DeserializeData() override { - std::u16string token = ReadInterfaceToken(); + [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); type = Read<u32_le>(); } @@ -473,8 +498,8 @@ private: class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> { public: - explicit IHOSBinderDriver(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("IHOSBinderDriver"), nv_flinger(std::move(nv_flinger)) { + explicit IHOSBinderDriver(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "IHOSBinderDriver"}, nv_flinger(nv_flinger_) { static const FunctionInfo functions[] = { {0, &IHOSBinderDriver::TransactParcel, "TransactParcel"}, {1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"}, @@ -509,10 +534,9 @@ private: const u32 flags = rp.Pop<u32>(); LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id, - static_cast<u32>(transaction), flags); + transaction, flags); - const auto guard = nv_flinger->Lock(); - auto& buffer_queue = nv_flinger->FindBufferQueue(id); + auto& buffer_queue = *nv_flinger.FindBufferQueue(id); switch (transaction) { case TransactionId::Connect: { @@ -522,13 +546,16 @@ private: Settings::values.resolution_factor.GetValue()), static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * Settings::values.resolution_factor.GetValue())}; + + buffer_queue.Connect(); + ctx.WriteBuffer(response.Serialize()); break; } case TransactionId::SetPreallocatedBuffer: { IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()}; - buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer); + buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer); IGBPSetPreallocatedBufferResponseParcel response{}; ctx.WriteBuffer(response.Serialize()); @@ -538,40 +565,25 @@ private: IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; const u32 width{request.data.width}; const u32 height{request.data.height}; - auto result = buffer_queue.DequeueBuffer(width, height); - - if (result) { - // Buffer is available - IGBPDequeueBufferResponseParcel response{result->first, *result->second}; - ctx.WriteBuffer(response.Serialize()); - } else { - // Wait the current thread until a buffer becomes available - ctx.SleepClientThread( - "IHOSBinderDriver::DequeueBuffer", UINT64_MAX, - [=, this](std::shared_ptr<Kernel::Thread> thread, - Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { - // Repeat TransactParcel DequeueBuffer when a buffer is available - const auto guard = nv_flinger->Lock(); - auto& buffer_queue = nv_flinger->FindBufferQueue(id); - auto result = buffer_queue.DequeueBuffer(width, height); - ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer."); - - IGBPDequeueBufferResponseParcel response{result->first, *result->second}; - ctx.WriteBuffer(response.Serialize()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - }, - buffer_queue.GetWritableBufferWaitEvent()); - } + + do { + if (auto result = buffer_queue.DequeueBuffer(width, height); result) { + // Buffer is available + IGBPDequeueBufferResponseParcel response{result->first, *result->second}; + ctx.WriteBuffer(response.Serialize()); + break; + } + } while (buffer_queue.IsConnected()); + break; } case TransactionId::RequestBuffer: { IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; auto& buffer = buffer_queue.RequestBuffer(request.slot); - IGBPRequestBufferResponseParcel response{buffer}; ctx.WriteBuffer(response.Serialize()); + break; } case TransactionId::QueueBuffer: { @@ -596,7 +608,12 @@ private: break; } case TransactionId::CancelBuffer: { - LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); + IGBPCancelBufferRequestParcel request{ctx.ReadBuffer()}; + + buffer_queue.CancelBuffer(request.data.slot, request.data.multi_fence); + + IGBPCancelBufferResponseParcel response{}; + ctx.WriteBuffer(response.Serialize()); break; } case TransactionId::Disconnect: { @@ -652,7 +669,7 @@ private: LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); - const auto& buffer_queue = nv_flinger->FindBufferQueue(id); + const auto& buffer_queue = *nv_flinger.FindBufferQueue(id); // TODO(Subv): Find out what this actually is. IPC::ResponseBuilder rb{ctx, 2, 1}; @@ -660,12 +677,13 @@ private: rb.PushCopyObjects(buffer_queue.GetBufferWaitEvent()); } - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; -}; // namespace VI + NVFlinger::NVFlinger& nv_flinger; +}; class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { public: - explicit ISystemDisplayService() : ServiceFramework("ISystemDisplayService") { + explicit ISystemDisplayService(Core::System& system_) + : ServiceFramework{system_, "ISystemDisplayService"} { static const FunctionInfo functions[] = { {1200, nullptr, "GetZOrderCountMin"}, {1202, nullptr, "GetZOrderCountMax"}, @@ -747,7 +765,7 @@ private: IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * @@ -766,8 +784,8 @@ private: class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { public: - explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) { + explicit IManagerDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "IManagerDisplayService"}, nv_flinger{nv_flinger_} { // clang-format off static const FunctionInfo functions[] = { {200, nullptr, "AllocateProcessHeapBlock"}, @@ -869,7 +887,7 @@ private: "(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}", unknown, display, aruid); - const auto layer_id = nv_flinger->CreateLayer(display); + const auto layer_id = nv_flinger.CreateLayer(display); if (!layer_id) { LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display); IPC::ResponseBuilder rb{ctx, 2}; @@ -906,12 +924,12 @@ private: rb.Push(RESULT_SUCCESS); } - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + NVFlinger::NVFlinger& nv_flinger; }; class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { public: - explicit IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); private: enum class ConvertedScaleMode : u64 { @@ -935,7 +953,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger); } void GetSystemDisplayService(Kernel::HLERequestContext& ctx) { @@ -943,7 +961,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemDisplayService>(); + rb.PushIpcInterface<ISystemDisplayService>(system); } void GetManagerDisplayService(Kernel::HLERequestContext& ctx) { @@ -951,7 +969,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IManagerDisplayService>(nv_flinger); + rb.PushIpcInterface<IManagerDisplayService>(system, nv_flinger); } void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) { @@ -959,7 +977,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger); } void OpenDisplay(Kernel::HLERequestContext& ctx) { @@ -986,7 +1004,7 @@ private: ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); - const auto display_id = nv_flinger->OpenDisplay(name); + const auto display_id = nv_flinger.OpenDisplay(name); if (!display_id) { LOG_ERROR(Service_VI, "Display not found! display_name={}", name); IPC::ResponseBuilder rb{ctx, 2}; @@ -1041,8 +1059,8 @@ private: const auto scaling_mode = rp.PopEnum<NintendoScaleMode>(); const u64 unknown = rp.Pop<u64>(); - LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}", - static_cast<u32>(scaling_mode), unknown); + LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}", scaling_mode, + unknown); IPC::ResponseBuilder rb{ctx, 2}; @@ -1086,7 +1104,7 @@ private: LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}, aruid=0x{:016X}", layer_id, aruid); - const auto display_id = nv_flinger->OpenDisplay(display_name); + const auto display_id = nv_flinger.OpenDisplay(display_name); if (!display_id) { LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -1094,7 +1112,7 @@ private: return; } - const auto buffer_queue_id = nv_flinger->FindBufferQueueId(*display_id, layer_id); + const auto buffer_queue_id = nv_flinger.FindBufferQueueId(*display_id, layer_id); if (!buffer_queue_id) { LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -1114,7 +1132,7 @@ private: LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id); - nv_flinger->CloseLayer(layer_id); + nv_flinger.CloseLayer(layer_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1130,7 +1148,7 @@ private: // TODO(Subv): What's the difference between a Stray and a Managed layer? - const auto layer_id = nv_flinger->CreateLayer(display_id); + const auto layer_id = nv_flinger.CreateLayer(display_id); if (!layer_id) { LOG_ERROR(Service_VI, "Layer not found! layer_id={}", *layer_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -1138,7 +1156,7 @@ private: return; } - const auto buffer_queue_id = nv_flinger->FindBufferQueueId(display_id, *layer_id); + const auto buffer_queue_id = nv_flinger.FindBufferQueueId(display_id, *layer_id); if (!buffer_queue_id) { LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -1169,7 +1187,7 @@ private: LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id); - const auto vsync_event = nv_flinger->FindVsyncEvent(display_id); + const auto vsync_event = nv_flinger.FindVsyncEvent(display_id); if (!vsync_event) { LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -1185,7 +1203,7 @@ private: void ConvertScalingMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto mode = rp.PopEnum<NintendoScaleMode>(); - LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode)); + LOG_DEBUG(Service_VI, "called mode={}", mode); const auto converted_mode = ConvertScalingModeImpl(mode); @@ -1205,8 +1223,8 @@ private: const auto height = rp.Pop<u64>(); LOG_DEBUG(Service_VI, "called width={}, height={}", width, height); - constexpr std::size_t base_size = 0x20000; - constexpr std::size_t alignment = 0x1000; + constexpr u64 base_size = 0x20000; + constexpr u64 alignment = 0x1000; const auto texture_size = width * height * 4; const auto out_size = (texture_size + base_size - 1) / base_size * base_size; @@ -1234,12 +1252,12 @@ private: } } - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + NVFlinger::NVFlinger& nv_flinger; }; -IApplicationDisplayService::IApplicationDisplayService( - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework("IApplicationDisplayService"), nv_flinger(std::move(nv_flinger)) { +IApplicationDisplayService::IApplicationDisplayService(Core::System& system_, + NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_} { static const FunctionInfo functions[] = { {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"}, {101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"}, @@ -1280,14 +1298,13 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) { return false; } -void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, - Permission permission) { +void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system, + NVFlinger::NVFlinger& nv_flinger, Permission permission) { IPC::RequestParser rp{ctx}; const auto policy = rp.PopEnum<Policy>(); if (!IsValidServiceAccess(permission, policy)) { - LOG_ERROR(Service_VI, "Permission denied for policy {}", static_cast<u32>(policy)); + LOG_ERROR(Service_VI, "Permission denied for policy {}", policy); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_PERMISSION_DENIED); return; @@ -1295,14 +1312,14 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger)); + rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger); } -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) { - std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager); - std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system, + NVFlinger::NVFlinger& nv_flinger) { + std::make_shared<VI_M>(system, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_S>(system, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_U>(system, nv_flinger)->InstallAsService(service_manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index 6b66f8b81..eec531d54 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -7,6 +7,10 @@ #include <memory> #include "common/common_types.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -43,12 +47,12 @@ enum class Policy { }; namespace detail { -void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission); +void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system, + NVFlinger::NVFlinger& nv_flinger, Permission permission); } // namespace detail /// Registers all VI services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system, + NVFlinger::NVFlinger& nv_flinger); } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 06070087f..87db1c416 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -8,8 +8,8 @@ namespace Service::VI { -VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} { +VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_} { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +22,7 @@ VI_M::~VI_M() = default; void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::Manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index 290e06689..d79c41beb 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -18,13 +22,13 @@ namespace Service::VI { class VI_M final : public ServiceFramework<VI_M> { public: - explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); ~VI_M() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + NVFlinger::NVFlinger& nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 57c596cc4..5cd22f7df 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -8,8 +8,8 @@ namespace Service::VI { -VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} { +VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_} { static const FunctionInfo functions[] = { {1, &VI_S::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +22,7 @@ VI_S::~VI_S() = default; void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::System); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index 47804dc0b..5f1f8f290 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -18,13 +22,13 @@ namespace Service::VI { class VI_S final : public ServiceFramework<VI_S> { public: - explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); ~VI_S() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + NVFlinger::NVFlinger& nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index 6b7329345..0079d51f0 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -8,8 +8,8 @@ namespace Service::VI { -VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) - : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} { +VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_) + : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_} { static const FunctionInfo functions[] = { {0, &VI_U::GetDisplayService, "GetDisplayService"}, {1, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -22,7 +22,7 @@ VI_U::~VI_U() = default; void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); - detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User); + detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::User); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index 19bdb73b0..8e3885c73 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -18,13 +22,13 @@ namespace Service::VI { class VI_U final : public ServiceFramework<VI_U> { public: - explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_); ~VI_U() override; private: void GetDisplayService(Kernel::HLERequestContext& ctx); - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; + NVFlinger::NVFlinger& nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/wlan/wlan.cpp b/src/core/hle/service/wlan/wlan.cpp index 0260d7dcf..ddbf04069 100644 --- a/src/core/hle/service/wlan/wlan.cpp +++ b/src/core/hle/service/wlan/wlan.cpp @@ -12,7 +12,7 @@ namespace Service::WLAN { class WLANInfra final : public ServiceFramework<WLANInfra> { public: - explicit WLANInfra() : ServiceFramework{"wlan:inf"} { + explicit WLANInfra(Core::System& system_) : ServiceFramework{system_, "wlan:inf"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenMode"}, @@ -55,7 +55,7 @@ public: class WLANLocal final : public ServiceFramework<WLANLocal> { public: - explicit WLANLocal() : ServiceFramework{"wlan:lcl"} { + explicit WLANLocal(Core::System& system_) : ServiceFramework{system_, "wlan:lcl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown0"}, @@ -120,7 +120,7 @@ public: class WLANLocalGetFrame final : public ServiceFramework<WLANLocalGetFrame> { public: - explicit WLANLocalGetFrame() : ServiceFramework{"wlan:lg"} { + explicit WLANLocalGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:lg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown"}, @@ -133,7 +133,7 @@ public: class WLANSocketGetFrame final : public ServiceFramework<WLANSocketGetFrame> { public: - explicit WLANSocketGetFrame() : ServiceFramework{"wlan:sg"} { + explicit WLANSocketGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:sg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown"}, @@ -146,7 +146,7 @@ public: class WLANSocketManager final : public ServiceFramework<WLANSocketManager> { public: - explicit WLANSocketManager() : ServiceFramework{"wlan:soc"} { + explicit WLANSocketManager(Core::System& system_) : ServiceFramework{system_, "wlan:soc"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Unknown0"}, @@ -169,12 +169,12 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<WLANInfra>()->InstallAsService(sm); - std::make_shared<WLANLocal>()->InstallAsService(sm); - std::make_shared<WLANLocalGetFrame>()->InstallAsService(sm); - std::make_shared<WLANSocketGetFrame>()->InstallAsService(sm); - std::make_shared<WLANSocketManager>()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared<WLANInfra>(system)->InstallAsService(sm); + std::make_shared<WLANLocal>(system)->InstallAsService(sm); + std::make_shared<WLANLocalGetFrame>(system)->InstallAsService(sm); + std::make_shared<WLANSocketGetFrame>(system)->InstallAsService(sm); + std::make_shared<WLANSocketManager>(system)->InstallAsService(sm); } } // namespace Service::WLAN diff --git a/src/core/hle/service/wlan/wlan.h b/src/core/hle/service/wlan/wlan.h index 054ea928a..3899eedbb 100644 --- a/src/core/hle/service/wlan/wlan.h +++ b/src/core/hle/service/wlan/wlan.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::WLAN { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::WLAN diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 134e83412..79ebf11de 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -12,7 +12,6 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/romfs_factory.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" @@ -89,7 +88,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua } AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load( - Kernel::Process& process) { + Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -114,7 +113,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } if (override_update) { - const FileSys::PatchManager patch_manager(metadata.GetTitleID()); + const FileSys::PatchManager patch_manager( + metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider()); dir = patch_manager.PatchExeFS(dir); } @@ -141,9 +141,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect continue; } - const bool should_pass_arguments{std::strcmp(module, "rtld") == 0}; - const auto tentative_next_load_addr{AppLoader_NSO::LoadModule( - process, *module_file, code_size, should_pass_arguments, false)}; + const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; + const auto tentative_next_load_addr = AppLoader_NSO::LoadModule( + process, system, *module_file, code_size, should_pass_arguments, false); if (!tentative_next_load_addr) { return {ResultStatus::ErrorLoadingNSO, {}}; } @@ -160,7 +160,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect modules.clear(); const VAddr base_address{process.PageTable().GetCodeRegionStart()}; VAddr next_load_addr{base_address}; - const FileSys::PatchManager pm{metadata.GetTitleID()}; + const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), + system.GetContentProvider()}; for (const auto& module : static_modules) { const FileSys::VirtualFile module_file{dir->GetFile(module)}; if (!module_file) { @@ -168,9 +169,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } const VAddr load_addr{next_load_addr}; - const bool should_pass_arguments{std::strcmp(module, "rtld") == 0}; - const auto tentative_next_load_addr{AppLoader_NSO::LoadModule( - process, *module_file, load_addr, should_pass_arguments, true, pm)}; + const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; + const auto tentative_next_load_addr = AppLoader_NSO::LoadModule( + process, system, *module_file, load_addr, should_pass_arguments, true, pm); if (!tentative_next_load_addr) { return {ResultStatus::ErrorLoadingNSO, {}}; } @@ -178,8 +179,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect next_load_addr = *tentative_next_load_addr; modules.insert_or_assign(load_addr, module); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); - // Register module with GDBStub - GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); } // Find the RomFS by searching for a ".romfs" file in this directory @@ -192,8 +191,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect // Register the RomFS if a ".romfs" file was found if (romfs_iter != files.end() && *romfs_iter != nullptr) { romfs = *romfs_iter; - Core::System::GetInstance().GetFileSystemController().RegisterRomFS( - std::make_unique<FileSys::RomFSFactory>(*this)); + system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>( + *this, system.GetContentProvider(), system.GetFileSystemController())); } is_loaded = true; diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 1c0a354a4..3c968580f 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -9,6 +9,10 @@ #include "core/file_sys/program_metadata.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace Loader { /** @@ -28,7 +32,7 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -37,7 +41,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 8f7615115..dca1fcb18 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -383,7 +383,8 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) { return FileType::Error; } -AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) { +AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process, + [[maybe_unused]] Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 7ef7770a6..2067932c7 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -8,6 +8,10 @@ #include "common/common_types.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace Loader { /// Loads an ELF/AXF file @@ -17,7 +21,7 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -26,7 +30,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; }; } // namespace Loader diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index 40fa03ad1..e162c4ff0 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -5,7 +5,6 @@ #include <cstring> #include "core/file_sys/kernel_executable.h" #include "core/file_sys/program_metadata.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" @@ -16,7 +15,7 @@ namespace Loader { namespace { constexpr u32 PageAlignSize(u32 size) { - return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK; + return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); } } // Anonymous namespace @@ -43,7 +42,8 @@ FileType AppLoader_KIP::GetFileType() const { : FileType::Error; } -AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { +AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process, + [[maybe_unused]] Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -90,8 +90,6 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize()); codeset.DataSegment().size += kip->GetBSSSize(); - GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size()); - codeset.memory = std::move(program_image); process.LoadModule(std::move(codeset), base_address); diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h index 12ca40269..14a85e295 100644 --- a/src/core/loader/kip.h +++ b/src/core/loader/kip.h @@ -6,6 +6,10 @@ #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace FileSys { class KIP; } @@ -19,14 +23,14 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); FileType GetFileType() const override; - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; private: std::unique_ptr<FileSys::KIP> kip; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 9bc3a8840..e4f5fd40c 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -10,6 +10,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" @@ -184,6 +185,10 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The INI file contains more than the maximum allowable number of KIP files.", }; +std::string GetResultStatusString(ResultStatus status) { + return RESULT_MESSAGES.at(static_cast<std::size_t>(status)); +} + std::ostream& operator<<(std::ostream& os, ResultStatus status) { os << RESULT_MESSAGES.at(static_cast<std::size_t>(status)); return os; @@ -194,15 +199,15 @@ AppLoader::~AppLoader() = default; /** * Get a loader for a file with a specific type - * @param file The file to load - * @param type The type of the file - * @param file the file to retrieve the loader for - * @param type the file type + * @param system The system context to use. + * @param file The file to retrieve the loader for + * @param type The file type + * @param program_index Specifies the index within the container of the program to launch. * @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type */ -static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) { +static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::VirtualFile file, + FileType type, std::size_t program_index) { switch (type) { - // Standard ELF file format. case FileType::ELF: return std::make_unique<AppLoader_ELF>(std::move(file)); @@ -221,7 +226,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT // NX XCI (nX Card Image) file format. case FileType::XCI: - return std::make_unique<AppLoader_XCI>(std::move(file)); + return std::make_unique<AppLoader_XCI>(std::move(file), system.GetFileSystemController(), + system.GetContentProvider(), program_index); // NX NAX (NintendoAesXts) file format. case FileType::NAX: @@ -229,7 +235,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT // NX NSP (Nintendo Submission Package) file format case FileType::NSP: - return std::make_unique<AppLoader_NSP>(std::move(file)); + return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(), + system.GetContentProvider(), program_index); // NX KIP (Kernel Internal Process) file format case FileType::KIP: @@ -244,20 +251,22 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT } } -std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) { +std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, + std::size_t program_index) { FileType type = IdentifyFile(file); - FileType filename_type = GuessFromFilename(file->GetName()); + const FileType filename_type = GuessFromFilename(file->GetName()); // Special case: 00 is either a NCA or NAX. if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) { LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName()); - if (FileType::Unknown == type) + if (FileType::Unknown == type) { type = filename_type; + } } LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type)); - return GetFileLoader(std::move(file), type); + return GetFileLoader(system, std::move(file), type, program_index); } } // namespace Loader diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 227ecc704..b2e5b13de 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -15,6 +15,10 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/vfs.h" +namespace Core { +class System; +} + namespace FileSys { class NACP; } // namespace FileSys @@ -131,6 +135,7 @@ enum class ResultStatus : u16 { ErrorINITooManyKIPs, }; +std::string GetResultStatusString(ResultStatus status); std::ostream& operator<<(std::ostream& os, ResultStatus status); /// Interface for loading an application @@ -154,9 +159,10 @@ public: /** * Load the application and return the created Process instance * @param process The newly created process. + * @param system The system that this process is being loaded under. * @return The status result of the operation. */ - virtual LoadResult Load(Kernel::Process& process) = 0; + virtual LoadResult Load(Kernel::Process& process, Core::System& system) = 0; /** * Get the code (typically .code section) of the application @@ -285,9 +291,14 @@ protected: /** * Identifies a bootable file and return a suitable loader - * @param file The bootable file - * @return the best loader for this file + * + * @param system The system context. + * @param file The bootable file. + * @param program_index Specifies the index within the container of the program to launch. + * + * @return the best loader for this file. */ -std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file); +std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, + std::size_t program_index = 0); } // namespace Loader diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index a152981a0..49028177b 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -41,7 +41,8 @@ FileType AppLoader_NAX::GetFileType() const { return IdentifyTypeImpl(*nax); } -AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) { +AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process, + [[maybe_unused]] Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -65,7 +66,7 @@ AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) { return {nca_status, {}}; } - const auto result = nca_loader->Load(process); + const auto result = nca_loader->Load(process, system); if (result.first != ResultStatus::Success) { return result; } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index eaec9bf58..a5b5e2ae1 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -8,10 +8,12 @@ #include "common/common_types.h" #include "core/loader/loader.h" -namespace FileSys { +namespace Core { +class System; +} +namespace FileSys { class NAX; - } // namespace FileSys namespace Loader { @@ -26,14 +28,14 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); FileType GetFileType() const override; - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; u64 ReadRomFSIVFCOffset() const override; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 5a0469978..fa694de37 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -31,7 +31,7 @@ FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { return FileType::Error; } -AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) { +AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -52,14 +52,14 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) { directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); - const auto load_result = directory_loader->Load(process); + const auto load_result = directory_loader->Load(process, system); if (load_result.first != ResultStatus::Success) { return load_result; } if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) { - Core::System::GetInstance().GetFileSystemController().RegisterRomFS( - std::make_unique<FileSys::RomFSFactory>(*this)); + system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>( + *this, system.GetContentProvider(), system.GetFileSystemController())); } is_loaded = true; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index e47dc0e47..918792800 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -8,6 +8,10 @@ #include "core/file_sys/vfs.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace FileSys { class NCA; } @@ -24,7 +28,7 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -33,7 +37,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; u64 ReadRomFSIVFCOffset() const override; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 906544bc9..f976d0a9c 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -14,8 +14,8 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_offset.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/code_set.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" @@ -127,7 +127,7 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) { } static constexpr u32 PageAlignSize(u32 size) { - return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK; + return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); } static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data, @@ -197,10 +197,6 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data, codeset.memory = std::move(program_image); process.LoadModule(std::move(codeset), process.PageTable().GetCodeRegionStart()); - // Register module with GDBStub - GDBStub::RegisterModule(name, process.PageTable().GetCodeRegionStart(), - process.PageTable().GetCodeRegionEnd()); - return true; } @@ -208,7 +204,7 @@ bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& fi return LoadNroImpl(process, file.ReadAllBytes(), file.GetName()); } -AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) { +AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -218,13 +214,13 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) { } if (romfs != nullptr) { - Core::System::GetInstance().GetFileSystemController().RegisterRomFS( - std::make_unique<FileSys::RomFSFactory>(*this)); + system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>( + *this, system.GetContentProvider(), system.GetFileSystemController())); } is_loaded = true; - return {ResultStatus::Success, - LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}}; + return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, + Core::Memory::DEFAULT_STACK_SIZE}}; } ResultStatus AppLoader_NRO::ReadIcon(std::vector<u8>& buffer) { diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 4593d48fb..a82b66221 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -10,6 +10,10 @@ #include "common/common_types.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace FileSys { class NACP; } @@ -28,7 +32,7 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -37,7 +41,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadProgramId(u64& out_program_id) override; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 575330a86..ea347ea83 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -14,8 +14,8 @@ #include "common/swap.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/code_set.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" #include "core/loader/nso.h" @@ -47,7 +47,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, } constexpr u32 PageAlignSize(u32 size) { - return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK; + return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); } } // Anonymous namespace @@ -71,21 +71,21 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { return FileType::NSO; } -std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, +std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::System& system, const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm) { if (file.GetSize() < sizeof(NSOHeader)) { - return {}; + return std::nullopt; } NSOHeader nso_header{}; if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { - return {}; + return std::nullopt; } if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { - return {}; + return std::nullopt; } // Build program image @@ -148,9 +148,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply cheats if they exist and the program has a valid title ID if (pm) { - auto& system = Core::System::GetInstance(); system.SetCurrentProcessBuildID(nso_header.build_id); - const auto cheats = pm->CreateCheatList(system, nso_header.build_id); + const auto cheats = pm->CreateCheatList(nso_header.build_id); if (!cheats.empty()) { system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); } @@ -160,13 +159,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, codeset.memory = std::move(program_image); process.LoadModule(std::move(codeset), load_base); - // Register module with GDBStub - GDBStub::RegisterModule(file.GetName(), load_base, load_base); - return load_base + image_size; } -AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { +AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -175,7 +171,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { // Load module const VAddr base_address = process.PageTable().GetCodeRegionStart(); - if (!LoadModule(process, *file, base_address, true, true)) { + if (!LoadModule(process, system, *file, base_address, true, true)) { return {ResultStatus::ErrorLoadingNSO, {}}; } @@ -183,8 +179,8 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); is_loaded = true; - return {ResultStatus::Success, - LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}}; + return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, + Core::Memory::DEFAULT_STACK_SIZE}}; } ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) { diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index b210830f0..3af461b5f 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -12,6 +12,10 @@ #include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace Kernel { class Process; } @@ -55,7 +59,7 @@ struct NSOHeader { static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); -constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; +constexpr u32 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; struct NSOArgumentHeader { u32_le allocated_size; @@ -71,7 +75,7 @@ public: /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -80,12 +84,12 @@ public: return IdentifyType(file); } - static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, - VAddr load_base, bool should_pass_arguments, - bool load_into_process, + static std::optional<VAddr> LoadModule(Kernel::Process& process, Core::System& system, + const FileSys::VfsFile& file, VAddr load_base, + bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm = {}); - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadNSOModules(Modules& modules) override; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 13950fc08..928f64c8c 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -21,26 +21,34 @@ namespace Loader { -AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) - : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)), +AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider, + std::size_t program_index) + : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file, program_index)), title_id(nsp->GetProgramTitleID()) { - if (nsp->GetStatus() != ResultStatus::Success) + if (nsp->GetStatus() != ResultStatus::Success) { return; + } if (nsp->IsExtractedType()) { secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); } else { const auto control_nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); - if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) { return; + } - std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); + std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] { + const FileSys::PatchManager pm{nsp->GetProgramTitleID(), fsc, content_provider}; + return pm.ParseControlNCA(*control_nca); + }(); - if (title_id == 0) + if (title_id == 0) { return; + } secondary_loader = std::make_unique<AppLoader_NCA>( nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program)); @@ -71,7 +79,7 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) { return FileType::Error; } -AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { +AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -99,15 +107,14 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; } - const auto result = secondary_loader->Load(process); + const auto result = secondary_loader->Load(process, system); if (result.first != ResultStatus::Success) { return result; } FileSys::VirtualFile update_raw; if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { - Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( - std::move(update_raw)); + system.GetFileSystemController().SetPackedUpdate(std::move(update_raw)); } is_loaded = true; diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 868b028d3..d48d87f2c 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -10,10 +10,15 @@ #include "core/loader/loader.h" namespace FileSys { +class ContentProvider; class NACP; class NSP; } // namespace FileSys +namespace Service::FileSystem { +class FileSystemController; +} + namespace Loader { class AppLoader_NCA; @@ -21,12 +26,15 @@ class AppLoader_NCA; /// Loads an XCI file class AppLoader_NSP final : public AppLoader { public: - explicit AppLoader_NSP(FileSys::VirtualFile file); + explicit AppLoader_NSP(FileSys::VirtualFile file, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider, + std::size_t program_index); ~AppLoader_NSP() override; /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -35,7 +43,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; u64 ReadRomFSIVFCOffset() const override; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 7186ad1ff..aaa250cea 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -20,18 +20,25 @@ namespace Loader { -AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) - : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), +AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider, + std::size_t program_index) + : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file, program_index)), nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) { - if (xci->GetStatus() != ResultStatus::Success) + if (xci->GetStatus() != ResultStatus::Success) { return; + } const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); - if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) { return; + } - std::tie(nacp_file, icon_file) = - FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca); + std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] { + const FileSys::PatchManager pm{xci->GetProgramTitleID(), fsc, content_provider}; + return pm.ParseControlNCA(*control_nca); + }(); } AppLoader_XCI::~AppLoader_XCI() = default; @@ -49,7 +56,7 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { return FileType::Error; } -AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) { +AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process, Core::System& system) { if (is_loaded) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } @@ -66,15 +73,14 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) { return {ResultStatus::ErrorMissingProductionKeyFile, {}}; } - const auto result = nca_loader->Load(process); + const auto result = nca_loader->Load(process, system); if (result.first != ResultStatus::Success) { return result; } FileSys::VirtualFile update_raw; if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { - Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( - std::move(update_raw)); + system.GetFileSystemController().SetPackedUpdate(std::move(update_raw)); } is_loaded = true; diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 618ae2f47..9f0ceb5ef 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -10,10 +10,15 @@ #include "core/loader/loader.h" namespace FileSys { +class ContentProvider; class NACP; class XCI; } // namespace FileSys +namespace Service::FileSystem { +class FileSystemController; +} + namespace Loader { class AppLoader_NCA; @@ -21,12 +26,15 @@ class AppLoader_NCA; /// Loads an XCI file class AppLoader_XCI final : public AppLoader { public: - explicit AppLoader_XCI(FileSys::VirtualFile file); + explicit AppLoader_XCI(FileSys::VirtualFile file, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider, + std::size_t program_index); ~AppLoader_XCI() override; /** * Returns the type of the file - * @param file std::shared_ptr<VfsFile> open file + * @param file open file * @return FileType found, or FileType::Error if this loader doesn't know it */ static FileType IdentifyType(const FileSys::VirtualFile& file); @@ -35,7 +43,7 @@ public: return IdentifyType(file); } - LoadResult Load(Kernel::Process& process) override; + LoadResult Load(Kernel::Process& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; u64 ReadRomFSIVFCOffset() const override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 86d17c6cb..11609682a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -44,44 +44,16 @@ struct Memory::Impl { MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); } - void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer mmio_handler) { - UNIMPLEMENTED(); - } - void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); } - void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook) { - UNIMPLEMENTED(); - } - - void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook) { - UNIMPLEMENTED(); - } - bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) const { const auto& page_table = process.PageTable().PageTableImpl(); - - const u8* const page_pointer = page_table.pointers[vaddr >> PAGE_BITS]; - if (page_pointer != nullptr) { - return true; - } - - if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory) { - return true; - } - - if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special) { - return false; - } - - return false; + const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType(); + return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; } bool IsValidVirtualAddress(VAddr vaddr) const { @@ -99,17 +71,15 @@ struct Memory::Impl { } u8* GetPointer(const VAddr vaddr) const { - u8* const page_pointer{current_page_table->pointers[vaddr >> PAGE_BITS]}; - if (page_pointer) { - return page_pointer + vaddr; + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { + return pointer + vaddr; } - - if (current_page_table->attributes[vaddr >> PAGE_BITS] == - Common::PageType::RasterizerCachedMemory) { + const auto type = Common::PageTable::PageInfo::ExtractType(raw_pointer); + if (type == Common::PageType::RasterizerCachedMemory) { return GetPointerFromRasterizerCachedMemory(vaddr); } - - return {}; + return nullptr; } u8 Read8(const VAddr addr) { @@ -120,9 +90,9 @@ struct Memory::Impl { if ((addr & 1) == 0) { return Read<u16_le>(addr); } else { - const u8 a{Read<u8>(addr)}; - const u8 b{Read<u8>(addr + sizeof(u8))}; - return (static_cast<u16>(b) << 8) | a; + const u32 a{Read<u8>(addr)}; + const u32 b{Read<u8>(addr + sizeof(u8))}; + return static_cast<u16>((b << 8) | a); } } @@ -130,9 +100,9 @@ struct Memory::Impl { if ((addr & 3) == 0) { return Read<u32_le>(addr); } else { - const u16 a{Read16(addr)}; - const u16 b{Read16(addr + sizeof(u16))}; - return (static_cast<u32>(b) << 16) | a; + const u32 a{Read16(addr)}; + const u32 b{Read16(addr + sizeof(u16))}; + return (b << 16) | a; } } @@ -221,7 +191,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -230,10 +201,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - - const u8* const src_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_buffer, src_ptr, copy_amount); break; } @@ -267,7 +236,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -276,10 +246,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - - const u8* const src_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_buffer, src_ptr, copy_amount); break; } @@ -319,7 +287,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -327,10 +296,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - - u8* const dest_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_ptr, src_buffer, copy_amount); break; } @@ -363,7 +330,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -371,10 +339,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - - u8* const dest_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_ptr, src_buffer, copy_amount); break; } @@ -413,7 +379,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -421,10 +388,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - - u8* dest_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS); std::memset(dest_ptr, 0, copy_amount); break; } @@ -460,7 +425,8 @@ struct Memory::Impl { std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (page_table.attributes[page_index]) { + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", @@ -469,9 +435,8 @@ struct Memory::Impl { break; } case Common::PageType::Memory: { - DEBUG_ASSERT(page_table.pointers[page_index]); - const u8* src_ptr = - page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); + DEBUG_ASSERT(pointer); + const u8* src_ptr = pointer + page_offset + (page_index << PAGE_BITS); WriteBlock(process, dest_addr, src_ptr, copy_amount); break; } @@ -501,16 +466,15 @@ struct Memory::Impl { if (vaddr == 0) { return; } - // Iterate over a contiguous CPU address space, which corresponds to the specified GPU // address space, marking the region as un/cached. The region is marked un/cached at a // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size // is different). This assumes the specified GPU address region is contiguous as well. - u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; - for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { - Common::PageType& page_type{current_page_table->attributes[vaddr >> PAGE_BITS]}; - + const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; + for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { + const Common::PageType page_type{ + current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; if (cached) { // Switch page type to cached if now cached switch (page_type) { @@ -519,8 +483,8 @@ struct Memory::Impl { // space, for example, a system module need not have a VRAM mapping. break; case Common::PageType::Memory: - page_type = Common::PageType::RasterizerCachedMemory; - current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; + current_page_table->pointers[vaddr >> PAGE_BITS].Store( + nullptr, Common::PageType::RasterizerCachedMemory); break; case Common::PageType::RasterizerCachedMemory: // There can be more than one GPU region mapped per CPU region, so it's common @@ -541,16 +505,16 @@ struct Memory::Impl { // that this area is already unmarked as cached. break; case Common::PageType::RasterizerCachedMemory: { - u8* pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)}; + u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)}; if (pointer == nullptr) { // It's possible that this function has been called while updating the // pagetable after unmapping a VMA. In that case the underlying VMA will no // longer exist, and we should just leave the pagetable entry blank. - page_type = Common::PageType::Unmapped; + current_page_table->pointers[vaddr >> PAGE_BITS].Store( + nullptr, Common::PageType::Unmapped); } else { - current_page_table->pointers[vaddr >> PAGE_BITS] = - pointer - (vaddr & ~PAGE_MASK); - page_type = Common::PageType::Memory; + current_page_table->pointers[vaddr >> PAGE_BITS].Store( + pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); } break; } @@ -567,7 +531,7 @@ struct Memory::Impl { * @param page_table The page table to use to perform the mapping. * @param base The base address to begin mapping at. * @param size The total size of the range in bytes. - * @param memory The memory to map. + * @param target The target address to begin mapping from. * @param type The page type to map the memory as. */ void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target, @@ -580,7 +544,7 @@ struct Memory::Impl { auto& gpu = system.GPU(); for (u64 i = 0; i < size; i++) { const auto page = base + i; - if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) { + if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) { gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE); } } @@ -595,20 +559,18 @@ struct Memory::Impl { "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE); while (base != end) { - page_table.attributes[base] = type; - page_table.pointers[base] = nullptr; + page_table.pointers[base].Store(nullptr, type); page_table.backing_addr[base] = 0; base += 1; } } else { while (base != end) { - page_table.pointers[base] = - system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS); - page_table.attributes[base] = type; + page_table.pointers[base].Store( + system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS), type); page_table.backing_addr[base] = target - (base << PAGE_BITS); - ASSERT_MSG(page_table.pointers[base], + ASSERT_MSG(page_table.pointers[base].Pointer(), "memory mapping base yield a nullptr within the table"); base += 1; @@ -630,16 +592,14 @@ struct Memory::Impl { */ template <typename T> T Read(const VAddr vaddr) { - const u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - if (page_pointer != nullptr) { - // NOTE: Avoid adding any extra logic to this fast-path block + // Avoid adding any extra logic to this fast-path block + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + if (const u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { T value; - std::memcpy(&value, &page_pointer[vaddr], sizeof(T)); + std::memcpy(&value, &pointer[vaddr], sizeof(T)); return value; } - - const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { + switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); return 0; @@ -667,20 +627,16 @@ struct Memory::Impl { * @tparam T The data type to write to memory. This type *must* be * trivially copyable, otherwise the behavior of this function * is undefined. - * - * @returns The instance of T write to the specified virtual address. */ template <typename T> void Write(const VAddr vaddr, const T data) { - u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - if (page_pointer != nullptr) { - // NOTE: Avoid adding any extra logic to this fast-path block - std::memcpy(&page_pointer[vaddr], &data, sizeof(T)); + // Avoid adding any extra logic to this fast-path block + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { + std::memcpy(&pointer[vaddr], &data, sizeof(T)); return; } - - const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { + switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, static_cast<u32>(data), vaddr); @@ -701,15 +657,13 @@ struct Memory::Impl { template <typename T> bool WriteExclusive(const VAddr vaddr, const T data, const T expected) { - u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - if (page_pointer != nullptr) { + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { // NOTE: Avoid adding any extra logic to this fast-path block - auto* pointer = reinterpret_cast<volatile T*>(&page_pointer[vaddr]); - return Common::AtomicCompareAndSwap(pointer, data, expected); + const auto volatile_pointer = reinterpret_cast<volatile T*>(&pointer[vaddr]); + return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); } - - const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { + switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, static_cast<u32>(data), vaddr); @@ -730,15 +684,13 @@ struct Memory::Impl { } bool WriteExclusive128(const VAddr vaddr, const u128 data, const u128 expected) { - u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - if (page_pointer != nullptr) { + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { // NOTE: Avoid adding any extra logic to this fast-path block - auto* pointer = reinterpret_cast<volatile u64*>(&page_pointer[vaddr]); - return Common::AtomicCompareAndSwap(pointer, data, expected); + const auto volatile_pointer = reinterpret_cast<volatile u64*>(&pointer[vaddr]); + return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); } - - const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { + switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}{:016X}", sizeof(data) * 8, static_cast<u64>(data[1]), static_cast<u64>(data[0]), vaddr); @@ -773,25 +725,10 @@ void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size impl->MapMemoryRegion(page_table, base, size, target); } -void Memory::MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer mmio_handler) { - impl->MapIoRegion(page_table, base, size, std::move(mmio_handler)); -} - void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { impl->UnmapRegion(page_table, base, size); } -void Memory::AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook) { - impl->AddDebugHook(page_table, base, size, std::move(hook)); -} - -void Memory::RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook) { - impl->RemoveDebugHook(page_table, base, size, std::move(hook)); -} - bool Memory::IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) const { return impl->IsValidVirtualAddress(process, vaddr); } diff --git a/src/core/memory.h b/src/core/memory.h index 4a1cc63f4..705ebb23d 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -8,7 +8,6 @@ #include <memory> #include <string> #include "common/common_types.h" -#include "common/memory_hook.h" namespace Common { struct PageTable; @@ -78,17 +77,6 @@ public: void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target); /** - * Maps a region of the emulated process address space as a IO region. - * - * @param page_table The page table of the emulated process. - * @param base The address to start mapping at. Must be page-aligned. - * @param size The amount of bytes to map. Must be page-aligned. - * @param mmio_handler The handler that backs the mapping. - */ - void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer mmio_handler); - - /** * Unmaps a region of the emulated process address space. * * @param page_table The page table of the emulated process. @@ -98,28 +86,6 @@ public: void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size); /** - * Adds a memory hook to intercept reads and writes to given region of memory. - * - * @param page_table The page table of the emulated process - * @param base The starting address to apply the hook to. - * @param size The size of the memory region to apply the hook to, in bytes. - * @param hook The hook to apply to the region of memory. - */ - void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook); - - /** - * Removes a memory hook from a given range of memory. - * - * @param page_table The page table of the emulated process. - * @param base The starting address to remove the hook from. - * @param size The size of the memory region to remove the hook from, in bytes. - * @param hook The hook to remove from the specified region of memory. - */ - void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, - Common::MemoryHookPointer hook); - - /** * Checks whether or not the supplied address is a valid virtual * address for the given process. * diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index e503118dd..2dd0eb0f8 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -19,10 +19,24 @@ #include "core/memory/cheat_engine.h" namespace Core::Memory { - +namespace { constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; +std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) { + auto end_index = start_index; + while (data[end_index] != match) { + ++end_index; + if (end_index > data.size() || + (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { + return {}; + } + } + + return data.substr(start_index, end_index - start_index); +} +} // Anonymous namespace + StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) : metadata(metadata), system(system) {} @@ -82,26 +96,9 @@ CheatParser::~CheatParser() = default; TextCheatParser::~TextCheatParser() = default; -namespace { -template <char match> -std::string_view ExtractName(std::string_view data, std::size_t start_index) { - auto end_index = start_index; - while (data[end_index] != match) { - ++end_index; - if (end_index > data.size() || - (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { - return {}; - } - } - - return data.substr(start_index, end_index - start_index); -} -} // Anonymous namespace - -std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, - std::string_view data) const { +std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { std::vector<CheatEntry> out(1); - std::optional<u64> current_entry = std::nullopt; + std::optional<u64> current_entry; for (std::size_t i = 0; i < data.size(); ++i) { if (::isspace(data[i])) { @@ -115,7 +112,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, return {}; } - const auto name = ExtractName<'}'>(data, i + 1); + const auto name = ExtractName(data, i + 1, '}'); if (name.empty()) { return {}; } @@ -132,7 +129,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, current_entry = out.size(); out.emplace_back(); - const auto name = ExtractName<']'>(data, i + 1); + const auto name = ExtractName(data, i + 1, ']'); if (name.empty()) { return {}; } @@ -156,8 +153,9 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, return {}; } + const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = - std::stoul(hex, nullptr, 0x10); + value; i += 8; } else { diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h index fa039a831..a31002346 100644 --- a/src/core/memory/cheat_engine.h +++ b/src/core/memory/cheat_engine.h @@ -47,8 +47,7 @@ class CheatParser { public: virtual ~CheatParser(); - virtual std::vector<CheatEntry> Parse(const Core::System& system, - std::string_view data) const = 0; + [[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0; }; // CheatParser implementation that parses text files @@ -56,7 +55,7 @@ class TextCheatParser final : public CheatParser { public: ~TextCheatParser() override; - std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override; + [[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override; }; // Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp index 56d173b5e..681e93468 100644 --- a/src/core/network/network.cpp +++ b/src/core/network/network.cpp @@ -11,7 +11,7 @@ #ifdef _WIN32 #define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname #include <winsock2.h> -#elif __unix__ +#elif YUZU_UNIX #include <errno.h> #include <fcntl.h> #include <netdb.h> @@ -54,7 +54,7 @@ constexpr IPv4Address TranslateIPv4(in_addr addr) { sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; -#ifdef __unix__ +#if YUZU_UNIX result.sin_len = sizeof(result); #endif @@ -63,7 +63,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) { result.sin_family = AF_INET; break; default: - UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family)); + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family); result.sin_family = AF_INET; break; } @@ -99,7 +99,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; } -#elif __unix__ // ^ _WIN32 v __unix__ +#elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX using SOCKET = int; using WSAPOLLFD = pollfd; @@ -133,7 +133,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) { result.sin_family = AF_INET; break; default: - UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family)); + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family); result.sin_family = AF_INET; break; } @@ -148,7 +148,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) { } int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) { - return poll(fds, nfds, timeout); + return poll(fds, static_cast<nfds_t>(nfds), timeout); } int closesocket(SOCKET fd) { @@ -186,7 +186,7 @@ int TranslateDomain(Domain domain) { case Domain::INET: return AF_INET; default: - UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain)); + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); return 0; } } @@ -198,7 +198,7 @@ int TranslateType(Type type) { case Type::DGRAM: return SOCK_DGRAM; default: - UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type)); + UNIMPLEMENTED_MSG("Unimplemented type={}", type); return 0; } } @@ -210,7 +210,7 @@ int TranslateProtocol(Protocol protocol) { case Protocol::UDP: return IPPROTO_UDP; default: - UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol)); + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); return 0; } } @@ -238,45 +238,45 @@ SockAddrIn TranslateToSockAddrIn(sockaddr input_) { return result; } -u16 TranslatePollEvents(u16 events) { - u16 result = 0; +short TranslatePollEvents(PollEvents events) { + short result = 0; - if (events & POLL_IN) { - events &= ~POLL_IN; + if (True(events & PollEvents::In)) { + events &= ~PollEvents::In; result |= POLLIN; } - if (events & POLL_PRI) { - events &= ~POLL_PRI; + if (True(events & PollEvents::Pri)) { + events &= ~PollEvents::Pri; #ifdef _WIN32 LOG_WARNING(Service, "Winsock doesn't support POLLPRI"); #else - result |= POLL_PRI; + result |= POLLPRI; #endif } - if (events & POLL_OUT) { - events &= ~POLL_OUT; + if (True(events & PollEvents::Out)) { + events &= ~PollEvents::Out; result |= POLLOUT; } - UNIMPLEMENTED_IF_MSG(events != 0, "Unhandled guest events=0x{:x}", events); + UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events); return result; } -u16 TranslatePollRevents(u16 revents) { - u16 result = 0; - const auto translate = [&result, &revents](int host, unsigned guest) { - if (revents & host) { - revents &= ~host; +PollEvents TranslatePollRevents(short revents) { + PollEvents result{}; + const auto translate = [&result, &revents](short host, PollEvents guest) { + if ((revents & host) != 0) { + revents &= static_cast<short>(~host); result |= guest; } }; - translate(POLLIN, POLL_IN); - translate(POLLPRI, POLL_PRI); - translate(POLLOUT, POLL_OUT); - translate(POLLERR, POLL_ERR); - translate(POLLHUP, POLL_HUP); + translate(POLLIN, PollEvents::In); + translate(POLLPRI, PollEvents::Pri); + translate(POLLOUT, PollEvents::Out); + translate(POLLERR, PollEvents::Err); + translate(POLLHUP, PollEvents::Hup); UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); @@ -408,7 +408,7 @@ std::pair<Socket::AcceptResult, Errno> Socket::Accept() { Errno Socket::Connect(SockAddrIn addr_in) { const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in); - if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != INVALID_SOCKET) { + if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) { return Errno::SUCCESS; } @@ -482,7 +482,7 @@ Errno Socket::Shutdown(ShutdownHow how) { host_how = SD_BOTH; break; default: - UNIMPLEMENTED_MSG("Unimplemented flag how={}", static_cast<int>(how)); + UNIMPLEMENTED_MSG("Unimplemented flag how={}", how); return Errno::SUCCESS; } if (shutdown(fd, host_how) != SOCKET_ERROR) { @@ -503,10 +503,10 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) { ASSERT(flags == 0); ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); - const int result = + const auto result = recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0); if (result != SOCKET_ERROR) { - return {result, Errno::SUCCESS}; + return {static_cast<s32>(result), Errno::SUCCESS}; } switch (const int ec = LastError()) { @@ -531,14 +531,14 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, Sock socklen_t* const p_addrlen = addr ? &addrlen : nullptr; sockaddr* const p_addr_in = addr ? &addr_in : nullptr; - const int result = recvfrom(fd, reinterpret_cast<char*>(message.data()), - static_cast<int>(message.size()), 0, p_addr_in, p_addrlen); + const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()), + static_cast<int>(message.size()), 0, p_addr_in, p_addrlen); if (result != SOCKET_ERROR) { if (addr) { ASSERT(addrlen == sizeof(addr_in)); *addr = TranslateToSockAddrIn(addr_in); } - return {result, Errno::SUCCESS}; + return {static_cast<s32>(result), Errno::SUCCESS}; } switch (const int ec = LastError()) { @@ -558,10 +558,10 @@ std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); - const int result = send(fd, reinterpret_cast<const char*>(message.data()), - static_cast<int>(message.size()), 0); + const auto result = send(fd, reinterpret_cast<const char*>(message.data()), + static_cast<int>(message.size()), 0); if (result != SOCKET_ERROR) { - return {result, Errno::SUCCESS}; + return {static_cast<s32>(result), Errno::SUCCESS}; } const int ec = LastError(); @@ -591,10 +591,10 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, to = &host_addr_in; } - const int result = sendto(fd, reinterpret_cast<const char*>(message.data()), - static_cast<int>(message.size()), 0, to, tolen); + const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()), + static_cast<int>(message.size()), 0, to, tolen); if (result != SOCKET_ERROR) { - return {result, Errno::SUCCESS}; + return {static_cast<s32>(result), Errno::SUCCESS}; } const int ec = LastError(); diff --git a/src/core/network/network.h b/src/core/network/network.h index 0622e4593..76b2821f2 100644 --- a/src/core/network/network.h +++ b/src/core/network/network.h @@ -61,19 +61,25 @@ struct SockAddrIn { }; /// Cross-platform poll fd structure + +enum class PollEvents : u16 { + // Using Pascal case because IN is a macro on Windows. + In = 1 << 0, + Pri = 1 << 1, + Out = 1 << 2, + Err = 1 << 3, + Hup = 1 << 4, + Nval = 1 << 5, +}; + +DECLARE_ENUM_FLAG_OPERATORS(PollEvents); + struct PollFD { Socket* socket; - u16 events; - u16 revents; + PollEvents events; + PollEvents revents; }; -constexpr u16 POLL_IN = 1 << 0; -constexpr u16 POLL_PRI = 1 << 1; -constexpr u16 POLL_OUT = 1 << 2; -constexpr u16 POLL_ERR = 1 << 3; -constexpr u16 POLL_HUP = 1 << 4; -constexpr u16 POLL_NVAL = 1 << 5; - class NetworkInstance { public: explicit NetworkInstance(); diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h index 7bdff0fe4..a44393325 100644 --- a/src/core/network/sockets.h +++ b/src/core/network/sockets.h @@ -9,7 +9,7 @@ #if defined(_WIN32) #include <winsock.h> -#elif !defined(__unix__) +#elif !YUZU_UNIX #error "Platform not implemented" #endif @@ -84,7 +84,7 @@ public: #if defined(_WIN32) SOCKET fd = INVALID_SOCKET; -#elif defined(__unix__) +#elif YUZU_UNIX int fd = -1; #endif }; diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 0becdf642..f199c3362 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -20,7 +20,6 @@ #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" #include "core/hle/result.h" -#include "core/hle/service/lm/manager.h" #include "core/memory.h" #include "core/reporter.h" #include "core/settings.h" @@ -360,55 +359,6 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result, SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); } -void Reporter::SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const { - if (!IsReportingEnabled()) { - return; - } - - const auto timestamp = GetTimestamp(); - json out; - - out["yuzu_version"] = GetYuzuVersionData(); - out["report_common"] = - GetReportCommonData(system.CurrentProcess()->GetTitleID(), RESULT_SUCCESS, timestamp); - - out["log_destination"] = - fmt::format("{}", static_cast<Service::LM::DestinationFlag>(destination)); - - auto json_messages = json::array(); - std::transform(messages.begin(), messages.end(), std::back_inserter(json_messages), - [](const Service::LM::LogMessage& message) { - json out; - out["is_head"] = fmt::format("{}", message.header.IsHeadLog()); - out["is_tail"] = fmt::format("{}", message.header.IsTailLog()); - out["pid"] = fmt::format("{:016X}", message.header.pid); - out["thread_context"] = - fmt::format("{:016X}", message.header.thread_context); - out["payload_size"] = fmt::format("{:016X}", message.header.payload_size); - out["flags"] = fmt::format("{:04X}", message.header.flags.Value()); - out["severity"] = fmt::format("{}", message.header.severity.Value()); - out["verbosity"] = fmt::format("{:02X}", message.header.verbosity); - - auto fields = json::array(); - std::transform(message.fields.begin(), message.fields.end(), - std::back_inserter(fields), [](const auto& kv) { - json out; - out["type"] = fmt::format("{}", kv.first); - out["data"] = - Service::LM::FormatField(kv.first, kv.second); - return out; - }); - - out["fields"] = std::move(fields); - return out; - }); - - out["log_messages"] = std::move(json_messages); - - SaveToFile(std::move(out), - GetPath("log_report", system.CurrentProcess()->GetTitleID(), timestamp)); -} - void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, std::string log_message) const { if (!IsReportingEnabled()) diff --git a/src/core/reporter.h b/src/core/reporter.h index 86d760cf0..b2c2d9a2e 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -72,9 +72,6 @@ public: void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, std::string log_message) const; - // Used by lm services - void SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const; - // Can be used anywhere to generate a backtrace and general info report at any point during // execution. Not intended to be used for anything other than debugging or testing. void SaveUserReport() const; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 28d3f9099..2ae5196e0 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,9 +4,10 @@ #include <string_view> +#include "common/assert.h" #include "common/file_util.h" +#include "common/logging/log.h" #include "core/core.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" #include "core/settings.h" #include "video_core/renderer_base.h" @@ -14,7 +15,7 @@ namespace Settings { Values values = {}; -bool configuring_global = true; +static bool configuring_global = true; std::string GetTimeZoneString() { static constexpr std::array timezones{ @@ -31,13 +32,9 @@ std::string GetTimeZoneString() { return timezones[time_zone_index]; } -void Apply() { - GDBStub::SetServerPort(values.gdbstub_port); - GDBStub::ToggleServer(values.use_gdbstub); - - auto& system_instance = Core::System::GetInstance(); - if (system_instance.IsPoweredOn()) { - system_instance.Renderer().RefreshBaseSettings(); +void Apply(Core::System& system) { + if (system.IsPoweredOn()) { + system.Renderer().RefreshBaseSettings(); } Service::HID::ReloadInputDevices(); @@ -49,13 +46,14 @@ void LogSettings() { }; LOG_INFO(Config, "yuzu Configuration:"); - log_setting("Controls_UseDockedMode", values.use_docked_mode); + log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); log_setting("System_CurrentUser", values.current_user); log_setting("System_LanguageIndex", values.language_index.GetValue()); log_setting("System_RegionIndex", values.region_index.GetValue()); log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); + log_setting("CPU_Accuracy", values.cpu_accuracy); log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue()); log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue()); log_setting("Renderer_FrameLimit", values.frame_limit.GetValue()); @@ -63,6 +61,7 @@ void LogSettings() { log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue()); log_setting("Renderer_UseAsynchronousGpuEmulation", values.use_asynchronous_gpu_emulation.GetValue()); + log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); @@ -71,20 +70,22 @@ void LogSettings() { log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); log_setting("Audio_OutputDevice", values.audio_device_id); log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); + log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)); + log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); + log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)); log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)); log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)); - log_setting("Debugging_UseGdbstub", values.use_gdbstub); - log_setting("Debugging_GdbstubPort", values.gdbstub_port); log_setting("Debugging_ProgramArgs", values.program_args); log_setting("Services_BCATBackend", values.bcat_backend); log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local); } -float Volume() { - if (values.audio_muted) { - return 0.0f; - } - return values.volume.GetValue(); +bool IsConfiguringGlobal() { + return configuring_global; +} + +void SetConfiguringGlobal(bool is_global) { + configuring_global = is_global; } bool IsGPULevelExtreme() { @@ -96,9 +97,16 @@ bool IsGPULevelHigh() { values.gpu_accuracy.GetValue() == GPUAccuracy::High; } -void RestoreGlobalState() { +float Volume() { + if (values.audio_muted) { + return 0.0f; + } + return values.volume.GetValue(); +} + +void RestoreGlobalState(bool is_powered_on) { // If a game is running, DO NOT restore the global settings state - if (Core::System::GetInstance().IsPoweredOn()) { + if (is_powered_on) { return; } @@ -119,6 +127,7 @@ void RestoreGlobalState() { values.use_disk_shader_cache.SetGlobal(true); values.gpu_accuracy.SetGlobal(true); values.use_asynchronous_gpu_emulation.SetGlobal(true); + values.use_nvdec_emulation.SetGlobal(true); values.use_vsync.SetGlobal(true); values.use_assembly_shaders.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); @@ -134,11 +143,12 @@ void RestoreGlobalState() { values.rng_seed.SetGlobal(true); values.custom_rtc.SetGlobal(true); values.sound_index.SetGlobal(true); -} -void Sanitize() { - values.use_asynchronous_gpu_emulation.SetValue( - values.use_asynchronous_gpu_emulation.GetValue() || values.use_multi_core.GetValue()); + // Controls + values.players.SetGlobal(true); + values.use_docked_mode.SetGlobal(true); + values.vibration_enabled.SetGlobal(true); + values.motion_enabled.SetGlobal(true); } } // namespace Settings diff --git a/src/core/settings.h b/src/core/settings.h index 732c6a894..d849dded3 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -14,6 +14,10 @@ #include "common/common_types.h" #include "input_common/settings.h" +namespace Core { +class System; +} + namespace Settings { enum class RendererBackend { @@ -33,8 +37,6 @@ enum class CPUAccuracy { DebugMode = 2, }; -extern bool configuring_global; - template <typename Type> class Setting final { public: @@ -67,6 +69,43 @@ private: Type local{}; }; +/** + * The InputSetting class allows for getting a reference to either the global or local members. + * This is required as we cannot easily modify the values of user-defined types within containers + * using the SetValue() member function found in the Setting class. The primary purpose of this + * class is to store an array of 10 PlayerInput structs for both the global and local (per-game) + * setting and allows for easily accessing and modifying both settings. + */ +template <typename Type> +class InputSetting final { +public: + InputSetting() = default; + explicit InputSetting(Type val) : global{val} {} + ~InputSetting() = default; + void SetGlobal(bool to_global) { + use_global = to_global; + } + bool UsingGlobal() const { + return use_global; + } + Type& GetValue(bool need_global = false) { + if (use_global || need_global) { + return global; + } + return local; + } + +private: + bool use_global = true; + Type global{}; + Type local{}; +}; + +struct TouchFromButtonMap { + std::string name; + std::vector<std::string> buttons; +}; + struct Values { // Audio std::string audio_device_id; @@ -92,13 +131,14 @@ struct Values { bool cpuopt_unsafe_unfuse_fma; bool cpuopt_unsafe_reduce_fp_error; + bool cpuopt_unsafe_inaccurate_nan; // Renderer Setting<RendererBackend> renderer_backend; bool renderer_debug; Setting<int> vulkan_device; - Setting<u16> resolution_factor = Setting(static_cast<u16>(1)); + Setting<u16> resolution_factor{1}; Setting<int> aspect_ratio; Setting<int> max_anisotropy; Setting<bool> use_frame_limit; @@ -106,6 +146,7 @@ struct Values { Setting<bool> use_disk_shader_cache; Setting<GPUAccuracy> gpu_accuracy; Setting<bool> use_asynchronous_gpu_emulation; + Setting<bool> use_nvdec_emulation; Setting<bool> use_vsync; Setting<bool> use_assembly_shaders; Setting<bool> use_asynchronous_shaders; @@ -129,14 +170,24 @@ struct Values { Setting<s32> sound_index; // Controls - std::array<PlayerInput, 10> players; + InputSetting<std::array<PlayerInput, 10>> players; + + Setting<bool> use_docked_mode; - bool use_docked_mode; + Setting<bool> vibration_enabled; + Setting<bool> enable_accurate_vibrations; + + Setting<bool> motion_enabled; + std::string motion_device; + std::string udp_input_servers; + bool mouse_panning; + float mouse_panning_sensitivity; bool mouse_enabled; std::string mouse_device; MouseButtonsRaw mouse_buttons; + bool emulate_analog_keyboard; bool keyboard_enabled; KeyboardKeysRaw keyboard_keys; KeyboardModsRaw keyboard_mods; @@ -145,15 +196,14 @@ struct Values { ButtonsRaw debug_pad_buttons; AnalogsRaw debug_pad_analogs; - std::string motion_device; + TouchscreenInput touchscreen; - bool vibration_enabled; + bool use_touch_from_button; + std::string touch_device; + int touch_from_button_map_index; + std::vector<TouchFromButtonMap> touch_from_button_maps; - TouchscreenInput touchscreen; std::atomic_bool is_device_reload_pending{true}; - std::string udp_input_address; - u16 udp_input_port; - u8 udp_pad_index; // Data Storage bool use_virtual_sd; @@ -171,8 +221,9 @@ struct Values { bool reporting_services; bool quest_flag; bool disable_macro_jit; + bool extended_logging; - // Misceallaneous + // Miscellaneous std::string log_filter; bool use_dev_keys; @@ -188,22 +239,24 @@ struct Values { // Add-Ons std::map<u64, std::vector<std::string>> disabled_addons; -} extern values; +}; -float Volume(); +extern Values values; + +bool IsConfiguringGlobal(); +void SetConfiguringGlobal(bool is_global); bool IsGPULevelExtreme(); bool IsGPULevelHigh(); +float Volume(); + std::string GetTimeZoneString(); -void Apply(); +void Apply(Core::System& system); void LogSettings(); // Restore the global state of all applicable settings in the Values struct -void RestoreGlobalState(); - -// Fixes settings that are known to cause issues with the emulator -void Sanitize(); +void RestoreGlobalState(bool is_powered_on); } // namespace Settings diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index da09c0dbc..d11b15f38 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -147,7 +147,9 @@ TelemetrySession::~TelemetrySession() { } } -void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { +void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider) { // Log one-time top-level information AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId()); @@ -167,7 +169,10 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { app_loader.ReadTitle(name); if (name.empty()) { - const auto metadata = FileSys::PatchManager(program_id).GetControlMetadata(); + const auto metadata = [&content_provider, &fsc, program_id] { + const FileSys::PatchManager pm{program_id, fsc, content_provider}; + return pm.GetControlMetadata(); + }(); if (metadata.first != nullptr) { name = metadata.first->GetApplicationName(); } @@ -206,12 +211,14 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue())); AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", Settings::values.use_asynchronous_gpu_emulation.GetValue()); + AddField(field_type, "Renderer_UseNvdecEmulation", + Settings::values.use_nvdec_emulation.GetValue()); AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); AddField(field_type, "Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders.GetValue()); AddField(field_type, "Renderer_UseAsynchronousShaders", Settings::values.use_asynchronous_shaders.GetValue()); - AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); + AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue()); } bool TelemetrySession::SubmitTestcase() { diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 66789d4bd..6f3d45bea 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -7,10 +7,18 @@ #include <string> #include "common/telemetry.h" +namespace FileSys { +class ContentProvider; +} + namespace Loader { class AppLoader; } +namespace Service::FileSystem { +class FileSystemController; +} + namespace Core { /** @@ -40,10 +48,14 @@ public: * - Title file format * - Miscellaneous settings values. * - * @param app_loader The application loader to use to retrieve - * title-specific information. + * @param app_loader The application loader to use to retrieve + * title-specific information. + * @param fsc Filesystem controller to use to retrieve info. + * @param content_provider Content provider to use to retrieve info. */ - void AddInitialInfo(Loader::AppLoader& app_loader); + void AddInitialInfo(Loader::AppLoader& app_loader, + const Service::FileSystem::FileSystemController& fsc, + const FileSys::ContentProvider& content_provider); /** * Wrapper around the Telemetry::FieldCollection::AddField method. diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 56267c8a8..38ab31898 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -5,14 +5,22 @@ add_library(input_common STATIC keyboard.h main.cpp main.h - motion_emu.cpp - motion_emu.h + motion_from_button.cpp + motion_from_button.h + motion_input.cpp + motion_input.h settings.cpp settings.h + touch_from_button.cpp + touch_from_button.h gcadapter/gc_adapter.cpp gcadapter/gc_adapter.h gcadapter/gc_poller.cpp gcadapter/gc_poller.h + mouse/mouse_input.cpp + mouse/mouse_input.h + mouse/mouse_poller.cpp + mouse/mouse_poller.h sdl/sdl.cpp sdl/sdl.h udp/client.cpp @@ -23,6 +31,39 @@ add_library(input_common STATIC udp/udp.h ) +if (MSVC) + target_compile_options(input_common PRIVATE + /W4 + /WX + + # 'expression' : signed/unsigned mismatch + /we4018 + # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point) + /we4244 + # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch + /we4245 + # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4254 + # 'var' : conversion from 'size_t' to 'type', possible loss of data + /we4267 + # 'context' : truncation from 'type1' to 'type2' + /we4305 + ) +else() + target_compile_options(input_common PRIVATE + -Werror + -Werror=conversion + -Werror=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=reorder + -Werror=shadow + -Werror=sign-compare + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> + -Werror=unused-variable + ) +endif() + if(SDL2_FOUND) target_sources(input_common PRIVATE sdl/sdl_impl.cpp diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp index 6cabdaa3c..770893687 100755 --- a/src/input_common/analog_from_button.cpp +++ b/src/input_common/analog_from_button.cpp @@ -2,6 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> +#include <chrono> +#include <cmath> +#include <thread> +#include "common/math_util.h" +#include "core/settings.h" #include "input_common/analog_from_button.h" namespace InputCommon { @@ -11,27 +17,130 @@ public: using Button = std::unique_ptr<Input::ButtonDevice>; Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, - float modifier_scale_) + float modifier_scale_, float modifier_angle_) : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), - right(std::move(right_)), modifier(std::move(modifier_)), - modifier_scale(modifier_scale_) {} + right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), + modifier_angle(modifier_angle_) { + update_thread_running.store(true); + update_thread = std::thread(&Analog::UpdateStatus, this); + } + + ~Analog() override { + if (update_thread_running.load()) { + update_thread_running.store(false); + if (update_thread.joinable()) { + update_thread.join(); + } + } + } + + void MoveToDirection(bool enable, float to_angle) { + if (!enable) { + return; + } + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float top_limit = to_angle + aperture; + const float bottom_limit = to_angle - aperture; + + if ((angle > to_angle && angle <= top_limit) || + (angle + TAU > to_angle && angle + TAU <= top_limit)) { + angle -= modifier_angle; + if (angle < 0) { + angle += TAU; + } + } else if ((angle >= bottom_limit && angle < to_angle) || + (angle - TAU >= bottom_limit && angle - TAU < to_angle)) { + angle += modifier_angle; + if (angle >= TAU) { + angle -= TAU; + } + } else { + angle = to_angle; + } + } + + void UpdateStatus() { + while (update_thread_running.load()) { + const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; + + bool r = right->GetStatus(); + bool l = left->GetStatus(); + bool u = up->GetStatus(); + bool d = down->GetStatus(); + + // Eliminate contradictory movements + if (r && l) { + r = false; + l = false; + } + if (u && d) { + u = false; + d = false; + } + + // Move to the right + MoveToDirection(r && !u && !d, 0.0f); + + // Move to the upper right + MoveToDirection(r && u && !d, Common::PI * 0.25f); + + // Move up + MoveToDirection(u && !l && !r, Common::PI * 0.5f); + + // Move to the upper left + MoveToDirection(l && u && !d, Common::PI * 0.75f); + + // Move to the left + MoveToDirection(l && !u && !d, Common::PI); + + // Move to the bottom left + MoveToDirection(l && !u && d, Common::PI * 1.25f); + + // Move down + MoveToDirection(d && !l && !r, Common::PI * 1.5f); + + // Move to the bottom right + MoveToDirection(r && !u && d, Common::PI * 1.75f); + + // Move if a key is pressed + if (r || l || u || d) { + amplitude = coef; + } else { + amplitude = 0; + } + + // Delay the update rate to 100hz + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } std::tuple<float, float> GetStatus() const override { + if (Settings::values.emulate_analog_keyboard) { + return std::make_tuple(std::cos(angle) * amplitude, std::sin(angle) * amplitude); + } constexpr float SQRT_HALF = 0.707106781f; int x = 0, y = 0; - - if (right->GetStatus()) + if (right->GetStatus()) { ++x; - if (left->GetStatus()) + } + if (left->GetStatus()) { --x; - if (up->GetStatus()) + } + if (up->GetStatus()) { ++y; - if (down->GetStatus()) + } + if (down->GetStatus()) { --y; + } + const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; + return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF), + static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF)); + } - float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF), - y * coef * (x == 0 ? 1.0f : SQRT_HALF)); + Input::AnalogProperties GetAnalogProperties() const override { + return {modifier_scale, 1.0f, 0.5f}; } bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { @@ -55,6 +164,11 @@ private: Button right; Button modifier; float modifier_scale; + float modifier_angle; + float angle{}; + float amplitude{}; + std::thread update_thread; + std::atomic<bool> update_thread_running{}; }; std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { @@ -65,8 +179,10 @@ std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::Para auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); auto modifier_scale = params.Get("modifier_scale", 0.5f); + auto modifier_angle = params.Get("modifier_angle", 0.035f); return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), - std::move(right), std::move(modifier), modifier_scale); + std::move(right), std::move(modifier), modifier_scale, + modifier_angle); } } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 74759ea7d..d80195c82 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,20 +4,23 @@ #include <chrono> #include <thread> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union +#endif #include <libusb.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "common/logging/log.h" +#include "common/param_package.h" #include "input_common/gcadapter/gc_adapter.h" +#include "input_common/settings.h" namespace GCAdapter { -/// Used to loop through and assign button in poller -constexpr std::array<PadButton, 12> PadButtonArray{ - PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, - PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R, - PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, - PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START, -}; - Adapter::Adapter() { if (usb_adapter_handle != nullptr) { return; @@ -26,179 +29,261 @@ Adapter::Adapter() { const int init_res = libusb_init(&libusb_ctx); if (init_res == LIBUSB_SUCCESS) { - Setup(); + adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); } else { LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); } } -GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { - GCPadStatus pad = {}; - const std::size_t offset = 1 + (9 * port); +Adapter::~Adapter() { + Reset(); +} - adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); +void Adapter::AdapterInputThread() { + LOG_DEBUG(Input, "GC Adapter input thread started"); + s32 payload_size{}; + AdapterPayload adapter_payload{}; - static constexpr std::array<PadButton, 8> b1_buttons{ - PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, - PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, - PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, - }; + if (adapter_scan_thread.joinable()) { + adapter_scan_thread.join(); + } - static constexpr std::array<PadButton, 4> b2_buttons{ - PadButton::PAD_BUTTON_START, - PadButton::PAD_TRIGGER_Z, - PadButton::PAD_TRIGGER_R, - PadButton::PAD_TRIGGER_L, - }; + while (adapter_input_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + static_cast<s32>(adapter_payload.size()), &payload_size, 16); + if (IsPayloadCorrect(adapter_payload, payload_size)) { + UpdateControllers(adapter_payload); + UpdateVibrations(); + } + std::this_thread::yield(); + } - static constexpr std::array<PadAxes, 6> axes{ - PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, - PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, - }; + if (restart_scan_thread) { + adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + restart_scan_thread = false; + } +} - if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { - // Controller may have been disconnected, recalibrate if reconnected. - get_origin[port] = true; +bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { + if (payload_size != static_cast<s32>(adapter_payload.size()) || + adapter_payload[0] != LIBUSB_DT_HID) { + LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, + adapter_payload[0]); + if (input_error_counter++ > 20) { + LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); + adapter_input_thread_running = false; + restart_scan_thread = true; + } + return false; } - if (adapter_controllers_status[port] != ControllerTypes::None) { - const u8 b1 = adapter_payload[offset + 1]; - const u8 b2 = adapter_payload[offset + 2]; + input_error_counter = 0; + return true; +} - for (std::size_t i = 0; i < b1_buttons.size(); ++i) { - if ((b1 & (1U << i)) != 0) { - pad.button |= static_cast<u16>(b1_buttons[i]); +void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { + for (std::size_t port = 0; port < pads.size(); ++port) { + const std::size_t offset = 1 + (9 * port); + const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); + UpdatePadType(port, type); + if (DeviceConnected(port)) { + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; + UpdateStateButtons(port, b1, b2); + UpdateStateAxes(port, adapter_payload); + if (configuring) { + UpdateYuzuSettings(port); } } + } +} - for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pad.button |= static_cast<u16>(b2_buttons[j]); - } - } - for (PadAxes axis : axes) { - const std::size_t index = static_cast<std::size_t>(axis); - pad.axis_values[index] = adapter_payload[offset + 3 + index]; +void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { + if (pads[port].type == pad_type) { + return; + } + // Device changed reset device and set new type + ResetDevice(port); + pads[port].type = pad_type; +} + +void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { + if (port >= pads.size()) { + return; + } + + static constexpr std::array<PadButton, 8> b1_buttons{ + PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, + PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, + }; + + static constexpr std::array<PadButton, 4> b2_buttons{ + PadButton::ButtonStart, + PadButton::TriggerZ, + PadButton::TriggerR, + PadButton::TriggerL, + }; + pads[port].buttons = 0; + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + if ((b1 & (1U << i)) != 0) { + pads[port].buttons = + static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); + pads[port].last_button = b1_buttons[i]; } + } - if (get_origin[port]) { - origin_status[port].axis_values = pad.axis_values; - get_origin[port] = false; + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + if ((b2 & (1U << j)) != 0) { + pads[port].buttons = + static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); + pads[port].last_button = b2_buttons[j]; } } - return pad; } -void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { - for (const auto& button : PadButtonArray) { - const u16 button_value = static_cast<u16>(button); - state.buttons.insert_or_assign(button_value, pad.button & button_value); +void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { + if (port >= pads.size()) { + return; } - for (size_t i = 0; i < pad.axis_values.size(); ++i) { - state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]); + const std::size_t offset = 1 + (9 * port); + static constexpr std::array<PadAxes, 6> axes{ + PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, + PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, + }; + + for (const PadAxes axis : axes) { + const auto index = static_cast<std::size_t>(axis); + const u8 axis_value = adapter_payload[offset + 3 + index]; + if (pads[port].axis_origin[index] == 255) { + pads[port].axis_origin[index] = axis_value; + } + pads[port].axis_values[index] = + static_cast<s16>(axis_value - pads[port].axis_origin[index]); } } -void Adapter::Read() { - LOG_DEBUG(Input, "GC Adapter Read() thread started"); +void Adapter::UpdateYuzuSettings(std::size_t port) { + if (port >= pads.size()) { + return; + } - int payload_size; - std::array<u8, 37> adapter_payload; - std::array<GCPadStatus, 4> pads; + constexpr u8 axis_threshold = 50; + GCPadStatus pad_status = {.port = port}; - while (adapter_thread_running) { - libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), - sizeof(adapter_payload), &payload_size, 16); - - if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) { - LOG_ERROR(Input, - "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?", - payload_size, adapter_payload[0]); - adapter_thread_running = false; // error reading from adapter, stop reading. - break; - } - for (std::size_t port = 0; port < pads.size(); ++port) { - pads[port] = GetPadStatus(port, adapter_payload); - if (DeviceConnected(port) && configuring) { - if (pads[port].button != 0) { - pad_queue[port].Push(pads[port]); - } + if (pads[port].buttons != 0) { + pad_status.button = pads[port].last_button; + pad_queue.Push(pad_status); + } - // Accounting for a threshold here to ensure an intentional press - for (size_t i = 0; i < pads[port].axis_values.size(); ++i) { - const u8 value = pads[port].axis_values[i]; - const u8 origin = origin_status[port].axis_values[i]; - - if (value > origin + pads[port].THRESHOLD || - value < origin - pads[port].THRESHOLD) { - pads[port].axis = static_cast<PadAxes>(i); - pads[port].axis_value = pads[port].axis_values[i]; - pad_queue[port].Push(pads[port]); - } - } - } - PadToState(pads[port], state[port]); + // Accounting for a threshold here to ensure an intentional press + for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { + const s16 value = pads[port].axis_values[i]; + + if (value > axis_threshold || value < -axis_threshold) { + pad_status.axis = static_cast<PadAxes>(i); + pad_status.axis_value = value; + pad_status.axis_threshold = axis_threshold; + pad_queue.Push(pad_status); } - std::this_thread::yield(); } } -void Adapter::Setup() { - // Initialize all controllers as unplugged - adapter_controllers_status.fill(ControllerTypes::None); - // Initialize all ports to store axis origin values - get_origin.fill(true); - - // pointer to list of connected usb devices - libusb_device** devices{}; - - // populate the list of devices, get the count - const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); - if (device_count < 0) { - LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); - return; +void Adapter::UpdateVibrations() { + // Use 8 states to keep the switching between on/off fast enough for + // a human to not notice the difference between switching from on/off + // More states = more rumble strengths = slower update time + constexpr u8 vibration_states = 8; + + vibration_counter = (vibration_counter + 1) % vibration_states; + + for (GCController& pad : pads) { + const bool vibrate = pad.rumble_amplitude > vibration_counter; + vibration_changed |= vibrate != pad.enable_vibration; + pad.enable_vibration = vibrate; } + SendVibrations(); +} - if (devices != nullptr) { - for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) { - if (CheckDeviceAccess(devices[index])) { - // GC Adapter found and accessible, registering it - GetGCEndpoint(devices[index]); - break; - } +void Adapter::SendVibrations() { + if (!rumble_enabled || !vibration_changed) { + return; + } + s32 size{}; + constexpr u8 rumble_command = 0x11; + const u8 p1 = pads[0].enable_vibration; + const u8 p2 = pads[1].enable_vibration; + const u8 p3 = pads[2].enable_vibration; + const u8 p4 = pads[3].enable_vibration; + std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4}; + const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(), + static_cast<s32>(payload.size()), &size, 16); + if (err) { + LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); + if (output_error_counter++ > 5) { + LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); + rumble_enabled = false; } - libusb_free_device_list(devices, 1); + return; } + output_error_counter = 0; + vibration_changed = false; } -bool Adapter::CheckDeviceAccess(libusb_device* device) { - libusb_device_descriptor desc; - const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); - if (get_descriptor_error) { - // could not acquire the descriptor, no point in trying to use it. - LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", - get_descriptor_error); - return false; +bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { + pads[port].rumble_amplitude = amplitude; + + return rumble_enabled; +} + +void Adapter::AdapterScanThread() { + adapter_scan_thread_running = true; + adapter_input_thread_running = false; + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } + ClearLibusbHandle(); + ResetDevices(); + while (adapter_scan_thread_running && !adapter_input_thread_running) { + Setup(); + std::this_thread::sleep_for(std::chrono::seconds(1)); } +} - if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { - // This isn't the device we are looking for. - return false; +void Adapter::Setup() { + usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); + + if (usb_adapter_handle == NULL) { + return; + } + if (!CheckDeviceAccess()) { + ClearLibusbHandle(); + return; } - const int open_error = libusb_open(device, &usb_adapter_handle); - if (open_error == LIBUSB_ERROR_ACCESS) { - LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", - desc.idVendor, desc.idProduct); - return false; + libusb_device* device = libusb_get_device(usb_adapter_handle); + + LOG_INFO(Input, "GC adapter is now connected"); + // GC Adapter found and accessible, registering it + if (GetGCEndpoint(device)) { + adapter_scan_thread_running = false; + adapter_input_thread_running = true; + rumble_enabled = true; + input_error_counter = 0; + output_error_counter = 0; + adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); } - if (open_error) { - LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); - return false; +} + +bool Adapter::CheckDeviceAccess() { + // This fixes payload problems from offbrand GCAdapters + const s32 control_transfer_error = + libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); + if (control_transfer_error < 0) { + LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); } - int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); + s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); if (kernel_driver_error == 1) { kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { @@ -224,13 +309,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) { return true; } -void Adapter::GetGCEndpoint(libusb_device* device) { +bool Adapter::GetGCEndpoint(libusb_device* device) { libusb_config_descriptor* config = nullptr; const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); if (config_descriptor_return != LIBUSB_SUCCESS) { LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", config_descriptor_return); - return; + return false; } for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { @@ -239,7 +324,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) { const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; for (u8 e = 0; e < interface->bNumEndpoints; e++) { const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; - if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { + if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { input_endpoint = endpoint->bEndpointAddress; } else { output_endpoint = endpoint->bEndpointAddress; @@ -252,78 +337,173 @@ void Adapter::GetGCEndpoint(libusb_device* device) { unsigned char clear_payload = 0x13; libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, sizeof(clear_payload), nullptr, 16); - - adapter_thread_running = true; - adapter_input_thread = std::thread(&Adapter::Read, this); + return true; } -Adapter::~Adapter() { - Reset(); -} +void Adapter::JoinThreads() { + restart_scan_thread = false; + adapter_input_thread_running = false; + adapter_scan_thread_running = false; -void Adapter::Reset() { - if (adapter_thread_running) { - adapter_thread_running = false; + if (adapter_scan_thread.joinable()) { + adapter_scan_thread.join(); } + if (adapter_input_thread.joinable()) { adapter_input_thread.join(); } +} - adapter_controllers_status.fill(ControllerTypes::None); - get_origin.fill(true); - +void Adapter::ClearLibusbHandle() { if (usb_adapter_handle) { libusb_release_interface(usb_adapter_handle, 1); libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; } +} + +void Adapter::ResetDevices() { + for (std::size_t i = 0; i < pads.size(); ++i) { + ResetDevice(i); + } +} + +void Adapter::ResetDevice(std::size_t port) { + pads[port].type = ControllerTypes::None; + pads[port].enable_vibration = false; + pads[port].rumble_amplitude = 0; + pads[port].buttons = 0; + pads[port].last_button = PadButton::Undefined; + pads[port].axis_values.fill(0); + pads[port].axis_origin.fill(255); +} + +void Adapter::Reset() { + JoinThreads(); + ClearLibusbHandle(); + ResetDevices(); if (libusb_ctx) { libusb_exit(libusb_ctx); } } -bool Adapter::DeviceConnected(std::size_t port) { - return adapter_controllers_status[port] != ControllerTypes::None; +std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + for (std::size_t port = 0; port < pads.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + std::string name = fmt::format("Gamecube Controller {}", port + 1); + devices.emplace_back(Common::ParamPackage{ + {"class", "gcpad"}, + {"display", std::move(name)}, + {"port", std::to_string(port)}, + }); + } + return devices; } -void Adapter::ResetDeviceType(std::size_t port) { - adapter_controllers_status[port] = ControllerTypes::None; +InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + // This list is missing ZL/ZR since those are not considered buttons. + // We will add those afterwards + // This list also excludes any button that can't be really mapped + static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12> + switch_to_gcadapter_button = { + std::pair{Settings::NativeButton::A, PadButton::ButtonA}, + {Settings::NativeButton::B, PadButton::ButtonB}, + {Settings::NativeButton::X, PadButton::ButtonX}, + {Settings::NativeButton::Y, PadButton::ButtonY}, + {Settings::NativeButton::Plus, PadButton::ButtonStart}, + {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, + {Settings::NativeButton::DUp, PadButton::ButtonUp}, + {Settings::NativeButton::DRight, PadButton::ButtonRight}, + {Settings::NativeButton::DDown, PadButton::ButtonDown}, + {Settings::NativeButton::SL, PadButton::TriggerL}, + {Settings::NativeButton::SR, PadButton::TriggerR}, + {Settings::NativeButton::R, PadButton::TriggerZ}, + }; + if (!params.Has("port")) { + return {}; + } + + InputCommon::ButtonMapping mapping{}; + for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { + Common::ParamPackage button_params({{"engine", "gcpad"}}); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast<int>(gcadapter_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Add the missing bindings for ZL/ZR + static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> + switch_to_gcadapter_axis = { + std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadAxes::TriggerRight}, + }; + for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { + Common::ParamPackage button_params({{"engine", "gcpad"}}); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast<s32>(PadButton::Stick)); + button_params.Set("axis", static_cast<s32>(gcadapter_axis)); + button_params.Set("threshold", 0.5f); + button_params.Set("direction", "+"); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + return mapping; } -void Adapter::BeginConfiguration() { - get_origin.fill(true); - for (auto& pq : pad_queue) { - pq.Clear(); +InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("port")) { + return {}; } + + InputCommon::AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", "gcpad"); + left_analog_params.Set("port", params.Get("port", 0)); + left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); + left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", "gcpad"); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); + right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +bool Adapter::DeviceConnected(std::size_t port) const { + return pads[port].type != ControllerTypes::None; +} + +void Adapter::BeginConfiguration() { + pad_queue.Clear(); configuring = true; } void Adapter::EndConfiguration() { - for (auto& pq : pad_queue) { - pq.Clear(); - } + pad_queue.Clear(); configuring = false; } -std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { +Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() { return pad_queue; } -const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { +const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const { return pad_queue; } -std::array<GCState, 4>& Adapter::GetPadState() { - return state; -} - -const std::array<GCState, 4>& Adapter::GetPadState() const { - return state; +GCController& Adapter::GetPadState(std::size_t port) { + return pads.at(port); } -int Adapter::GetOriginValue(int port, int axis) const { - return origin_status[port].axis_values[axis]; +const GCController& Adapter::GetPadState(std::size_t port) const { + return pads.at(port); } } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index bed81915c..7a6c545bd 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -10,6 +10,7 @@ #include <unordered_map> #include "common/common_types.h" #include "common/threadsafe_queue.h" +#include "input_common/main.h" struct libusb_context; struct libusb_device; @@ -18,24 +19,23 @@ struct libusb_device_handle; namespace GCAdapter { enum class PadButton { - PAD_BUTTON_LEFT = 0x0001, - PAD_BUTTON_RIGHT = 0x0002, - PAD_BUTTON_DOWN = 0x0004, - PAD_BUTTON_UP = 0x0008, - PAD_TRIGGER_Z = 0x0010, - PAD_TRIGGER_R = 0x0020, - PAD_TRIGGER_L = 0x0040, - PAD_BUTTON_A = 0x0100, - PAD_BUTTON_B = 0x0200, - PAD_BUTTON_X = 0x0400, - PAD_BUTTON_Y = 0x0800, - PAD_BUTTON_START = 0x1000, + Undefined = 0x0000, + ButtonLeft = 0x0001, + ButtonRight = 0x0002, + ButtonDown = 0x0004, + ButtonUp = 0x0008, + TriggerZ = 0x0010, + TriggerR = 0x0020, + TriggerL = 0x0040, + ButtonA = 0x0100, + ButtonB = 0x0200, + ButtonX = 0x0400, + ButtonY = 0x0800, + ButtonStart = 0x1000, // Below is for compatibility with "AxisButton" type - PAD_STICK = 0x2000, + Stick = 0x2000, }; -extern const std::array<PadButton, 12> PadButtonArray; - enum class PadAxes : u8 { StickX, StickY, @@ -46,85 +46,122 @@ enum class PadAxes : u8 { Undefined, }; +enum class ControllerTypes { + None, + Wired, + Wireless, +}; + struct GCPadStatus { - u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + std::size_t port{}; - std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes - static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling + PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - u8 port{}; PadAxes axis{PadAxes::Undefined}; - u8 axis_value{255}; + s16 axis_value{}; + u8 axis_threshold{50}; }; -struct GCState { - std::unordered_map<int, bool> buttons; - std::unordered_map<int, u16> axes; +struct GCController { + ControllerTypes type{}; + bool enable_vibration{}; + u8 rumble_amplitude{}; + u16 buttons{}; + PadButton last_button{}; + std::array<s16, 6> axis_values{}; + std::array<u8, 6> axis_origin{}; }; -enum class ControllerTypes { None, Wired, Wireless }; - class Adapter { public: - /// Initialize the GC Adapter capture and read sequence Adapter(); - - /// Close the adapter read thread and release the adapter ~Adapter(); + + /// Request a vibration for a controller + bool RumblePlay(std::size_t port, u8 amplitude); + /// Used for polling void BeginConfiguration(); void EndConfiguration(); - /// Returns true if there is a device connected to port - bool DeviceConnected(std::size_t port); + Common::SPSCQueue<GCPadStatus>& GetPadQueue(); + const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const; - std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); - const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; + GCController& GetPadState(std::size_t port); + const GCController& GetPadState(std::size_t port) const; - std::array<GCState, 4>& GetPadState(); - const std::array<GCState, 4>& GetPadState() const; + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port) const; - int GetOriginValue(int port, int axis) const; + /// Used for automapping features + std::vector<Common::ParamPackage> GetInputDevices() const; + InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; private: - GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload); + using AdapterPayload = std::array<u8, 37>; + + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateYuzuSettings(std::size_t port); + void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); + void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); + void UpdateVibrations(); + + void AdapterInputThread(); - void PadToState(const GCPadStatus& pad, GCState& state); + void AdapterScanThread(); - void Read(); + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); - /// Resets status of device connected to port - void ResetDeviceType(std::size_t port); + // Updates vibration state of all controllers + void SendVibrations(); + + /// For use in initialization, querying devices to find the adapter + void Setup(); + + /// Resets status of all GC controller devices to a disconnected state + void ResetDevices(); + + /// Resets status of device connected to a disconnected state + void ResetDevice(std::size_t port); /// Returns true if we successfully gain access to GC Adapter - bool CheckDeviceAccess(libusb_device* device); + bool CheckDeviceAccess(); - /// Captures GC Adapter endpoint address, - void GetGCEndpoint(libusb_device* device); + /// Captures GC Adapter endpoint address + /// Returns true if the endpoint was set correctly + bool GetGCEndpoint(libusb_device* device); /// For shutting down, clear all data, join all threads, release usb void Reset(); - /// For use in initialization, querying devices to find the adapter - void Setup(); + // Join all threads + void JoinThreads(); + + // Release usb handles + void ClearLibusbHandle(); libusb_device_handle* usb_adapter_handle = nullptr; + std::array<GCController, 4> pads; + Common::SPSCQueue<GCPadStatus> pad_queue; std::thread adapter_input_thread; - bool adapter_thread_running; + std::thread adapter_scan_thread; + bool adapter_input_thread_running; + bool adapter_scan_thread_running; + bool restart_scan_thread; libusb_context* libusb_ctx; - u8 input_endpoint = 0; - u8 output_endpoint = 0; + u8 input_endpoint{0}; + u8 output_endpoint{0}; + u8 input_error_counter{0}; + u8 output_error_counter{0}; + int vibration_counter{0}; - bool configuring = false; - - std::array<GCState, 4> state; - std::array<bool, 4> get_origin; - std::array<GCPadStatus, 4> origin_status; - std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; - std::array<ControllerTypes, 4> adapter_controllers_status{}; + bool configuring{false}; + bool rumble_enabled{true}; + bool vibration_changed{true}; }; - } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 71cd85eeb..1b6ded8d6 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,35 +15,35 @@ namespace InputCommon { class GCButton final : public Input::ButtonDevice { public: - explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) + explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) : port(port_), button(button_), gcadapter(adapter) {} ~GCButton() override; bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { - return gcadapter->GetPadState()[port].buttons.at(button); + return (gcadapter->GetPadState(port).buttons & button) != 0; } return false; } private: - const int port; - const int button; - GCAdapter::Adapter* gcadapter; + const u32 port; + const s32 button; + const GCAdapter::Adapter* gcadapter; }; class GCAxisButton final : public Input::ButtonDevice { public: - explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, - GCAdapter::Adapter* adapter) + explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, + const GCAdapter::Adapter* adapter) : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), - gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {} + gcadapter(adapter) {} bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { - const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis); - const float axis_value = (current_axis_value - origin_value) / 128.0f; + const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); + const float axis_value = current_axis_value / 128.0f; if (trigger_if_greater) { // TODO: Might be worthwile to set a slider for the trigger threshold. It is // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick @@ -55,12 +55,11 @@ public: } private: - const int port; - const int axis; + const u32 port; + const u32 axis; float threshold; bool trigger_if_greater; - GCAdapter::Adapter* gcadapter; - const float origin_value; + const GCAdapter::Adapter* gcadapter; }; GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) @@ -69,10 +68,10 @@ GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) GCButton::~GCButton() = default; std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { - const int button_id = params.Get("button", 0); - const int port = params.Get("port", 0); + const auto button_id = params.Get("button", 0); + const auto port = static_cast<u32>(params.Get("port", 0)); - constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); + constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick); // button is not an axis/stick button if (button_id != PAD_STICK_ID) { @@ -97,7 +96,6 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param adapter.get()); } - UNREACHABLE(); return nullptr; } @@ -105,32 +103,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() const { Common::ParamPackage params; GCAdapter::GCPadStatus pad; auto& queue = adapter->GetPadQueue(); - for (std::size_t port = 0; port < queue.size(); ++port) { - while (queue[port].Pop(pad)) { - // This while loop will break on the earliest detected button - params.Set("engine", "gcpad"); - params.Set("port", static_cast<int>(port)); - for (const auto& button : GCAdapter::PadButtonArray) { - const u16 button_value = static_cast<u16>(button); - if (pad.button & button_value) { - params.Set("button", button_value); - break; - } - } + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + params.Set("engine", "gcpad"); + params.Set("port", static_cast<s32>(pad.port)); + if (pad.button != GCAdapter::PadButton::Undefined) { + params.Set("button", static_cast<u16>(pad.button)); + } - // For Axis button implementation - if (pad.axis != GCAdapter::PadAxes::Undefined) { - params.Set("axis", static_cast<u8>(pad.axis)); - params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); - if (pad.axis_value > 128) { - params.Set("direction", "+"); - params.Set("threshold", "0.25"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.25"); - } - break; + // For Axis button implementation + if (pad.axis != GCAdapter::PadAxes::Undefined) { + params.Set("axis", static_cast<u8>(pad.axis)); + params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick)); + params.Set("threshold", "0.25"); + if (pad.axis_value > 0) { + params.Set("direction", "+"); + } else { + params.Set("direction", "-"); } + break; } } return params; @@ -148,25 +139,30 @@ void GCButtonFactory::EndConfiguration() { class GCAnalog final : public Input::AnalogDevice { public: - GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter, - float range_) - : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), - origin_value_x(adapter->GetOriginValue(port_, axis_x_)), - origin_value_y(adapter->GetOriginValue(port_, axis_y_)), range(range_) {} + explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, + float deadzone_, float range_, const GCAdapter::Adapter* adapter) + : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), + deadzone(deadzone_), range(range_), gcadapter(adapter) {} - float GetAxis(int axis) const { + float GetAxis(u32 axis) const { if (gcadapter->DeviceConnected(port)) { std::lock_guard lock{mutex}; - const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; - return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); + const auto axis_value = + static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis)); + return (axis_value) / (100.0f * range); } return 0.0f; } - std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); - + std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_axis_y); + if (invert_x) { + x = -x; + } + if (invert_y) { + y = -y; + } // Make sure the coordinates are in the unit circle, // otherwise normalize it. float r = x * x + y * y; @@ -189,6 +185,16 @@ public: return {0.0f, 0.0f}; } + std::tuple<float, float> GetRawStatus() const override { + const float x = GetAxis(axis_x); + const float y = GetAxis(axis_y); + return {x, y}; + } + + Input::AnalogProperties GetAnalogProperties() const override { + return {deadzone, range, 0.5f}; + } + bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { const auto [x, y] = GetStatus(); const float directional_deadzone = 0.5f; @@ -206,14 +212,14 @@ public: } private: - const int port; - const int axis_x; - const int axis_y; + const u32 port; + const u32 axis_x; + const u32 axis_y; + const bool invert_x; + const bool invert_y; const float deadzone; - GCAdapter::Adapter* gcadapter; - const float origin_value_x; - const float origin_value_y; const float range; + const GCAdapter::Adapter* gcadapter; mutable std::mutex mutex; }; @@ -229,13 +235,18 @@ GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) * - "axis_y": the index of the axis to be bind as y-axis */ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - - return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range); + const auto port = static_cast<u32>(params.Get("port", 0)); + const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); + const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + const std::string invert_x_value = params.Get("invert_x", "+"); + const std::string invert_y_value = params.Get("invert_y", "+"); + const bool invert_x = invert_x_value == "-"; + const bool invert_y = invert_y_value == "-"; + + return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, + adapter.get()); } void GCAnalogFactory::BeginConfiguration() { @@ -250,31 +261,51 @@ void GCAnalogFactory::EndConfiguration() { Common::ParamPackage GCAnalogFactory::GetNextInput() { GCAdapter::GCPadStatus pad; + Common::ParamPackage params; auto& queue = adapter->GetPadQueue(); - for (std::size_t port = 0; port < queue.size(); ++port) { - while (queue[port].Pop(pad)) { - if (pad.axis == GCAdapter::PadAxes::Undefined || - std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second input event. The axes also must be from the same joystick. - const u8 axis = static_cast<u8>(pad.axis); - if (analog_x_axis == -1) { - analog_x_axis = axis; - controller_number = static_cast<int>(port); - } else if (analog_y_axis == -1 && analog_x_axis != axis && - controller_number == static_cast<int>(port)) { - analog_y_axis = axis; - } + while (queue.Pop(pad)) { + if (pad.button != GCAdapter::PadButton::Undefined) { + params.Set("engine", "gcpad"); + params.Set("port", static_cast<s32>(pad.port)); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + if (pad.axis == GCAdapter::PadAxes::Undefined || + std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second input event. The axes also must be from the same joystick. + const u8 axis = static_cast<u8>(pad.axis); + if (axis == 0 || axis == 1) { + analog_x_axis = 0; + analog_y_axis = 1; + controller_number = static_cast<s32>(pad.port); + break; + } + if (axis == 2 || axis == 3) { + analog_x_axis = 2; + analog_y_axis = 3; + controller_number = static_cast<s32>(pad.port); + break; + } + + if (analog_x_axis == -1) { + analog_x_axis = axis; + controller_number = static_cast<s32>(pad.port); + } else if (analog_y_axis == -1 && analog_x_axis != axis && + controller_number == static_cast<s32>(pad.port)) { + analog_y_axis = axis; + break; } } - Common::ParamPackage params; if (analog_x_axis != -1 && analog_y_axis != -1) { params.Set("engine", "gcpad"); params.Set("port", controller_number); params.Set("axis_x", analog_x_axis); params.Set("axis_y", analog_y_axis); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); analog_x_axis = -1; analog_y_axis = -1; controller_number = -1; @@ -283,4 +314,43 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { return params; } +class GCVibration final : public Input::VibrationDevice { +public: + explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) + : port(port_), gcadapter(adapter) {} + + u8 GetStatus() const override { + return gcadapter->RumblePlay(port, 0); + } + + bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, + [[maybe_unused]] f32 freq_high) const override { + const auto mean_amplitude = (amp_low + amp_high) * 0.5f; + const auto processed_amplitude = + static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); + + return gcadapter->RumblePlay(port, processed_amplitude); + } + +private: + const u32 port; + GCAdapter::Adapter* gcadapter; +}; + +/// An vibration device factory that creates vibration devices from GC Adapter +GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) + : adapter(std::move(adapter_)) {} + +/** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + */ +std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast<u32>(params.Get("port", 0)); + + return std::make_unique<GCVibration>(port, adapter.get()); +} + } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 0527f328f..d1271e3ea 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -64,4 +64,15 @@ private: bool polling = false; }; +/// A vibration device factory creates vibration devices from GC Adapter +class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> { +public: + explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + + std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override; + +private: + std::shared_ptr<GCAdapter::Adapter> adapter; +}; + } // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp index afb8e6612..24a6f7a33 100644 --- a/src/input_common/keyboard.cpp +++ b/src/input_common/keyboard.cpp @@ -49,8 +49,9 @@ public: void ChangeKeyStatus(int key_code, bool pressed) { std::lock_guard guard{mutex}; for (const KeyButtonPair& pair : list) { - if (pair.key_code == key_code) + if (pair.key_code == key_code) { pair.key_button->status.store(pressed); + } } } @@ -73,7 +74,7 @@ KeyButton::~KeyButton() { } std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) { - int key_code = params.Get("code", 0); + const int key_code = params.Get("code", 0); std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list); key_button_list->AddKeyButton(key_code, button.get()); return button; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 57e7a25fe..7c4e7dd3b 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -10,7 +10,11 @@ #include "input_common/gcadapter/gc_poller.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/motion_from_button.h" +#include "input_common/mouse/mouse_input.h" +#include "input_common/mouse/mouse_poller.h" +#include "input_common/touch_from_button.h" +#include "input_common/udp/client.h" #include "input_common/udp/udp.h" #ifdef HAVE_SDL2 #include "input_common/sdl/sdl.h" @@ -20,47 +24,82 @@ namespace InputCommon { struct InputSubsystem::Impl { void Initialize() { - auto gcadapter = std::make_shared<GCAdapter::Adapter>(); + gcadapter = std::make_shared<GCAdapter::Adapter>(); gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); + gcvibration = std::make_shared<GCVibrationFactory>(gcadapter); + Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration); keyboard = std::make_shared<Keyboard>(); Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", std::make_shared<AnalogFromButton>()); - motion_emu = std::make_shared<MotionEmu>(); - Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); + Input::RegisterFactory<Input::MotionDevice>("keyboard", + std::make_shared<MotionFromButton>()); + Input::RegisterFactory<Input::TouchDevice>("touch_from_button", + std::make_shared<TouchFromButtonFactory>()); #ifdef HAVE_SDL2 sdl = SDL::Init(); #endif - udp = CemuhookUDP::Init(); + udp = std::make_shared<InputCommon::CemuhookUDP::Client>(); + udpmotion = std::make_shared<UDPMotionFactory>(udp); + Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); + udptouch = std::make_shared<UDPTouchFactory>(udp); + Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch); + + mouse = std::make_shared<MouseInput::Mouse>(); + mousebuttons = std::make_shared<MouseButtonFactory>(mouse); + Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons); + mouseanalog = std::make_shared<MouseAnalogFactory>(mouse); + Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog); + mousemotion = std::make_shared<MouseMotionFactory>(mouse); + Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); + mousetouch = std::make_shared<MouseTouchFactory>(mouse); + Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); } void Shutdown() { Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); + Input::UnregisterFactory<Input::MotionDevice>("keyboard"); keyboard.reset(); Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); - Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); - motion_emu.reset(); + Input::UnregisterFactory<Input::TouchDevice>("touch_from_button"); #ifdef HAVE_SDL2 sdl.reset(); #endif - udp.reset(); Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); + Input::UnregisterFactory<Input::VibrationDevice>("gcpad"); gcbuttons.reset(); gcanalog.reset(); + gcvibration.reset(); + + Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); + Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); + + udpmotion.reset(); + udptouch.reset(); + + Input::UnregisterFactory<Input::ButtonDevice>("mouse"); + Input::UnregisterFactory<Input::AnalogDevice>("mouse"); + Input::UnregisterFactory<Input::MotionDevice>("mouse"); + Input::UnregisterFactory<Input::TouchDevice>("mouse"); + + mousebuttons.reset(); + mouseanalog.reset(); + mousemotion.reset(); + mousetouch.reset(); } [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { std::vector<Common::ParamPackage> devices = { Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, - Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}}, + Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, }; #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); @@ -68,6 +107,8 @@ struct InputSubsystem::Impl { #endif auto udp_devices = udp->GetInputDevices(); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); + auto gcpad_devices = gcadapter->GetInputDevices(); + devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end()); return devices; } @@ -76,9 +117,8 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "key") { - // TODO consider returning the SDL key codes for the default keybindings - return {}; + if (params.Get("class", "") == "gcpad") { + return gcadapter->GetAnalogMappingForDevice(params); } #ifdef HAVE_SDL2 if (params.Get("class", "") == "sdl") { @@ -93,9 +133,8 @@ struct InputSubsystem::Impl { if (!params.Has("class") || params.Get("class", "") == "any") { return {}; } - if (params.Get("class", "") == "key") { - // TODO consider returning the SDL key codes for the default keybindings - return {}; + if (params.Get("class", "") == "gcpad") { + return gcadapter->GetButtonMappingForDevice(params); } #ifdef HAVE_SDL2 if (params.Get("class", "") == "sdl") { @@ -105,14 +144,34 @@ struct InputSubsystem::Impl { return {}; } + [[nodiscard]] MotionMapping GetMotionMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("class") || params.Get("class", "") == "any") { + return {}; + } + if (params.Get("class", "") == "cemuhookudp") { + // TODO return the correct motion device + return {}; + } + return {}; + } + std::shared_ptr<Keyboard> keyboard; - std::shared_ptr<MotionEmu> motion_emu; #ifdef HAVE_SDL2 std::unique_ptr<SDL::State> sdl; #endif - std::unique_ptr<CemuhookUDP::State> udp; std::shared_ptr<GCButtonFactory> gcbuttons; std::shared_ptr<GCAnalogFactory> gcanalog; + std::shared_ptr<GCVibrationFactory> gcvibration; + std::shared_ptr<UDPMotionFactory> udpmotion; + std::shared_ptr<UDPTouchFactory> udptouch; + std::shared_ptr<MouseButtonFactory> mousebuttons; + std::shared_ptr<MouseAnalogFactory> mouseanalog; + std::shared_ptr<MouseMotionFactory> mousemotion; + std::shared_ptr<MouseTouchFactory> mousetouch; + std::shared_ptr<CemuhookUDP::Client> udp; + std::shared_ptr<GCAdapter::Adapter> gcadapter; + std::shared_ptr<MouseInput::Mouse> mouse; }; InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} @@ -135,12 +194,12 @@ const Keyboard* InputSubsystem::GetKeyboard() const { return impl->keyboard.get(); } -MotionEmu* InputSubsystem::GetMotionEmu() { - return impl->motion_emu.get(); +MouseInput::Mouse* InputSubsystem::GetMouse() { + return impl->mouse.get(); } -const MotionEmu* InputSubsystem::GetMotionEmu() const { - return impl->motion_emu.get(); +const MouseInput::Mouse* InputSubsystem::GetMouse() const { + return impl->mouse.get(); } std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { @@ -155,6 +214,10 @@ ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPacka return impl->GetButtonMappingForDevice(device); } +MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPackage& device) const { + return impl->GetMotionMappingForDevice(device); +} + GCAnalogFactory* InputSubsystem::GetGCAnalogs() { return impl->gcanalog.get(); } @@ -171,6 +234,61 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const { return impl->gcbuttons.get(); } +UDPMotionFactory* InputSubsystem::GetUDPMotions() { + return impl->udpmotion.get(); +} + +const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { + return impl->udpmotion.get(); +} + +UDPTouchFactory* InputSubsystem::GetUDPTouch() { + return impl->udptouch.get(); +} + +const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { + return impl->udptouch.get(); +} + +MouseButtonFactory* InputSubsystem::GetMouseButtons() { + return impl->mousebuttons.get(); +} + +const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { + return impl->mousebuttons.get(); +} + +MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { + return impl->mouseanalog.get(); +} + +const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const { + return impl->mouseanalog.get(); +} + +MouseMotionFactory* InputSubsystem::GetMouseMotions() { + return impl->mousemotion.get(); +} + +const MouseMotionFactory* InputSubsystem::GetMouseMotions() const { + return impl->mousemotion.get(); +} + +MouseTouchFactory* InputSubsystem::GetMouseTouch() { + return impl->mousetouch.get(); +} + +const MouseTouchFactory* InputSubsystem::GetMouseTouch() const { + return impl->mousetouch.get(); +} + +void InputSubsystem::ReloadInputDevices() { + if (!impl->udp) { + return; + } + impl->udp->ReloadSockets(); +} + std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( Polling::DeviceType type) const { #ifdef HAVE_SDL2 diff --git a/src/input_common/main.h b/src/input_common/main.h index 58e5dc250..5d6f26385 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -21,10 +21,18 @@ namespace Settings::NativeButton { enum Values : int; } +namespace Settings::NativeMotion { +enum Values : int; +} + +namespace MouseInput { +class Mouse; +} + namespace InputCommon { namespace Polling { -enum class DeviceType { Button, AnalogPreferred }; +enum class DeviceType { Button, AnalogPreferred, Motion }; /** * A class that can be used to get inputs from an input device like controllers without having to @@ -50,8 +58,13 @@ public: class GCAnalogFactory; class GCButtonFactory; +class UDPMotionFactory; +class UDPTouchFactory; +class MouseButtonFactory; +class MouseAnalogFactory; +class MouseMotionFactory; +class MouseTouchFactory; class Keyboard; -class MotionEmu; /** * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default @@ -59,6 +72,7 @@ class MotionEmu; */ using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; +using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>; class InputSubsystem { public: @@ -83,11 +97,11 @@ public: /// Retrieves the underlying keyboard device. [[nodiscard]] const Keyboard* GetKeyboard() const; - /// Retrieves the underlying motion emulation factory. - [[nodiscard]] MotionEmu* GetMotionEmu(); + /// Retrieves the underlying mouse device. + [[nodiscard]] MouseInput::Mouse* GetMouse(); - /// Retrieves the underlying motion emulation factory. - [[nodiscard]] const MotionEmu* GetMotionEmu() const; + /// Retrieves the underlying mouse device. + [[nodiscard]] const MouseInput::Mouse* GetMouse() const; /** * Returns all available input devices that this Factory can create a new device with. @@ -103,6 +117,9 @@ public: /// Retrieves the button mappings for the given device. [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; + /// Retrieves the motion mappings for the given device. + [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; + /// Retrieves the underlying GameCube analog handler. [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); @@ -115,6 +132,45 @@ public: /// Retrieves the underlying GameCube button handler. [[nodiscard]] const GCButtonFactory* GetGCButtons() const; + /// Retrieves the underlying udp motion handler. + [[nodiscard]] UDPMotionFactory* GetUDPMotions(); + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] UDPTouchFactory* GetUDPTouch(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] MouseButtonFactory* GetMouseButtons(); + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] MouseMotionFactory* GetMouseMotions(); + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] MouseTouchFactory* GetMouseTouch(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; + + /// Reloads the input devices + void ReloadInputDevices(); + /// Get all DevicePoller from all backends for a specific device type [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( Polling::DeviceType type) const; diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp deleted file mode 100644 index d4cdf76a3..000000000 --- a/src/input_common/motion_emu.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <chrono> -#include <mutex> -#include <thread> -#include <tuple> -#include "common/math_util.h" -#include "common/quaternion.h" -#include "common/thread.h" -#include "common/vector_math.h" -#include "input_common/motion_emu.h" - -namespace InputCommon { - -// Implementation class of the motion emulation device -class MotionEmuDevice { -public: - MotionEmuDevice(int update_millisecond, float sensitivity) - : update_millisecond(update_millisecond), - update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>( - std::chrono::milliseconds(update_millisecond))), - sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} - - ~MotionEmuDevice() { - if (motion_emu_thread.joinable()) { - shutdown_event.Set(); - motion_emu_thread.join(); - } - } - - void BeginTilt(int x, int y) { - mouse_origin = Common::MakeVec(x, y); - is_tilting = true; - } - - void Tilt(int x, int y) { - auto mouse_move = Common::MakeVec(x, y) - mouse_origin; - if (is_tilting) { - std::lock_guard guard{tilt_mutex}; - if (mouse_move.x == 0 && mouse_move.y == 0) { - tilt_angle = 0; - } else { - tilt_direction = mouse_move.Cast<float>(); - tilt_angle = - std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f); - } - } - } - - void EndTilt() { - std::lock_guard guard{tilt_mutex}; - tilt_angle = 0; - is_tilting = false; - } - - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { - std::lock_guard guard{status_mutex}; - return status; - } - -private: - const int update_millisecond; - const std::chrono::steady_clock::duration update_duration; - const float sensitivity; - - Common::Vec2<int> mouse_origin; - - std::mutex tilt_mutex; - Common::Vec2<float> tilt_direction; - float tilt_angle = 0; - - bool is_tilting = false; - - Common::Event shutdown_event; - - std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; - std::mutex status_mutex; - - // Note: always keep the thread declaration at the end so that other objects are initialized - // before this! - std::thread motion_emu_thread; - - void MotionEmuThread() { - auto update_time = std::chrono::steady_clock::now(); - Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0); - Common::Quaternion<float> old_q; - - while (!shutdown_event.WaitUntil(update_time)) { - update_time += update_duration; - old_q = q; - - { - std::lock_guard guard{tilt_mutex}; - - // Find the quaternion describing current 3DS tilting - q = Common::MakeQuaternion( - Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle); - } - - auto inv_q = q.Inverse(); - - // Set the gravity vector in world space - auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f); - - // Find the angular rate vector in world space - auto angular_rate = ((q - old_q) * inv_q).xyz * 2; - angular_rate *= 1000 / update_millisecond / Common::PI * 180; - - // Transform the two vectors from world space to 3DS space - gravity = QuaternionRotate(inv_q, gravity); - angular_rate = QuaternionRotate(inv_q, angular_rate); - - // Update the sensor state - { - std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate); - } - } - } -}; - -// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as -// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory -// can forward all the inputs to the implementation only when it is valid. -class MotionEmuDeviceWrapper : public Input::MotionDevice { -public: - MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { - device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); - } - - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { - return device->GetStatus(); - } - - std::shared_ptr<MotionEmuDevice> device; -}; - -std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) { - int update_period = params.Get("update_period", 100); - float sensitivity = params.Get("sensitivity", 0.01f); - auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity); - // Previously created device is disconnected here. Having two motion devices for 3DS is not - // expected. - current_device = device_wrapper->device; - return device_wrapper; -} - -void MotionEmu::BeginTilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->BeginTilt(x, y); - } -} - -void MotionEmu::Tilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->Tilt(x, y); - } -} - -void MotionEmu::EndTilt() { - if (auto ptr = current_device.lock()) { - ptr->EndTilt(); - } -} - -} // namespace InputCommon diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h deleted file mode 100644 index 7a7e22467..000000000 --- a/src/input_common/motion_emu.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/input.h" - -namespace InputCommon { - -class MotionEmuDevice; - -class MotionEmu : public Input::Factory<Input::MotionDevice> { -public: - /** - * Creates a motion device emulated from mouse input - * @param params contains parameters for creating the device: - * - "update_period": update period in milliseconds - * - "sensitivity": the coefficient converting mouse movement to tilting angle - */ - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; - - /** - * Signals that a motion sensor tilt has begun. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void BeginTilt(int x, int y); - - /** - * Signals that a motion sensor tilt is occurring. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void Tilt(int x, int y); - - /** - * Signals that a motion sensor tilt has ended. - */ - void EndTilt(); - -private: - std::weak_ptr<MotionEmuDevice> current_device; -}; - -} // namespace InputCommon diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp new file mode 100644 index 000000000..29045a673 --- /dev/null +++ b/src/input_common/motion_from_button.cpp @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/motion_from_button.h" +#include "input_common/motion_input.h" + +namespace InputCommon { + +class MotionKey final : public Input::MotionDevice { +public: + using Button = std::unique_ptr<Input::ButtonDevice>; + + explicit MotionKey(Button key_) : key(std::move(key_)) {} + + Input::MotionStatus GetStatus() const override { + + if (key->GetStatus()) { + return motion.GetRandomMotion(2, 6); + } + return motion.GetRandomMotion(0, 0); + } + +private: + Button key; + InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; +}; + +std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) { + auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize()); + return std::make_unique<MotionKey>(std::move(key)); +} + +} // namespace InputCommon diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h new file mode 100644 index 000000000..a959046fb --- /dev/null +++ b/src/input_common/motion_from_button.h @@ -0,0 +1,25 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * An motion device factory that takes a keyboard button and uses it as a random + * motion device. + */ +class MotionFromButton final : public Input::Factory<Input::MotionDevice> { +public: + /** + * Creates an motion device from button devices + * @param params contains parameters for creating the device: + * - "key": a serialized ParamPackage for creating a button device + */ + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp new file mode 100644 index 000000000..6a65f175e --- /dev/null +++ b/src/input_common/motion_input.cpp @@ -0,0 +1,301 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include <random> +#include "common/math_util.h" +#include "input_common/motion_input.h" + +namespace InputCommon { + +MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {} + +void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { + accel = acceleration; +} + +void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { + gyro = gyroscope - gyro_drift; + + // Auto adjust drift to minimize drift + if (!IsMoving(0.1f)) { + gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f); + } + + if (gyro.Length2() < gyro_threshold) { + gyro = {}; + } else { + only_accelerometer = false; + } +} + +void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { + quat = quaternion; +} + +void MotionInput::SetGyroDrift(const Common::Vec3f& drift) { + gyro_drift = drift; +} + +void MotionInput::SetGyroThreshold(f32 threshold) { + gyro_threshold = threshold; +} + +void MotionInput::EnableReset(bool reset) { + reset_enabled = reset; +} + +void MotionInput::ResetRotations() { + rotations = {}; +} + +bool MotionInput::IsMoving(f32 sensitivity) const { + return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f; +} + +bool MotionInput::IsCalibrated(f32 sensitivity) const { + return real_error.Length() < sensitivity; +} + +void MotionInput::UpdateRotation(u64 elapsed_time) { + const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f; + if (sample_period > 0.1f) { + return; + } + rotations += gyro * sample_period; +} + +void MotionInput::UpdateOrientation(u64 elapsed_time) { + if (!IsCalibrated(0.1f)) { + ResetOrientation(); + } + // Short name local variable for readability + f32 q1 = quat.w; + f32 q2 = quat.xyz[0]; + f32 q3 = quat.xyz[1]; + f32 q4 = quat.xyz[2]; + const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f; + + // Ignore invalid elapsed time + if (sample_period > 0.1f) { + return; + } + + const auto normal_accel = accel.Normalized(); + auto rad_gyro = gyro * Common::PI * 2; + const f32 swap = rad_gyro.x; + rad_gyro.x = rad_gyro.y; + rad_gyro.y = -swap; + rad_gyro.z = -rad_gyro.z; + + // Clear gyro values if there is no gyro present + if (only_accelerometer) { + rad_gyro.x = 0; + rad_gyro.y = 0; + rad_gyro.z = 0; + } + + // Ignore drift correction if acceleration is not reliable + if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) { + const f32 ax = -normal_accel.x; + const f32 ay = normal_accel.y; + const f32 az = -normal_accel.z; + + // Estimated direction of gravity + const f32 vx = 2.0f * (q2 * q4 - q1 * q3); + const f32 vy = 2.0f * (q1 * q2 + q3 * q4); + const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; + + // Error is cross product between estimated direction and measured direction of gravity + const Common::Vec3f new_real_error = { + az * vx - ax * vz, + ay * vz - az * vy, + ax * vy - ay * vx, + }; + + derivative_error = new_real_error - real_error; + real_error = new_real_error; + + // Prevent integral windup + if (ki != 0.0f && !IsCalibrated(0.05f)) { + integral_error += real_error; + } else { + integral_error = {}; + } + + // Apply feedback terms + if (!only_accelerometer) { + rad_gyro += kp * real_error; + rad_gyro += ki * integral_error; + rad_gyro += kd * derivative_error; + } else { + // Give more weight to accelerometer values to compensate for the lack of gyro + rad_gyro += 35.0f * kp * real_error; + rad_gyro += 10.0f * ki * integral_error; + rad_gyro += 10.0f * kd * derivative_error; + + // Emulate gyro values for games that need them + gyro.x = -rad_gyro.y; + gyro.y = rad_gyro.x; + gyro.z = -rad_gyro.z; + UpdateRotation(elapsed_time); + } + } + + const f32 gx = rad_gyro.y; + const f32 gy = rad_gyro.x; + const f32 gz = rad_gyro.z; + + // Integrate rate of change of quaternion + const f32 pa = q2; + const f32 pb = q3; + const f32 pc = q4; + q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period); + q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period); + q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); + q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); + + quat.w = q1; + quat.xyz[0] = q2; + quat.xyz[1] = q3; + quat.xyz[2] = q4; + quat = quat.Normalized(); +} + +std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const { + const Common::Quaternion<float> quad{ + .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w}, + .w = -quat.xyz[2], + }; + const std::array<float, 16> matrix4x4 = quad.ToMatrix(); + + return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])}; +} + +Common::Vec3f MotionInput::GetAcceleration() const { + return accel; +} + +Common::Vec3f MotionInput::GetGyroscope() const { + return gyro; +} + +Common::Quaternion<f32> MotionInput::GetQuaternion() const { + return quat; +} + +Common::Vec3f MotionInput::GetRotations() const { + return rotations; +} + +Input::MotionStatus MotionInput::GetMotion() const { + const Common::Vec3f gyroscope = GetGyroscope(); + const Common::Vec3f accelerometer = GetAcceleration(); + const Common::Vec3f rotation = GetRotations(); + const std::array<Common::Vec3f, 3> orientation = GetOrientation(); + return {accelerometer, gyroscope, rotation, orientation}; +} + +Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<s16> distribution(-1000, 1000); + const Common::Vec3f gyroscope{ + static_cast<f32>(distribution(gen)) * 0.001f, + static_cast<f32>(distribution(gen)) * 0.001f, + static_cast<f32>(distribution(gen)) * 0.001f, + }; + const Common::Vec3f accelerometer{ + static_cast<f32>(distribution(gen)) * 0.001f, + static_cast<f32>(distribution(gen)) * 0.001f, + static_cast<f32>(distribution(gen)) * 0.001f, + }; + constexpr Common::Vec3f rotation; + constexpr std::array orientation{ + Common::Vec3f{1.0f, 0.0f, 0.0f}, + Common::Vec3f{0.0f, 1.0f, 0.0f}, + Common::Vec3f{0.0f, 0.0f, 1.0f}, + }; + return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation}; +} + +void MotionInput::ResetOrientation() { + if (!reset_enabled || only_accelerometer) { + return; + } + if (!IsMoving(0.5f) && accel.z <= -0.9f) { + ++reset_counter; + if (reset_counter > 900) { + quat.w = 0; + quat.xyz[0] = 0; + quat.xyz[1] = 0; + quat.xyz[2] = -1; + SetOrientationFromAccelerometer(); + integral_error = {}; + reset_counter = 0; + } + } else { + reset_counter = 0; + } +} + +void MotionInput::SetOrientationFromAccelerometer() { + int iterations = 0; + const f32 sample_period = 0.015f; + + const auto normal_accel = accel.Normalized(); + + while (!IsCalibrated(0.01f) && ++iterations < 100) { + // Short name local variable for readability + f32 q1 = quat.w; + f32 q2 = quat.xyz[0]; + f32 q3 = quat.xyz[1]; + f32 q4 = quat.xyz[2]; + + Common::Vec3f rad_gyro; + const f32 ax = -normal_accel.x; + const f32 ay = normal_accel.y; + const f32 az = -normal_accel.z; + + // Estimated direction of gravity + const f32 vx = 2.0f * (q2 * q4 - q1 * q3); + const f32 vy = 2.0f * (q1 * q2 + q3 * q4); + const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; + + // Error is cross product between estimated direction and measured direction of gravity + const Common::Vec3f new_real_error = { + az * vx - ax * vz, + ay * vz - az * vy, + ax * vy - ay * vx, + }; + + derivative_error = new_real_error - real_error; + real_error = new_real_error; + + rad_gyro += 10.0f * kp * real_error; + rad_gyro += 5.0f * ki * integral_error; + rad_gyro += 10.0f * kd * derivative_error; + + const f32 gx = rad_gyro.y; + const f32 gy = rad_gyro.x; + const f32 gz = rad_gyro.z; + + // Integrate rate of change of quaternion + const f32 pa = q2; + const f32 pb = q3; + const f32 pc = q4; + q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period); + q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period); + q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); + q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); + + quat.w = q1; + quat.xyz[0] = q2; + quat.xyz[1] = q3; + quat.xyz[2] = q4; + quat = quat.Normalized(); + } +} +} // namespace InputCommon diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h new file mode 100644 index 000000000..efe74cf19 --- /dev/null +++ b/src/input_common/motion_input.h @@ -0,0 +1,74 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "common/common_types.h" +#include "common/quaternion.h" +#include "common/vector_math.h" +#include "core/frontend/input.h" + +namespace InputCommon { + +class MotionInput { +public: + explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + + MotionInput(const MotionInput&) = default; + MotionInput& operator=(const MotionInput&) = default; + + MotionInput(MotionInput&&) = default; + MotionInput& operator=(MotionInput&&) = default; + + void SetAcceleration(const Common::Vec3f& acceleration); + void SetGyroscope(const Common::Vec3f& gyroscope); + void SetQuaternion(const Common::Quaternion<f32>& quaternion); + void SetGyroDrift(const Common::Vec3f& drift); + void SetGyroThreshold(f32 threshold); + + void EnableReset(bool reset); + void ResetRotations(); + + void UpdateRotation(u64 elapsed_time); + void UpdateOrientation(u64 elapsed_time); + + [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const; + [[nodiscard]] Common::Vec3f GetAcceleration() const; + [[nodiscard]] Common::Vec3f GetGyroscope() const; + [[nodiscard]] Common::Vec3f GetRotations() const; + [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; + [[nodiscard]] Input::MotionStatus GetMotion() const; + [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude, + int gyro_magnitude) const; + + [[nodiscard]] bool IsMoving(f32 sensitivity) const; + [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; + +private: + void ResetOrientation(); + void SetOrientationFromAccelerometer(); + + // PID constants + f32 kp; + f32 ki; + f32 kd; + + // PID errors + Common::Vec3f real_error; + Common::Vec3f integral_error; + Common::Vec3f derivative_error; + + Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f}; + Common::Vec3f rotations; + Common::Vec3f accel; + Common::Vec3f gyro; + Common::Vec3f gyro_drift; + + f32 gyro_threshold = 0.0f; + u32 reset_counter = 0; + bool reset_enabled = true; + bool only_accelerometer = true; +}; + +} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp new file mode 100644 index 000000000..67a584d53 --- /dev/null +++ b/src/input_common/mouse/mouse_input.cpp @@ -0,0 +1,159 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "core/settings.h" +#include "input_common/mouse/mouse_input.h" + +namespace MouseInput { + +Mouse::Mouse() { + update_thread = std::thread(&Mouse::UpdateThread, this); +} + +Mouse::~Mouse() { + update_thread_running = false; + if (update_thread.joinable()) { + update_thread.join(); + } +} + +void Mouse::UpdateThread() { + constexpr int update_time = 10; + while (update_thread_running) { + for (MouseInfo& info : mouse_info) { + const Common::Vec3f angular_direction{ + -info.tilt_direction.y, + 0.0f, + -info.tilt_direction.x, + }; + + info.motion.SetGyroscope(angular_direction * info.tilt_speed); + info.motion.UpdateRotation(update_time * 1000); + info.motion.UpdateOrientation(update_time * 1000); + info.tilt_speed = 0; + info.data.motion = info.motion.GetMotion(); + } + if (configuring) { + UpdateYuzuSettings(); + } + if (mouse_panning_timout++ > 8) { + StopPanning(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::UpdateYuzuSettings() { + if (buttons == 0) { + return; + } + + mouse_queue.Push(MouseStatus{ + .button = last_button, + }); +} + +void Mouse::PressButton(int x, int y, int button_) { + const auto button_index = static_cast<std::size_t>(button_); + if (button_index >= mouse_info.size()) { + return; + } + + const auto button = 1U << button_index; + buttons |= static_cast<u16>(button); + last_button = static_cast<MouseButton>(button_index); + + mouse_info[button_index].mouse_origin = Common::MakeVec(x, y); + mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y); + mouse_info[button_index].data.pressed = true; +} + +void Mouse::StopPanning() { + for (MouseInfo& info : mouse_info) { + if (Settings::values.mouse_panning) { + info.data.axis = {}; + info.tilt_speed = 0; + info.last_mouse_change = {}; + } + } +} + +void Mouse::MouseMove(int x, int y, int center_x, int center_y) { + for (MouseInfo& info : mouse_info) { + if (Settings::values.mouse_panning) { + const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y); + mouse_panning_timout = 0; + + if (mouse_change.y == 0 && mouse_change.x == 0) { + continue; + } + + info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f); + info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x), + static_cast<int>(16 * -info.last_mouse_change.y)}; + info.tilt_direction = info.last_mouse_change; + info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; + continue; + } + + if (info.data.pressed) { + const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin; + const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position; + info.last_mouse_position = Common::MakeVec(x, y); + info.data.axis = {mouse_move.x, -mouse_move.y}; + + if (mouse_change.x == 0 && mouse_change.y == 0) { + info.tilt_speed = 0; + } else { + info.tilt_direction = mouse_change.Cast<float>(); + info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; + } + } + } +} + +void Mouse::ReleaseButton(int button_) { + const auto button_index = static_cast<std::size_t>(button_); + if (button_index >= mouse_info.size()) { + return; + } + + const auto button = 1U << button_index; + buttons &= static_cast<u16>(0xFF - button); + + mouse_info[button_index].tilt_speed = 0; + mouse_info[button_index].data.pressed = false; + mouse_info[button_index].data.axis = {0, 0}; +} + +void Mouse::BeginConfiguration() { + buttons = 0; + last_button = MouseButton::Undefined; + mouse_queue.Clear(); + configuring = true; +} + +void Mouse::EndConfiguration() { + buttons = 0; + last_button = MouseButton::Undefined; + mouse_queue.Clear(); + configuring = false; +} + +Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() { + return mouse_queue; +} + +const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const { + return mouse_queue; +} + +MouseData& Mouse::GetMouseState(std::size_t button) { + return mouse_info[button].data; +} + +const MouseData& Mouse::GetMouseState(std::size_t button) const { + return mouse_info[button].data; +} +} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h new file mode 100644 index 000000000..46aa676c1 --- /dev/null +++ b/src/input_common/mouse/mouse_input.h @@ -0,0 +1,103 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <mutex> +#include <thread> + +#include "common/common_types.h" +#include "common/threadsafe_queue.h" +#include "common/vector_math.h" +#include "core/frontend/input.h" +#include "input_common/motion_input.h" + +namespace MouseInput { + +enum class MouseButton { + Left, + Wheel, + Right, + Forward, + Backward, + Undefined, +}; + +struct MouseStatus { + MouseButton button{MouseButton::Undefined}; +}; + +struct MouseData { + bool pressed{}; + std::array<int, 2> axis{}; + Input::MotionStatus motion{}; + Input::TouchStatus touch{}; +}; + +class Mouse { +public: + Mouse(); + ~Mouse(); + + /// Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + /** + * Signals that a button is pressed. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param button_ the button pressed + */ + void PressButton(int x, int y, int button_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void MouseMove(int x, int y, int center_x, int center_y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void ReleaseButton(int button_); + + [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue(); + [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const; + + [[nodiscard]] MouseData& GetMouseState(std::size_t button); + [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; + +private: + void UpdateThread(); + void UpdateYuzuSettings(); + void StopPanning(); + + struct MouseInfo { + InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; + Common::Vec2<int> mouse_origin; + Common::Vec2<int> last_mouse_position; + Common::Vec2<float> last_mouse_change; + bool is_tilting = false; + float sensitivity{0.120f}; + + float tilt_speed = 0; + Common::Vec2<float> tilt_direction; + MouseData data; + }; + + u16 buttons{}; + std::thread update_thread; + MouseButton last_button{MouseButton::Undefined}; + std::array<MouseInfo, 5> mouse_info; + Common::SPSCQueue<MouseStatus> mouse_queue; + bool configuring{false}; + bool update_thread_running{true}; + int mouse_panning_timout{}; +}; +} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp new file mode 100644 index 000000000..bb56787ee --- /dev/null +++ b/src/input_common/mouse/mouse_poller.cpp @@ -0,0 +1,285 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <utility> + +#include "common/threadsafe_queue.h" +#include "core/settings.h" +#include "input_common/mouse/mouse_input.h" +#include "input_common/mouse/mouse_poller.h" + +namespace InputCommon { + +class MouseButton final : public Input::ButtonDevice { +public: + explicit MouseButton(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + bool GetStatus() const override { + return mouse_input->GetMouseState(button).pressed; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseButton>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseButtonFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseButtonFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseButtonFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +class MouseAnalog final : public Input::AnalogDevice { +public: + explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, + float deadzone_, float range_, const MouseInput::Mouse* mouse_input_) + : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), + deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {} + + float GetAxis(u32 axis) const { + std::lock_guard lock{mutex}; + const auto axis_value = + static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis)); + return axis_value * Settings::values.mouse_panning_sensitivity / (100.0f * range); + } + + std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_axis_y); + if (invert_x) { + x = -x; + } + if (invert_y) { + y = -y; + } + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return {x, y}; + } + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return {x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)}; + } + return {0.0f, 0.0f}; + } + + std::tuple<float, float> GetRawStatus() const override { + const float x = GetAxis(axis_x); + const float y = GetAxis(axis_y); + return {x, y}; + } + + Input::AnalogProperties GetAnalogProperties() const override { + return {deadzone, range, 0.5f}; + } + +private: + const u32 button; + const u32 axis_x; + const u32 axis_y; + const bool invert_x; + const bool invert_y; + const float deadzone; + const float range; + const MouseInput::Mouse* mouse_input; + mutable std::mutex mutex; +}; + +/// An analog device factory that creates analog devices from GC Adapter +MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +/** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ +std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast<u32>(params.Get("port", 0)); + const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); + const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + const std::string invert_x_value = params.Get("invert_x", "+"); + const std::string invert_y_value = params.Get("invert_y", "+"); + const bool invert_x = invert_x_value == "-"; + const bool invert_y = invert_y_value == "-"; + + return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, + mouse_input.get()); +} + +void MouseAnalogFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseAnalogFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +Common::ParamPackage MouseAnalogFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("port", static_cast<u16>(pad.button)); + params.Set("axis_x", 0); + params.Set("axis_y", 1); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); + return params; + } + } + return params; +} + +class MouseMotion final : public Input::MotionDevice { +public: + explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + Input::MotionStatus GetStatus() const override { + return mouse_input->GetMouseState(button).motion; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseMotion>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseMotionFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseMotionFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseMotionFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +class MouseTouch final : public Input::TouchDevice { +public: + explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + Input::TouchStatus GetStatus() const override { + return mouse_input->GetMouseState(button).touch; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseTouch>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseTouchFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseTouchFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseTouchFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h new file mode 100644 index 000000000..cf331293b --- /dev/null +++ b/src/input_common/mouse/mouse_poller.h @@ -0,0 +1,109 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" +#include "input_common/mouse/mouse_input.h" + +namespace InputCommon { + +/** + * A button device factory representing a mouse. It receives mouse events and forward them + * to all button devices it created. + */ +class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + /** + * Creates a button device from a button press + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// An analog device factory that creates analog devices from mouse +class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// A motion device factory that creates motion devices from mouse +class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> { +public: + explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// An touch device factory that creates touch devices from mouse +class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: + explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index f3554be9a..42bbf14d4 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -23,7 +23,7 @@ public: /// Unregisters SDL device factories and shut them down. virtual ~State() = default; - virtual Pollers GetPollers(Polling::DeviceType type) { + virtual Pollers GetPollers(Polling::DeviceType) { return {}; } diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index e865a6831..a88ae452f 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -3,7 +3,9 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <atomic> +#include <chrono> #include <cmath> #include <functional> #include <mutex> @@ -20,6 +22,7 @@ #include "common/param_package.h" #include "common/threadsafe_queue.h" #include "core/frontend/input.h" +#include "input_common/motion_input.h" #include "input_common/sdl/sdl_impl.h" #include "input_common/settings.h" @@ -53,9 +56,9 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) { class SDLJoystick { public: SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* gamecontroller) + SDL_GameController* game_controller) : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{gamecontroller, &SDL_GameControllerClose} {} + sdl_controller{game_controller, &SDL_GameControllerClose} {} void SetButton(int button, bool value) { std::lock_guard lock{mutex}; @@ -74,7 +77,21 @@ public: float GetAxis(int axis, float range) const { std::lock_guard lock{mutex}; - return state.axes.at(axis) / (32767.0f * range); + return static_cast<float>(state.axes.at(axis)) / (32767.0f * range); + } + + bool RumblePlay(u16 amp_low, u16 amp_high) { + constexpr u32 rumble_max_duration_ms = 1000; + + if (sdl_controller) { + return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, + rumble_max_duration_ms) == 0; + } else if (sdl_joystick) { + return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, + rumble_max_duration_ms) == 0; + } + + return false; } std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { @@ -94,6 +111,10 @@ public: return std::make_tuple(x, y); } + const MotionInput& GetMotion() const { + return motion; + } + void SetHat(int hat, Uint8 direction) { std::lock_guard lock{mutex}; state.hats.insert_or_assign(hat, direction); @@ -121,15 +142,15 @@ public: return sdl_joystick.get(); } - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { - sdl_controller.reset(controller); - sdl_joystick.reset(joystick); - } - SDL_GameController* GetSDLGameController() const { return sdl_controller.get(); } + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_joystick.reset(joystick); + sdl_controller.reset(controller); + } + private: struct State { std::unordered_map<int, bool> buttons; @@ -141,74 +162,66 @@ private: std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; mutable std::mutex mutex; + + // Motion is initialized without PID values as motion input is not aviable for SDL2 + MotionInput motion{0.0f, 0.0f, 0.0f}; }; std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { std::lock_guard lock{joystick_map_mutex}; const auto it = joystick_map.find(guid); + if (it != joystick_map.end()) { while (it->second.size() <= static_cast<std::size_t>(port)) { auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr, nullptr); it->second.emplace_back(std::move(joystick)); } - return it->second[port]; + + return it->second[static_cast<std::size_t>(port)]; } + auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); + return joystick_map[guid].emplace_back(std::move(joystick)); } std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id); const std::string guid = GetGUID(sdl_joystick); std::lock_guard lock{joystick_map_mutex}; const auto map_it = joystick_map.find(guid); - if (map_it != joystick_map.end()) { - const auto vec_it = - std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { - return sdl_joystick == joystick->GetSDLJoystick(); - }); - if (vec_it != map_it->second.end()) { - // This is the common case: There is already an existing SDL_Joystick maped to a - // SDLJoystick. return the SDLJoystick - return *vec_it; - } - - // Search for a SDLJoystick without a mapped SDL_Joystick... - const auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [](const std::shared_ptr<SDLJoystick>& joystick) { - return !joystick->GetSDLJoystick(); - }); - if (nullptr_it != map_it->second.end()) { - // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller); - return *nullptr_it; - } - - // There is no SDLJoystick without a mapped SDL_Joystick - // Create a new SDLJoystick - const int port = static_cast<int>(map_it->second.size()); - auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller); - return map_it->second.emplace_back(std::move(joystick)); - } - - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller); - return joystick_map[guid].emplace_back(std::move(joystick)); + + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; } void SDLState::InitJoystick(int joystick_index) { SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); SDL_GameController* sdl_gamecontroller = nullptr; + if (SDL_IsGameController(joystick_index)) { sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); } + if (!sdl_joystick) { - LOG_ERROR(Input, "failed to open joystick {}", joystick_index); + LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); return; } + const std::string guid = GetGUID(sdl_joystick); std::lock_guard lock{joystick_map_mutex}; @@ -217,14 +230,17 @@ void SDLState::InitJoystick(int joystick_index) { joystick_map[guid].emplace_back(std::move(joystick)); return; } + auto& joystick_guid_list = joystick_map[guid]; - const auto it = std::find_if( - joystick_guid_list.begin(), joystick_guid_list.end(), - [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); - if (it != joystick_guid_list.end()) { - (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); return; } + const int port = static_cast<int>(joystick_guid_list.size()); auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); joystick_guid_list.emplace_back(std::move(joystick)); @@ -233,22 +249,15 @@ void SDLState::InitJoystick(int joystick_index) { void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { const std::string guid = GetGUID(sdl_joystick); - std::shared_ptr<SDLJoystick> joystick; - { - std::lock_guard lock{joystick_map_mutex}; - // This call to guid is safe since the joystick is guaranteed to be in the map - const auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - joystick = *joystick_it; - } - - // Destruct SDL_Joystick outside the lock guard because SDL can internally call the - // event callback which locks the mutex again. - joystick->SetSDLJoystick(nullptr, nullptr); + std::lock_guard lock{joystick_map_mutex}; + // This call to guid is safe since the joystick is guaranteed to be in the map + const auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + (*joystick_it)->SetSDLJoystick(nullptr, nullptr); } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -346,19 +355,36 @@ private: class SDLAnalog final : public Input::AnalogDevice { public: - SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_, - float range_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), - range(range_) {} + explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, + bool invert_x_, bool invert_y_, float deadzone_, float range_) + : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), + invert_y(invert_y_), deadzone(deadzone_), range(range_) {} std::tuple<float, float> GetStatus() const override { - const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); + auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); const float r = std::sqrt((x * x) + (y * y)); + if (invert_x) { + x = -x; + } + if (invert_y) { + y = -y; + } + if (r > deadzone) { return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), y / r * (r - deadzone) / (1 - deadzone)); } - return std::make_tuple<float, float>(0.0f, 0.0f); + return {}; + } + + std::tuple<float, float> GetRawStatus() const override { + const float x = joystick->GetAxis(axis_x, range); + const float y = joystick->GetAxis(axis_y, range); + return {x, -y}; + } + + Input::AnalogProperties GetAnalogProperties() const override { + return {deadzone, range, 0.5f}; } bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { @@ -381,10 +407,100 @@ private: std::shared_ptr<SDLJoystick> joystick; const int axis_x; const int axis_y; + const bool invert_x; + const bool invert_y; const float deadzone; const float range; }; +class SDLVibration final : public Input::VibrationDevice { +public: + explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_) + : joystick(std::move(joystick_)) {} + + u8 GetStatus() const override { + joystick->RumblePlay(1, 1); + return joystick->RumblePlay(0, 0); + } + + bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, + [[maybe_unused]] f32 freq_high) const override { + const auto process_amplitude = [](f32 amplitude) { + return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF); + }; + + const auto processed_amp_low = process_amplitude(amp_low); + const auto processed_amp_high = process_amplitude(amp_high); + + return joystick->RumblePlay(processed_amp_low, processed_amp_high); + } + +private: + std::shared_ptr<SDLJoystick> joystick; +}; + +class SDLDirectionMotion final : public Input::MotionDevice { +public: + explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) + : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} + + Input::MotionStatus GetStatus() const override { + if (joystick->GetHatDirection(hat, direction)) { + return joystick->GetMotion().GetRandomMotion(2, 6); + } + return joystick->GetMotion().GetRandomMotion(0, 0); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int hat; + Uint8 direction; +}; + +class SDLAxisMotion final : public Input::MotionDevice { +public: + explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, + bool trigger_if_greater_) + : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), + trigger_if_greater(trigger_if_greater_) {} + + Input::MotionStatus GetStatus() const override { + const float axis_value = joystick->GetAxis(axis, 1.0f); + bool trigger = axis_value < threshold; + if (trigger_if_greater) { + trigger = axis_value > threshold; + } + + if (trigger) { + return joystick->GetMotion().GetRandomMotion(2, 6); + } + return joystick->GetMotion().GetRandomMotion(0, 0); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int axis; + float threshold; + bool trigger_if_greater; +}; + +class SDLButtonMotion final : public Input::MotionDevice { +public: + explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_) + : joystick(std::move(joystick_)), button(button_) {} + + Input::MotionStatus GetStatus() const override { + if (joystick->GetButton(button)) { + return joystick->GetMotion().GetRandomMotion(2, 6); + } + return joystick->GetMotion().GetRandomMotion(0, 0); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int button; +}; + /// A button device factory that creates button devices from SDL joystick class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { public: @@ -465,7 +581,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { public: explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} /** - * Creates analog device from joystick axes + * Creates an analog device from joystick axes * @param params contains parameters for creating the device: * - "guid": the guid of the joystick to bind * - "port": the nth joystick of the same type @@ -479,12 +595,101 @@ public: const int axis_y = params.Get("axis_y", 1); const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + const std::string invert_x_value = params.Get("invert_x", "+"); + const std::string invert_y_value = params.Get("invert_y", "+"); + const bool invert_x = invert_x_value == "-"; + const bool invert_y = invert_y_value == "-"; auto joystick = state.GetSDLJoystickByGUID(guid, port); // This is necessary so accessing GetAxis with axis_x and axis_y won't crash joystick->SetAxis(axis_x, 0); joystick->SetAxis(axis_y, 0); - return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range); + return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone, + range); + } + +private: + SDLState& state; +}; + +/// An vibration device factory that creates vibration devices from SDL joystick +class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> { +public: + explicit SDLVibrationFactory(SDLState& state_) : state(state_) {} + /** + * Creates a vibration device from a joystick + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + */ + std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port)); + } + +private: + SDLState& state; +}; + +/// A motion device factory that creates motion devices from SDL joystick +class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> { +public: + explicit SDLMotionFactory(SDLState& state_) : state(state_) {} + /** + * Creates motion device from joystick axes + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + */ + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + + auto joystick = state.GetSDLJoystickByGUID(guid, port); + + if (params.Has("hat")) { + const int hat = params.Get("hat", 0); + const std::string direction_name = params.Get("direction", ""); + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + // This is necessary so accessing GetHat with hat won't crash + joystick->SetHat(hat, SDL_HAT_CENTERED); + return std::make_unique<SDLDirectionMotion>(joystick, hat, direction); + } + + if (params.Has("axis")) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.5f); + const std::string direction_name = params.Get("direction", ""); + bool trigger_if_greater; + if (direction_name == "+") { + trigger_if_greater = true; + } else if (direction_name == "-") { + trigger_if_greater = false; + } else { + trigger_if_greater = true; + LOG_ERROR(Input, "Unknown direction {}", direction_name); + } + // This is necessary so accessing GetAxis with axis won't crash + joystick->SetAxis(axis, 0); + return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater); + } + + const int button = params.Get("button", 0); + // This is necessary so accessing GetButton with button won't crash + joystick->SetButton(button, false); + return std::make_unique<SDLButtonMotion>(joystick, button); } private: @@ -493,18 +698,22 @@ private: SDLState::SDLState() { using namespace Input; - analog_factory = std::make_shared<SDLAnalogFactory>(*this); button_factory = std::make_shared<SDLButtonFactory>(*this); - RegisterFactory<AnalogDevice>("sdl", analog_factory); + analog_factory = std::make_shared<SDLAnalogFactory>(*this); + vibration_factory = std::make_shared<SDLVibrationFactory>(*this); + motion_factory = std::make_shared<SDLMotionFactory>(*this); RegisterFactory<ButtonDevice>("sdl", button_factory); + RegisterFactory<AnalogDevice>("sdl", analog_factory); + RegisterFactory<VibrationDevice>("sdl", vibration_factory); + RegisterFactory<MotionDevice>("sdl", motion_factory); - // If the frontend is going to manage the event loop, then we dont start one here - start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); + // If the frontend is going to manage the event loop, then we don't start one here + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); return; } - has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0; if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); } @@ -524,7 +733,7 @@ SDLState::SDLState() { using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); - std::this_thread::sleep_for(5ms); + std::this_thread::sleep_for(1ms); } }); } @@ -539,6 +748,8 @@ SDLState::~SDLState() { using namespace Input; UnregisterFactory<ButtonDevice>("sdl"); UnregisterFactory<AnalogDevice>("sdl"); + UnregisterFactory<VibrationDevice>("sdl"); + UnregisterFactory<MotionDevice>("sdl"); CloseJoysticks(); SDL_DelEventWatch(&SDLEventWatcher, this); @@ -555,8 +766,7 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() { std::vector<Common::ParamPackage> devices; for (const auto& [key, value] : joystick_map) { for (const auto& joystick : value) { - auto joy = joystick->GetSDLJoystick(); - if (auto controller = joystick->GetSDLGameController()) { + if (auto* const controller = joystick->GetSDLGameController()) { std::string name = fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort()); devices.emplace_back(Common::ParamPackage{ @@ -565,7 +775,7 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() { {"guid", joystick->GetGUID()}, {"port", std::to_string(joystick->GetPort())}, }); - } else if (joy) { + } else if (auto* const joy = joystick->GetSDLJoystick()) { std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort()); devices.emplace_back(Common::ParamPackage{ {"class", "sdl"}, @@ -580,11 +790,11 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() { } namespace { -Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis, - float value = 0.1) { +Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, + float value = 0.1f) { Common::ParamPackage params({{"engine", "sdl"}}); params.Set("port", port); - params.Set("guid", guid); + params.Set("guid", std::move(guid)); params.Set("axis", axis); if (value > 0) { params.Set("direction", "+"); @@ -596,19 +806,19 @@ Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid return params; } -Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) { +Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) { Common::ParamPackage params({{"engine", "sdl"}}); params.Set("port", port); - params.Set("guid", guid); + params.Set("guid", std::move(guid)); params.Set("button", button); return params; } -Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) { +Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) { Common::ParamPackage params({{"engine", "sdl"}}); params.Set("port", port); - params.Set("guid", guid); + params.Set("guid", std::move(guid)); params.Set("hat", hat); switch (value) { case SDL_HAT_UP: @@ -632,19 +842,56 @@ Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { switch (event.type) { case SDL_JOYAXISMOTION: { - const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - event.jaxis.axis, event.jaxis.value); + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { + return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jaxis.axis), + event.jaxis.value); + } + break; } case SDL_JOYBUTTONUP: { - const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); - return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - event.jbutton.button); + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { + return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jbutton.button)); + } + break; } case SDL_JOYHATMOTION: { - const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); - return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - event.jhat.hat, event.jhat.value); + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { + return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jhat.hat), + static_cast<s32>(event.jhat.value)); + } + break; + } + } + return {}; +} + +Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) { + switch (event.type) { + case SDL_JOYAXISMOTION: { + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { + return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jaxis.axis), + event.jaxis.value); + } + break; + } + case SDL_JOYBUTTONUP: { + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { + return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jbutton.button)); + } + break; + } + case SDL_JOYHATMOTION: { + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { + return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + static_cast<s32>(event.jhat.hat), + static_cast<s32>(event.jhat.value)); + } + break; } } return {}; @@ -653,6 +900,8 @@ Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Eve Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) { switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_NONE: + break; case SDL_CONTROLLER_BINDTYPE_AXIS: return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); case SDL_CONTROLLER_BINDTYPE_BUTTON: @@ -672,60 +921,69 @@ Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& gui params.Set("guid", guid); params.Set("axis_x", axis_x); params.Set("axis_y", axis_y); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); return params; } } // Anonymous namespace ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { - // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. - // We will add those afterwards - // This list also excludes Screenshot since theres not really a mapping for that - std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerButton> - switch_to_sdl_button = { - {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - }; if (!params.Has("guid") || !params.Has("port")) { return {}; } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - auto controller = joystick->GetSDLGameController(); - if (!controller) { + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { return {}; } - ButtonMapping mapping{}; + // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // We will add those afterwards + // This list also excludes Screenshot since theres not really a mapping for that + using ButtonBindings = + std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; + static constexpr ButtonBindings switch_to_sdl_button{{ + {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }}; + + // Add the missing bindings for ZL/ZR + using ZBindings = + std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; + static constexpr ZBindings switch_to_sdl_axis{{ + {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + }}; + + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); - mapping[switch_button] = - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } - - // Add the missing bindings for ZL/ZR - std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerAxis> switch_to_sdl_axis = - { - {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, - {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, - }; for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); - mapping[switch_button] = - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } return mapping; @@ -736,8 +994,8 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa return {}; } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - auto controller = joystick->GetSDLGameController(); - if (!controller) { + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { return {}; } @@ -746,16 +1004,18 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); const auto& binding_left_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - mapping[Settings::NativeAnalog::LStick] = - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + binding_left_x.value.axis, + binding_left_y.value.axis)); const auto& binding_right_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); const auto& binding_right_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - mapping[Settings::NativeAnalog::RStick] = - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_right_x.value.axis, binding_right_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + binding_right_x.value.axis, + binding_right_y.value.axis)); return mapping; } @@ -764,7 +1024,7 @@ class SDLPoller : public InputCommon::Polling::DevicePoller { public: explicit SDLPoller(SDLState& state_) : state(state_) {} - void Start(const std::string& device_id) override { + void Start([[maybe_unused]] const std::string& device_id) override { state.event_queue.Clear(); state.polling = true; } @@ -791,19 +1051,91 @@ public: } return {}; } - std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) { + [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) { switch (event.type) { case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + if (!axis_memory.count(event.jaxis.which) || + !axis_memory[event.jaxis.which].count(event.jaxis.axis)) { + axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value; + axis_event_count[event.jaxis.which][event.jaxis.axis] = 1; break; + } else { + axis_event_count[event.jaxis.which][event.jaxis.axis]++; + // The joystick and axis exist in our map if we take this branch, so no checks + // needed + if (std::abs( + (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) / + 32767.0) < 0.5) { + break; + } else { + if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 && + IsAxisAtPole(event.jaxis.value) && + IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) { + // If we have exactly two events and both are near a pole, this is + // likely a digital input masquerading as an analog axis; Instead of + // trying to look at the direction the axis travelled, assume the first + // event was press and the second was release; This should handle most + // digital axes while deferring to the direction of travel for analog + // axes + event.jaxis.value = static_cast<Sint16>( + std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis])); + } else { + // There are more than two events, so this is likely a true analog axis, + // check the direction it travelled + event.jaxis.value = static_cast<Sint16>(std::copysign( + 32767, + event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis])); + } + axis_memory.clear(); + axis_event_count.clear(); + } } [[fallthrough]]; case SDL_JOYBUTTONUP: case SDL_JOYHATMOTION: return {SDLEventToButtonParamPackage(state, event)}; } + return std::nullopt; + } + +private: + // Determine whether an axis value is close to an extreme or center + // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per + // axis, which is why the center must be considered a pole + bool IsAxisAtPole(int16_t value) const { + return std::abs(value) >= 32767 || std::abs(value) < 327; + } + std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory; + std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count; +}; + +class SDLMotionPoller final : public SDLPoller { +public: + explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {} + + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (state.event_queue.Pop(event)) { + const auto package = FromEvent(event); + if (package) { + return *package; + } + } return {}; } + [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const { + switch (event.type) { + case SDL_JOYAXISMOTION: + if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + break; + } + [[fallthrough]]; + case SDL_JOYBUTTONUP: + case SDL_JOYHATMOTION: + return {SDLEventToMotionParamPackage(state, event)}; + } + return std::nullopt; + } }; /** @@ -818,7 +1150,6 @@ public: void Start(const std::string& device_id) override { SDLPoller::Start(device_id); - // Load the game controller // Reset stored axes analog_x_axis = -1; analog_y_axis = -1; @@ -831,52 +1162,34 @@ public: if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } - // Simplify controller config by testing if game controller support is enabled. if (event.type == SDL_JOYAXISMOTION) { const auto axis = event.jaxis.axis; - const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - const auto controller = joystick->GetSDLGameController(); - if (controller) { - const auto axis_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) - .value.axis; - const auto axis_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) - .value.axis; - const auto axis_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) - .value.axis; - const auto axis_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) - .value.axis; - - if (axis == axis_left_x || axis == axis_left_y) { - analog_x_axis = axis_left_x; - analog_y_axis = axis_left_y; - break; - } else if (axis == axis_right_x || axis == axis_right_y) { - analog_x_axis = axis_right_x; - analog_y_axis = axis_right_y; - break; - } + // In order to return a complete analog param, we need inputs for both axes. + // First we take the x-axis (horizontal) input, then the y-axis (vertical) input. + if (analog_x_axis == -1) { + analog_x_axis = axis; + } else if (analog_y_axis == -1 && analog_x_axis != axis) { + analog_y_axis = axis; + } + } else { + // If the press wasn't accepted as a joy axis, check for a button press + auto button_press = button_poller.FromEvent(event); + if (button_press) { + return *button_press; } - } - - // If the press wasn't accepted as a joy axis, check for a button press - auto button_press = button_poller.FromEvent(event); - if (button_press) { - return *button_press; } } if (analog_x_axis != -1 && analog_y_axis != -1) { - const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - analog_x_axis, analog_y_axis); - analog_x_axis = -1; - analog_y_axis = -1; - return params; + if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { + auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + analog_x_axis, analog_y_axis); + analog_x_axis = -1; + analog_y_axis = -1; + return params; + } } + return {}; } @@ -897,6 +1210,9 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { case InputCommon::Polling::DeviceType::Button: pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); break; + case InputCommon::Polling::DeviceType::Motion: + pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this)); + break; } return pollers; diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index bd19ba61d..08044b00d 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -21,6 +21,8 @@ namespace InputCommon::SDL { class SDLAnalogFactory; class SDLButtonFactory; +class SDLMotionFactory; +class SDLVibrationFactory; class SDLJoystick; class SDLState : public State { @@ -71,6 +73,8 @@ private: std::shared_ptr<SDLButtonFactory> button_factory; std::shared_ptr<SDLAnalogFactory> analog_factory; + std::shared_ptr<SDLVibrationFactory> vibration_factory; + std::shared_ptr<SDLMotionFactory> motion_factory; bool start_thread = false; std::atomic<bool> initialized = false; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index 80c719cf4..557e7a9a0 100644 --- a/src/input_common/settings.cpp +++ b/src/input_common/settings.cpp @@ -21,6 +21,20 @@ const std::array<const char*, NumAnalogs> mapping = {{ }}; } +namespace NativeVibration { +const std::array<const char*, NumVibrations> mapping = {{ + "left_vibration_device", + "right_vibration_device", +}}; +} + +namespace NativeMotion { +const std::array<const char*, NumMotions> mapping = {{ + "motionleft", + "motionright", +}}; +} + namespace NativeMouseButton { const std::array<const char*, NumMouseButtons> mapping = {{ "left", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 2d258960b..a59f5d461 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -66,6 +66,36 @@ constexpr int NUM_STICKS_HID = NumAnalogs; extern const std::array<const char*, NumAnalogs> mapping; } // namespace NativeAnalog +namespace NativeVibration { +enum Values : int { + LeftVibrationDevice, + RightVibrationDevice, + + NumVibrations, +}; + +constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice; +constexpr int VIBRATION_HID_END = NumVibrations; +constexpr int NUM_VIBRATIONS_HID = NumVibrations; + +extern const std::array<const char*, NumVibrations> mapping; +}; // namespace NativeVibration + +namespace NativeMotion { +enum Values : int { + MotionLeft, + MotionRight, + + NumMotions, +}; + +constexpr int MOTION_HID_BEGIN = MotionLeft; +constexpr int MOTION_HID_END = NumMotions; +constexpr int NUM_MOTIONS_HID = NumMotions; + +extern const std::array<const char*, NumMotions> mapping; +} // namespace NativeMotion + namespace NativeMouseButton { enum Values { Left, @@ -290,8 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; } // namespace NativeKeyboard -using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; +using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; +using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>; +using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>; + using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; @@ -307,6 +340,7 @@ enum class ControllerType { LeftJoycon, RightJoycon, Handheld, + GameCube, }; struct PlayerInput { @@ -314,8 +348,11 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; - std::string lstick_mod; - std::string rstick_mod; + VibrationsRaw vibrations; + MotionsRaw motions; + + bool vibration_enabled; + int vibration_strength; u32 body_color_left; u32 body_color_right; diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp new file mode 100644 index 000000000..ffbe4f2ed --- /dev/null +++ b/src/input_common/touch_from_button.cpp @@ -0,0 +1,52 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" +#include "input_common/touch_from_button.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Input::TouchDevice { +public: + TouchFromButtonDevice() { + const auto button_index = + static_cast<std::size_t>(Settings::values.touch_from_button_map_index); + const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons; + + for (const auto& config_entry : buttons) { + const Common::ParamPackage package{config_entry}; + map.emplace_back( + Input::CreateDevice<Input::ButtonDevice>(config_entry), + std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)), + std::clamp(package.Get("y", 0), 0, + static_cast<int>(Layout::ScreenUndocked::Height))); + } + } + + Input::TouchStatus GetStatus() const override { + Input::TouchStatus touch_status{}; + for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) { + const bool state = std::get<0>(map[id])->GetStatus(); + if (state) { + const float x = static_cast<float>(std::get<1>(map[id])) / + static_cast<int>(Layout::ScreenUndocked::Width); + const float y = static_cast<float>(std::get<2>(map[id])) / + static_cast<int>(Layout::ScreenUndocked::Height); + touch_status[id] = {x, y, true}; + } + } + return touch_status; + } + +private: + // A vector of the mapped button, its x and its y-coordinate + std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; +}; + +std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) { + return std::make_unique<TouchFromButtonDevice>(); +} + +} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h new file mode 100644 index 000000000..8b4d1aa96 --- /dev/null +++ b/src/input_common/touch_from_button.h @@ -0,0 +1,23 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * A touch device factory that takes a list of button devices and combines them into a touch device. + */ +class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { +public: + /** + * Creates a touch device from a list of button devices + */ + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 3f4eaf448..c4afa4174 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -2,14 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> -#include <array> #include <chrono> #include <cstring> #include <functional> #include <thread> #include <boost/asio.hpp> #include "common/logging/log.h" +#include "core/settings.h" #include "input_common/udp/client.h" #include "input_common/udp/protocol.h" @@ -27,11 +26,11 @@ class Socket { public: using clock = std::chrono::system_clock; - explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, - SocketCallback callback) - : callback(std::move(callback)), timer(io_service), - socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id), - pad_index(pad_index) { + explicit Socket(const std::string& host, u16 port, std::size_t pad_index_, u32 client_id_, + SocketCallback callback_) + : callback(std::move(callback_)), timer(io_service), + socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id_), + pad_index(pad_index_) { boost::system::error_code ec{}; auto ipv4 = boost::asio::ip::make_address_v4(host, ec); if (ec.value() != boost::system::errc::success) { @@ -64,7 +63,7 @@ public: } private: - void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { + void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) { if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { switch (*type) { case Type::Version: { @@ -91,16 +90,20 @@ private: StartReceive(); } - void HandleSend(const boost::system::error_code& error) { + void HandleSend(const boost::system::error_code&) { boost::system::error_code _ignored{}; // Send a request for getting port info for the pad - Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; + const Request::PortInfo port_info{1, {static_cast<u8>(pad_index), 0, 0, 0}}; const auto port_message = Request::Create(port_info, client_id); std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); // Send a request for getting pad data for the pad - Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; + const Request::PadData pad_data{ + Request::PadData::Flags::Id, + static_cast<u8>(pad_index), + EMPTY_MAC_ADDRESS, + }; const auto pad_message = Request::Create(pad_data, client_id); std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); @@ -113,7 +116,7 @@ private: udp::socket socket; u32 client_id{}; - u8 pad_index{}; + std::size_t pad_index{}; static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); @@ -131,92 +134,301 @@ static void SocketLoop(Socket* socket) { socket->Loop(); } -Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, - u8 pad_index, u32 client_id) - : status(std::move(status)) { - StartCommunication(host, port, pad_index, client_id); +Client::Client() { + LOG_INFO(Input, "Udp Initialization started"); + finger_id.fill(MAX_TOUCH_FINGERS); + ReloadSockets(); } Client::~Client() { - socket->Stop(); - thread.join(); + Reset(); } -void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - socket->Stop(); - thread.join(); - StartCommunication(host, port, pad_index, client_id); +Client::ClientData::ClientData() = default; + +Client::ClientData::~ClientData() = default; + +std::vector<Common::ParamPackage> Client::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + for (std::size_t client = 0; client < clients.size(); client++) { + if (!DeviceConnected(client)) { + continue; + } + std::string name = fmt::format("UDP Controller {}", client); + devices.emplace_back(Common::ParamPackage{ + {"class", "cemuhookudp"}, + {"display", std::move(name)}, + {"port", std::to_string(client)}, + }); + } + return devices; } -void Client::OnVersion(Response::Version data) { +bool Client::DeviceConnected(std::size_t client) const { + // Use last timestamp to detect if the socket has stopped sending data + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = + static_cast<u64>(std::chrono::duration_cast<std::chrono::milliseconds>( + now - clients[client].last_motion_update) + .count()); + return time_difference < 1000 && clients[client].active == 1; +} + +void Client::ReloadSockets() { + Reset(); + + std::stringstream servers_ss(Settings::values.udp_input_servers); + std::string server_token; + std::size_t client = 0; + while (std::getline(servers_ss, server_token, ',')) { + if (client == MAX_UDP_CLIENTS) { + break; + } + std::stringstream server_ss(server_token); + std::string token; + std::getline(server_ss, token, ':'); + std::string udp_input_address = token; + std::getline(server_ss, token, ':'); + char* temp; + const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0)); + if (*temp != '\0') { + LOG_ERROR(Input, "Port number is not valid {}", token); + continue; + } + + for (std::size_t pad = 0; pad < 4; ++pad) { + const std::size_t client_number = + GetClientNumber(udp_input_address, udp_input_port, pad); + if (client_number != MAX_UDP_CLIENTS) { + LOG_ERROR(Input, "Duplicated UDP servers found"); + continue; + } + StartCommunication(client++, udp_input_address, udp_input_port, pad, 24872); + } + } +} + +std::size_t Client::GetClientNumber(std::string_view host, u16 port, std::size_t pad) const { + for (std::size_t client = 0; client < clients.size(); client++) { + if (clients[client].active == -1) { + continue; + } + if (clients[client].host == host && clients[client].port == port && + clients[client].pad_index == pad) { + return client; + } + } + return MAX_UDP_CLIENTS; +} + +void Client::OnVersion([[maybe_unused]] Response::Version data) { LOG_TRACE(Input, "Version packet received: {}", data.version); } -void Client::OnPortInfo(Response::PortInfo data) { +void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { LOG_TRACE(Input, "PortInfo packet received: {}", data.model); } -void Client::OnPadData(Response::PadData data) { +void Client::OnPadData(Response::PadData data, std::size_t client) { + // Accept packets only for the correct pad + if (static_cast<u8>(clients[client].pad_index) != data.info.id) { + return; + } + LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter <= packet_sequence) { + if (data.packet_counter == clients[client].packet_sequence) { LOG_WARNING( Input, "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - packet_sequence, data.packet_counter); + clients[client].packet_sequence, data.packet_counter); return; } - packet_sequence = data.packet_counter; - // TODO: Check how the Switch handles motions and how the CemuhookUDP motion - // directions correspond to the ones of the Switch - Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); - Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); + clients[client].active = static_cast<s8>(data.info.is_pad_active); + clients[client].packet_sequence = data.packet_counter; + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = + static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>( + now - clients[client].last_motion_update) + .count()); + clients[client].last_motion_update = now; + const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; + clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f); + clients[client].motion.UpdateRotation(time_difference); + clients[client].motion.UpdateOrientation(time_difference); + { - std::lock_guard guard(status->update_mutex); + std::lock_guard guard(clients[client].status.update_mutex); + clients[client].status.motion_status = clients[client].motion.GetMotion(); + + for (std::size_t id = 0; id < data.touch.size(); ++id) { + UpdateTouchInput(data.touch[id], client, id); + } + + if (configuring) { + const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope(); + const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration(); + UpdateYuzuSettings(client, accelerometer, gyroscope); + } + } +} - status->motion_status = {accel, gyro}; +void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, + std::size_t pad_index, u32 client_id) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this, client](Response::PadData data) { OnPadData(data, client); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}:{}", host, port, + pad_index); + clients[client].host = host; + clients[client].port = port; + clients[client].pad_index = pad_index; + clients[client].active = 0; + clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; + // Set motion parameters + // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode + // Real HW values are unknown, 0.0001 is an approximate to Standard + clients[client].motion.SetGyroThreshold(0.0001f); +} - // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates - // between a simple "tap" and a hard press that causes the touch screen to click. - const bool is_active = data.touch_1.is_active != 0; +void Client::Reset() { + for (auto& client : clients) { + if (client.thread.joinable()) { + client.active = -1; + client.socket->Stop(); + client.thread.join(); + } + } +} - float x = 0; - float y = 0; +void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro) { + if (gyro.Length() > 0.2f) { + LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client, + gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]); + } + UDPPadStatus pad{ + .host = clients[client].host, + .port = clients[client].port, + .pad_index = clients[client].pad_index, + }; + for (std::size_t i = 0; i < 3; ++i) { + if (gyro[i] > 5.0f || gyro[i] < -5.0f) { + pad.motion = static_cast<PadMotion>(i); + pad.motion_value = gyro[i]; + pad_queue.Push(pad); + } + if (acc[i] > 1.75f || acc[i] < -1.75f) { + pad.motion = static_cast<PadMotion>(i + 3); + pad.motion_value = acc[i]; + pad_queue.Push(pad); + } + } +} - if (is_active && status->touch_calibration) { - const u16 min_x = status->touch_calibration->min_x; - const u16 max_x = status->touch_calibration->max_x; - const u16 min_y = status->touch_calibration->min_y; - const u16 max_y = status->touch_calibration->max_y; +std::optional<std::size_t> Client::GetUnusedFingerID() const { + std::size_t first_free_id = 0; + while (first_free_id < MAX_TOUCH_FINGERS) { + if (!std::get<2>(touch_status[first_free_id])) { + return first_free_id; + } else { + first_free_id++; + } + } + return std::nullopt; +} - x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / - static_cast<float>(max_x - min_x); - y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / - static_cast<float>(max_y - min_y); +void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) { + // TODO: Use custom calibration per device + const Common::ParamPackage touch_param(Settings::values.touch_device); + const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100)); + const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50)); + const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800)); + const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850)); + const std::size_t touch_id = client * 2 + id; + if (touch_pad.is_active) { + if (finger_id[touch_id] == MAX_TOUCH_FINGERS) { + const auto first_free_id = GetUnusedFingerID(); + if (!first_free_id) { + // Invalid finger id skip to next input + return; + } + finger_id[touch_id] = *first_free_id; } + auto& [x, y, pressed] = touch_status[finger_id[touch_id]]; + x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) / + static_cast<float>(max_x - min_x); + y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) / + static_cast<float>(max_y - min_y); + pressed = true; + return; + } - status->touch_status = {x, y, is_active}; + if (finger_id[touch_id] != MAX_TOUCH_FINGERS) { + touch_status[finger_id[touch_id]] = {}; + finger_id[touch_id] = MAX_TOUCH_FINGERS; } } -void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this](Response::PadData data) { OnPadData(data); }}; - LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); - thread = std::thread{SocketLoop, this->socket.get()}; +void Client::BeginConfiguration() { + pad_queue.Clear(); + configuring = true; +} + +void Client::EndConfiguration() { + pad_queue.Clear(); + configuring = false; +} + +DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) { + const std::size_t client_number = GetClientNumber(host, port, pad); + if (client_number == MAX_UDP_CLIENTS) { + return clients[0].status; + } + return clients[client_number].status; +} + +const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const { + const std::size_t client_number = GetClientNumber(host, port, pad); + if (client_number == MAX_UDP_CLIENTS) { + return clients[0].status; + } + return clients[client_number].status; +} + +Input::TouchStatus& Client::GetTouchState() { + return touch_status; +} + +const Input::TouchStatus& Client::GetTouchState() const { + return touch_status; +} + +Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() { + return pad_queue; +} + +const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const { + return pad_queue; } -void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, - std::function<void()> success_callback, - std::function<void()> failure_callback) { +void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id, + const std::function<void()>& success_callback, + const std::function<void()>& failure_callback) { std::thread([=] { Common::Event success_event; - SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, - [&](Response::PadData data) { success_event.Set(); }}; + SocketCallback callback{ + .version = [](Response::Version) {}, + .port_info = [](Response::PortInfo) {}, + .pad_data = [&](Response::PadData) { success_event.Set(); }, + }; Socket socket{host, port, pad_index, client_id, std::move(callback)}; std::thread worker_thread{SocketLoop, &socket}; - bool result = success_event.WaitFor(std::chrono::seconds(8)); + const bool result = success_event.WaitFor(std::chrono::seconds(5)); socket.Stop(); worker_thread.join(); if (result) { @@ -228,7 +440,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie } CalibrationConfigurationJob::CalibrationConfigurationJob( - const std::string& host, u16 port, u8 pad_index, u32 client_id, + const std::string& host, u16 port, std::size_t pad_index, u32 client_id, std::function<void(Status)> status_callback, std::function<void(u16, u16, u16, u16)> data_callback) { @@ -241,31 +453,31 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( u16 max_y{}; Status current_status{Status::Initialized}; - SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, + SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, [&](Response::PadData data) { if (current_status == Status::Initialized) { // Receiving data means the communication is ready now current_status = Status::Ready; status_callback(current_status); } - if (!data.touch_1.is_active) { + if (data.touch[0].is_active == 0) { return; } - LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, - data.touch_1.y); - min_x = std::min(min_x, static_cast<u16>(data.touch_1.x)); - min_y = std::min(min_y, static_cast<u16>(data.touch_1.y)); + LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, + data.touch[0].y); + min_x = std::min(min_x, static_cast<u16>(data.touch[0].x)); + min_y = std::min(min_y, static_cast<u16>(data.touch[0].y)); if (current_status == Status::Ready) { // First touch - min data (min_x/min_y) current_status = Status::Stage1Completed; status_callback(current_status); } - if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && - data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { + if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD && + data.touch[0].y - min_y > CALIBRATION_THRESHOLD) { // Set the current position as max value and finishes // configuration - max_x = data.touch_1.x; - max_y = data.touch_1.y; + max_x = data.touch[0].x; + max_y = data.touch[0].y; current_status = Status::Completed; data_callback(min_x, min_y, max_x, max_y); status_callback(current_status); diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index b8c654755..a523f6124 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -12,25 +12,52 @@ #include <thread> #include <tuple> #include "common/common_types.h" +#include "common/param_package.h" #include "common/thread.h" +#include "common/threadsafe_queue.h" #include "common/vector_math.h" +#include "core/frontend/input.h" +#include "input_common/motion_input.h" namespace InputCommon::CemuhookUDP { -constexpr u16 DEFAULT_PORT = 26760; -constexpr char DEFAULT_ADDR[] = "127.0.0.1"; +constexpr char DEFAULT_SRV[] = "127.0.0.1:26760"; class Socket; namespace Response { struct PadData; struct PortInfo; +struct TouchPad; struct Version; } // namespace Response +enum class PadMotion { + GyroX, + GyroY, + GyroZ, + AccX, + AccY, + AccZ, + Undefined, +}; + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + std::string host{"127.0.0.1"}; + u16 port{26760}; + std::size_t pad_index{}; + PadMotion motion{PadMotion::Undefined}; + f32 motion_value{0.0f}; +}; + struct DeviceStatus { std::mutex update_mutex; - std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; + Input::MotionStatus motion_status; std::tuple<float, float, bool> touch_status; // calibration data for scaling the device's touch area to 3ds @@ -45,22 +72,81 @@ struct DeviceStatus { class Client { public: - explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, - u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); + // Initialize the UDP client capture and read sequence + Client(); + + // Close and release the client ~Client(); - void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, - u32 client_id = 24872); + + // Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::vector<Common::ParamPackage> GetInputDevices() const; + + bool DeviceConnected(std::size_t client) const; + void ReloadSockets(); + + Common::SPSCQueue<UDPPadStatus>& GetPadQueue(); + const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const; + + DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); + const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; + + Input::TouchStatus& GetTouchState(); + const Input::TouchStatus& GetTouchState() const; private: + struct ClientData { + ClientData(); + ~ClientData(); + + std::string host{"127.0.0.1"}; + u16 port{26760}; + std::size_t pad_index{}; + std::unique_ptr<Socket> socket; + DeviceStatus status; + std::thread thread; + u64 packet_sequence{}; + s8 active{-1}; + + // Realtime values + // motion is initalized with PID values for drift correction on joycons + InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; + std::chrono::time_point<std::chrono::steady_clock> last_motion_update; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + + // Translates configuration to client number + std::size_t GetClientNumber(std::string_view host, u16 port, std::size_t pad) const; + void OnVersion(Response::Version); void OnPortInfo(Response::PortInfo); - void OnPadData(Response::PadData); - void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + void OnPadData(Response::PadData, std::size_t client); + void StartCommunication(std::size_t client, const std::string& host, u16 port, + std::size_t pad_index, u32 client_id); + void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro); + + // Returns an unused finger id, if there is no fingers available std::nullopt will be + // returned + std::optional<std::size_t> GetUnusedFingerID() const; + + // Merges and updates all touch inputs into the touch_status array + void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id); + + bool configuring = false; - std::unique_ptr<Socket> socket; - std::shared_ptr<DeviceStatus> status; - std::thread thread; - u64 packet_sequence = 0; + // Allocate clients for 8 udp servers + static constexpr std::size_t MAX_UDP_CLIENTS = 4 * 8; + // Each client can have up 2 touch inputs + static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2; + std::array<ClientData, MAX_UDP_CLIENTS> clients{}; + Common::SPSCQueue<UDPPadStatus> pad_queue{}; + Input::TouchStatus touch_status{}; + std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{}; }; /// An async job allowing configuration of the touchpad calibration. @@ -78,7 +164,7 @@ public: * @param status_callback Callback for job status updates * @param data_callback Called when calibration data is ready */ - explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, + explicit CalibrationConfigurationJob(const std::string& host, u16 port, std::size_t pad_index, u32 client_id, std::function<void(Status)> status_callback, std::function<void(u16, u16, u16, u16)> data_callback); ~CalibrationConfigurationJob(); @@ -88,8 +174,8 @@ private: Common::Event complete_event; }; -void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, - std::function<void()> success_callback, - std::function<void()> failure_callback); +void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id, + const std::function<void()>& success_callback, + const std::function<void()>& failure_callback); } // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h index 3ba4d1fc8..a3d276697 100644 --- a/src/input_common/udp/protocol.h +++ b/src/input_common/udp/protocol.h @@ -7,7 +7,16 @@ #include <array> #include <optional> #include <type_traits> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4701) +#endif #include <boost/crc.hpp> +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "common/bit_field.h" #include "common/swap.h" @@ -93,7 +102,7 @@ static_assert(std::is_trivially_copyable_v<PadData>, /** * Creates a message with the proper header data that can be sent to the server. - * @param T data Request body to send + * @param data Request body to send * @param client_id ID of the udp client (usually not checked on the server) */ template <typename T> @@ -131,6 +140,14 @@ static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong si static_assert(std::is_trivially_copyable_v<PortInfo>, "UDP Response PortInfo is not trivially copyable"); +struct TouchPad { + u8 is_active{}; + u8 id{}; + u16_le x{}; + u16_le y{}; +}; +static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); + #pragma pack(push, 1) struct PadData { PortInfo info{}; @@ -181,12 +198,7 @@ struct PadData { u8 button_13{}; } analog_button; - struct TouchPad { - u8 is_active{}; - u8 id{}; - u16_le x{}; - u16_le y{}; - } touch_1, touch_2; + std::array<TouchPad, 2> touch; u64_le motion_timestamp; @@ -213,7 +225,6 @@ static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE, static_assert(sizeof(PadData::AnalogButton) == 12, "UDP Response AnalogButton struct has wrong size "); -static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); static_assert(sizeof(PadData::Accelerometer) == 12, "UDP Response Accelerometer struct has wrong size "); static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp index 4b347e47e..9829da6f0 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -1,105 +1,110 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include <mutex> -#include <optional> -#include <tuple> - -#include "common/param_package.h" -#include "core/frontend/input.h" -#include "core/settings.h" +#include <utility> +#include "common/assert.h" +#include "common/threadsafe_queue.h" #include "input_common/udp/client.h" #include "input_common/udp/udp.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class UDPTouchDevice final : public Input::TouchDevice { +class UDPMotion final : public Input::MotionDevice { public: - explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - std::tuple<float, float, bool> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->touch_status; + explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) + : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} + + Input::MotionStatus GetStatus() const override { + return client->GetPadState(ip, port, pad).motion_status; } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + const u16 port; + const u16 pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -class UDPMotionDevice final : public Input::MotionDevice { -public: - explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->motion_status; - } +/// A motion device factory that creates motion devices from a UDP client +UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) + : client(std::move(client_)) {} + +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the UDP port number + */ +std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) { + auto ip = params.Get("ip", "127.0.0.1"); + const auto port = static_cast<u16>(params.Get("port", 26760)); + const auto pad = static_cast<u16>(params.Get("pad_index", 0)); + + return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get()); +} -private: - std::shared_ptr<DeviceStatus> status; -}; +void UDPMotionFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); +} -class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { -public: - explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { - { - std::lock_guard guard(status->update_mutex); - status->touch_calibration = DeviceStatus::CalibrationData{}; - // These default values work well for DS4 but probably not other touch inputs - status->touch_calibration->min_x = params.Get("min_x", 100); - status->touch_calibration->min_y = params.Get("min_y", 50); - status->touch_calibration->max_x = params.Get("max_x", 1800); - status->touch_calibration->max_y = params.Get("max_y", 850); +void UDPMotionFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); +} + +Common::ParamPackage UDPMotionFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + while (queue.Pop(pad)) { + if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { + continue; } - return std::make_unique<UDPTouchDevice>(status); + params.Set("engine", "cemuhookudp"); + params.Set("ip", pad.host); + params.Set("port", static_cast<u16>(pad.port)); + params.Set("pad_index", static_cast<u16>(pad.pad_index)); + params.Set("motion", static_cast<u16>(pad.motion)); + return params; } + return params; +} -private: - std::shared_ptr<DeviceStatus> status; -}; - -class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { +class UDPTouch final : public Input::TouchDevice { public: - explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) + : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { - return std::make_unique<UDPMotionDevice>(status); + Input::TouchStatus GetStatus() const override { + return client->GetTouchState(); } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + [[maybe_unused]] const u16 port; + [[maybe_unused]] const u16 pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -State::State() { - auto status = std::make_shared<DeviceStatus>(); - client = - std::make_unique<Client>(status, Settings::values.udp_input_address, - Settings::values.udp_input_port, Settings::values.udp_pad_index); - - motion_factory = std::make_shared<UDPMotionFactory>(status); - touch_factory = std::make_shared<UDPTouchFactory>(status); - - Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); - Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); -} - -State::~State() { - Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); - Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); -} - -std::vector<Common::ParamPackage> State::GetInputDevices() const { - // TODO support binding udp devices - return {}; +/// A motion device factory that creates motion devices from a UDP client +UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) + : client(std::move(client_)) {} + +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the UDP port number + */ +std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) { + auto ip = params.Get("ip", "127.0.0.1"); + const auto port = static_cast<u16>(params.Get("port", 26760)); + const auto pad = static_cast<u16>(params.Get("pad_index", 0)); + + return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get()); } -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, - Settings::values.udp_pad_index); -} - -std::unique_ptr<State> Init() { - return std::make_unique<State>(); -} -} // namespace InputCommon::CemuhookUDP +} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 672a5c812..ea3fd4175 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -1,32 +1,57 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include <memory> -#include <vector> -#include "common/param_package.h" +#include "core/frontend/input.h" +#include "input_common/udp/client.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class Client; -class UDPMotionFactory; -class UDPTouchFactory; - -class State { +/// A motion device factory that creates motion devices from udp clients +class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { public: - State(); - ~State(); - void ReloadUDPClient(); - std::vector<Common::ParamPackage> GetInputDevices() const; + explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); + + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } private: - std::unique_ptr<Client> client; - std::shared_ptr<UDPMotionFactory> motion_factory; - std::shared_ptr<UDPTouchFactory> touch_factory; + std::shared_ptr<CemuhookUDP::Client> client; + bool polling = false; }; -std::unique_ptr<State> Init(); +/// A touch device factory that creates touch devices from udp clients +class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: + explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_); + + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<CemuhookUDP::Client> client; + bool polling = false; +}; -} // namespace InputCommon::CemuhookUDP +} // namespace InputCommon diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 47ef30aa9..6a5c18945 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,14 +1,11 @@ add_executable(tests common/bit_field.cpp - common/bit_utils.cpp common/fibers.cpp - common/multi_level_queue.cpp common/param_package.cpp common/ring_buffer.cpp - core/arm/arm_test_common.cpp - core/arm/arm_test_common.h core/core_timing.cpp tests.cpp + video_core/buffer_base.cpp ) create_target_directory_groups(tests) diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp index 8ca1889f9..182638000 100644 --- a/src/tests/common/bit_field.cpp +++ b/src/tests/common/bit_field.cpp @@ -68,7 +68,7 @@ TEST_CASE("BitField", "[common]") { }}); // bit fields: 01101100111101'10101110'1011'101100 - REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100); + REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100U); REQUIRE(be_bitfield.a == 0b101100); REQUIRE(be_bitfield.b == -5); // 1011 as two's complement REQUIRE(be_bitfield.c == TestEnum::B); @@ -80,7 +80,7 @@ TEST_CASE("BitField", "[common]") { be_bitfield.d.Assign(0b01010101010101); std::memcpy(&raw, &be_bitfield, sizeof(raw)); // bit fields: 01010101010101'00001111'1111'000111 - REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111); + REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111U); REQUIRE(raw == std::array<u8, 4>{{ 0b01010101, 0b01010100, diff --git a/src/tests/common/bit_utils.cpp b/src/tests/common/bit_utils.cpp deleted file mode 100644 index 479b5995a..000000000 --- a/src/tests/common/bit_utils.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <catch2/catch.hpp> -#include <math.h> -#include "common/bit_util.h" - -namespace Common { - -TEST_CASE("BitUtils::CountTrailingZeroes", "[common]") { - REQUIRE(Common::CountTrailingZeroes32(0) == 32); - REQUIRE(Common::CountTrailingZeroes64(0) == 64); - REQUIRE(Common::CountTrailingZeroes32(9) == 0); - REQUIRE(Common::CountTrailingZeroes32(8) == 3); - REQUIRE(Common::CountTrailingZeroes32(0x801000) == 12); - REQUIRE(Common::CountTrailingZeroes64(9) == 0); - REQUIRE(Common::CountTrailingZeroes64(8) == 3); - REQUIRE(Common::CountTrailingZeroes64(0x801000) == 12); - REQUIRE(Common::CountTrailingZeroes64(0x801000000000UL) == 36); -} - -} // namespace Common diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp index 4fd92428f..d94492fc6 100644 --- a/src/tests/common/fibers.cpp +++ b/src/tests/common/fibers.cpp @@ -6,18 +6,40 @@ #include <cstdlib> #include <functional> #include <memory> +#include <mutex> +#include <stdexcept> #include <thread> #include <unordered_map> #include <vector> #include <catch2/catch.hpp> -#include <math.h> + #include "common/common_types.h" #include "common/fiber.h" -#include "common/spin_lock.h" namespace Common { +class ThreadIds { +public: + void Register(u32 id) { + const auto thread_id = std::this_thread::get_id(); + std::scoped_lock lock{mutex}; + if (ids.contains(thread_id)) { + throw std::logic_error{"Registering the same thread twice"}; + } + ids.emplace(thread_id, id); + } + + [[nodiscard]] u32 Get() const { + std::scoped_lock lock{mutex}; + return ids.at(std::this_thread::get_id()); + } + +private: + mutable std::mutex mutex; + std::unordered_map<std::thread::id, u32> ids; +}; + class TestControl1 { public: TestControl1() = default; @@ -26,7 +48,7 @@ public: void ExecuteThread(u32 id); - std::unordered_map<std::thread::id, u32> ids; + ThreadIds thread_ids; std::vector<std::shared_ptr<Common::Fiber>> thread_fibers; std::vector<std::shared_ptr<Common::Fiber>> work_fibers; std::vector<u32> items; @@ -39,8 +61,7 @@ static void WorkControl1(void* control) { } void TestControl1::DoWork() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); u32 value = items[id]; for (u32 i = 0; i < id; i++) { value++; @@ -50,8 +71,7 @@ void TestControl1::DoWork() { } void TestControl1::ExecuteThread(u32 id) { - std::thread::id this_id = std::this_thread::get_id(); - ids[this_id] = id; + thread_ids.Register(id); auto thread_fiber = Fiber::ThreadToFiber(); thread_fibers[id] = thread_fiber; work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this); @@ -98,8 +118,7 @@ public: value1 += i; } Fiber::YieldTo(fiber1, fiber3); - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); assert1 = id == 1; value2 += 5000; Fiber::YieldTo(fiber1, thread_fibers[id]); @@ -115,8 +134,7 @@ public: } void DoWork3() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); assert2 = id == 0; value1 += 1000; Fiber::YieldTo(fiber3, thread_fibers[id]); @@ -125,14 +143,12 @@ public: void ExecuteThread(u32 id); void CallFiber1() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); Fiber::YieldTo(thread_fibers[id], fiber1); } void CallFiber2() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); Fiber::YieldTo(thread_fibers[id], fiber2); } @@ -145,7 +161,7 @@ public: u32 value2{}; std::atomic<bool> trap{true}; std::atomic<bool> trap2{true}; - std::unordered_map<std::thread::id, u32> ids; + ThreadIds thread_ids; std::vector<std::shared_ptr<Common::Fiber>> thread_fibers; std::shared_ptr<Common::Fiber> fiber1; std::shared_ptr<Common::Fiber> fiber2; @@ -168,15 +184,13 @@ static void WorkControl2_3(void* control) { } void TestControl2::ExecuteThread(u32 id) { - std::thread::id this_id = std::this_thread::get_id(); - ids[this_id] = id; + thread_ids.Register(id); auto thread_fiber = Fiber::ThreadToFiber(); thread_fibers[id] = thread_fiber; } void TestControl2::Exit() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); thread_fibers[id]->Exit(); } @@ -193,7 +207,7 @@ static void ThreadStart2_2(u32 id, TestControl2& test_control) { } /** This test checks for fiber thread exchange configuration and validates that fibers are - * that a fiber has been succesfully transfered from one thread to another and that the TLS + * that a fiber has been successfully transferred from one thread to another and that the TLS * region of the thread is kept while changing fibers. */ TEST_CASE("Fibers::InterExchange", "[common]") { @@ -228,24 +242,21 @@ public: void DoWork1() { value1 += 1; Fiber::YieldTo(fiber1, fiber2); - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); value3 += 1; Fiber::YieldTo(fiber1, thread_fibers[id]); } void DoWork2() { value2 += 1; - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); Fiber::YieldTo(fiber2, thread_fibers[id]); } void ExecuteThread(u32 id); void CallFiber1() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); Fiber::YieldTo(thread_fibers[id], fiber1); } @@ -254,7 +265,7 @@ public: u32 value1{}; u32 value2{}; u32 value3{}; - std::unordered_map<std::thread::id, u32> ids; + ThreadIds thread_ids; std::vector<std::shared_ptr<Common::Fiber>> thread_fibers; std::shared_ptr<Common::Fiber> fiber1; std::shared_ptr<Common::Fiber> fiber2; @@ -271,15 +282,13 @@ static void WorkControl3_2(void* control) { } void TestControl3::ExecuteThread(u32 id) { - std::thread::id this_id = std::this_thread::get_id(); - ids[this_id] = id; + thread_ids.Register(id); auto thread_fiber = Fiber::ThreadToFiber(); thread_fibers[id] = thread_fiber; } void TestControl3::Exit() { - std::thread::id this_id = std::this_thread::get_id(); - u32 id = ids[this_id]; + const u32 id = thread_ids.Get(); thread_fibers[id]->Exit(); } @@ -290,7 +299,7 @@ static void ThreadStart3(u32 id, TestControl3& test_control) { } /** This test checks for one two threads racing for starting the same fiber. - * It checks execution occured in an ordered manner and by no time there were + * It checks execution occurred in an ordered manner and by no time there were * two contexts at the same time. */ TEST_CASE("Fibers::StartRace", "[common]") { diff --git a/src/tests/common/multi_level_queue.cpp b/src/tests/common/multi_level_queue.cpp deleted file mode 100644 index cca7ec7da..000000000 --- a/src/tests/common/multi_level_queue.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 Yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <catch2/catch.hpp> -#include <math.h> -#include "common/common_types.h" -#include "common/multi_level_queue.h" - -namespace Common { - -TEST_CASE("MultiLevelQueue", "[common]") { - std::array<f32, 8> values = {0.0, 5.0, 1.0, 9.0, 8.0, 2.0, 6.0, 7.0}; - Common::MultiLevelQueue<f32, 64> mlq; - REQUIRE(mlq.empty()); - mlq.add(values[2], 2); - mlq.add(values[7], 7); - mlq.add(values[3], 3); - mlq.add(values[4], 4); - mlq.add(values[0], 0); - mlq.add(values[5], 5); - mlq.add(values[6], 6); - mlq.add(values[1], 1); - u32 index = 0; - bool all_set = true; - for (auto& f : mlq) { - all_set &= (f == values[index]); - index++; - } - REQUIRE(all_set); - REQUIRE(!mlq.empty()); - f32 v = 8.0; - mlq.add(v, 2); - v = -7.0; - mlq.add(v, 2, false); - REQUIRE(mlq.front(2) == -7.0); - mlq.yield(2); - REQUIRE(mlq.front(2) == values[2]); - REQUIRE(mlq.back(2) == -7.0); - REQUIRE(mlq.empty(8)); - v = 10.0; - mlq.add(v, 8); - mlq.adjust(v, 8, 9); - REQUIRE(mlq.front(9) == v); - REQUIRE(mlq.empty(8)); - REQUIRE(!mlq.empty(9)); - mlq.adjust(values[0], 0, 9); - REQUIRE(mlq.highest_priority_set() == 1); - REQUIRE(mlq.lowest_priority_set() == 9); - mlq.remove(values[1], 1); - REQUIRE(mlq.highest_priority_set() == 2); - REQUIRE(mlq.empty(1)); -} - -} // namespace Common diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp index c883c4d56..903626e4b 100644 --- a/src/tests/common/ring_buffer.cpp +++ b/src/tests/common/ring_buffer.cpp @@ -14,70 +14,70 @@ namespace Common { TEST_CASE("RingBuffer: Basic Tests", "[common]") { - RingBuffer<char, 4, 1> buf; + RingBuffer<char, 4> buf; // Pushing values into a ring buffer with space should succeed. for (std::size_t i = 0; i < 4; i++) { const char elem = static_cast<char>(i); const std::size_t count = buf.Push(&elem, 1); - REQUIRE(count == 1); + REQUIRE(count == 1U); } - REQUIRE(buf.Size() == 4); + REQUIRE(buf.Size() == 4U); // Pushing values into a full ring buffer should fail. { const char elem = static_cast<char>(42); const std::size_t count = buf.Push(&elem, 1); - REQUIRE(count == 0); + REQUIRE(count == 0U); } - REQUIRE(buf.Size() == 4); + REQUIRE(buf.Size() == 4U); // Popping multiple values from a ring buffer with values should succeed. { const std::vector<char> popped = buf.Pop(2); - REQUIRE(popped.size() == 2); + REQUIRE(popped.size() == 2U); REQUIRE(popped[0] == 0); REQUIRE(popped[1] == 1); } - REQUIRE(buf.Size() == 2); + REQUIRE(buf.Size() == 2U); // Popping a single value from a ring buffer with values should succeed. { const std::vector<char> popped = buf.Pop(1); - REQUIRE(popped.size() == 1); + REQUIRE(popped.size() == 1U); REQUIRE(popped[0] == 2); } - REQUIRE(buf.Size() == 1); + REQUIRE(buf.Size() == 1U); // Pushing more values than space available should partially suceed. { std::vector<char> to_push(6); std::iota(to_push.begin(), to_push.end(), 88); const std::size_t count = buf.Push(to_push); - REQUIRE(count == 3); + REQUIRE(count == 3U); } - REQUIRE(buf.Size() == 4); + REQUIRE(buf.Size() == 4U); // Doing an unlimited pop should pop all values. { const std::vector<char> popped = buf.Pop(); - REQUIRE(popped.size() == 4); + REQUIRE(popped.size() == 4U); REQUIRE(popped[0] == 3); REQUIRE(popped[1] == 88); REQUIRE(popped[2] == 89); REQUIRE(popped[3] == 90); } - REQUIRE(buf.Size() == 0); + REQUIRE(buf.Size() == 0U); } TEST_CASE("RingBuffer: Threaded Test", "[common]") { - RingBuffer<char, 4, 2> buf; + RingBuffer<char, 8> buf; const char seed = 42; const std::size_t count = 1000000; std::size_t full = 0; @@ -92,8 +92,8 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") { std::array<char, 2> value = {seed, seed}; std::size_t i = 0; while (i < count) { - if (const std::size_t c = buf.Push(&value[0], 1); c > 0) { - REQUIRE(c == 1); + if (const std::size_t c = buf.Push(&value[0], 2); c > 0) { + REQUIRE(c == 2U); i++; next_value(value); } else { @@ -107,8 +107,8 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") { std::array<char, 2> value = {seed, seed}; std::size_t i = 0; while (i < count) { - if (const std::vector<char> v = buf.Pop(1); v.size() > 0) { - REQUIRE(v.size() == 2); + if (const std::vector<char> v = buf.Pop(2); v.size() > 0) { + REQUIRE(v.size() == 2U); REQUIRE(v[0] == value[0]); REQUIRE(v[1] == value[1]); i++; @@ -123,7 +123,7 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") { producer.join(); consumer.join(); - REQUIRE(buf.Size() == 0); + REQUIRE(buf.Size() == 0U); printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty); } diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp deleted file mode 100644 index e54674d11..000000000 --- a/src/tests/core/arm/arm_test_common.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> - -#include "common/page_table.h" -#include "core/core.h" -#include "core/hle/kernel/memory/page_table.h" -#include "core/hle/kernel/process.h" -#include "core/memory.h" -#include "tests/core/arm/arm_test_common.h" - -namespace ArmTests { - -TestEnvironment::TestEnvironment(bool mutable_memory_) - : mutable_memory(mutable_memory_), - test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} { - auto& system = Core::System::GetInstance(); - - auto process = Kernel::Process::Create(system, "", Kernel::Process::ProcessType::Userland); - page_table = &process->PageTable().PageTableImpl(); - - system.Memory().MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); - system.Memory().MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); - - kernel.MakeCurrentProcess(process.get()); -} - -TestEnvironment::~TestEnvironment() { - auto& system = Core::System::GetInstance(); - system.Memory().UnmapRegion(*page_table, 0x80000000, 0x80000000); - system.Memory().UnmapRegion(*page_table, 0x00000000, 0x80000000); -} - -void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { - SetMemory32(vaddr + 0, static_cast<u32>(value)); - SetMemory32(vaddr + 4, static_cast<u32>(value >> 32)); -} - -void TestEnvironment::SetMemory32(VAddr vaddr, u32 value) { - SetMemory16(vaddr + 0, static_cast<u16>(value)); - SetMemory16(vaddr + 2, static_cast<u16>(value >> 16)); -} - -void TestEnvironment::SetMemory16(VAddr vaddr, u16 value) { - SetMemory8(vaddr + 0, static_cast<u8>(value)); - SetMemory8(vaddr + 1, static_cast<u8>(value >> 8)); -} - -void TestEnvironment::SetMemory8(VAddr vaddr, u8 value) { - test_memory->data[vaddr] = value; -} - -std::vector<WriteRecord> TestEnvironment::GetWriteRecords() const { - return write_records; -} - -void TestEnvironment::ClearWriteRecords() { - write_records.clear(); -} - -TestEnvironment::TestMemory::~TestMemory() {} - -std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) { - return true; -} - -std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) { - const auto iter = data.find(addr); - - if (iter == data.end()) { - // Some arbitrary data - return static_cast<u8>(addr); - } - - return iter->second; -} - -std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) { - return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8; -} - -std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) { - return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16; -} - -std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) { - return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32; -} - -bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) { - VAddr addr = src_addr; - u8* data = static_cast<u8*>(dest_buffer); - - for (std::size_t i = 0; i < size; i++, addr++, data++) { - *data = *Read8(addr); - } - - return true; -} - -bool TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) { - env->write_records.emplace_back(8, addr, data); - if (env->mutable_memory) - env->SetMemory8(addr, data); - return true; -} - -bool TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) { - env->write_records.emplace_back(16, addr, data); - if (env->mutable_memory) - env->SetMemory16(addr, data); - return true; -} - -bool TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) { - env->write_records.emplace_back(32, addr, data); - if (env->mutable_memory) - env->SetMemory32(addr, data); - return true; -} - -bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) { - env->write_records.emplace_back(64, addr, data); - if (env->mutable_memory) - env->SetMemory64(addr, data); - return true; -} - -bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer, - std::size_t size) { - VAddr addr = dest_addr; - const u8* data = static_cast<const u8*>(src_buffer); - - for (std::size_t i = 0; i < size; i++, addr++, data++) { - env->write_records.emplace_back(8, addr, *data); - if (env->mutable_memory) - env->SetMemory8(addr, *data); - } - - return true; -} - -} // namespace ArmTests diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h deleted file mode 100644 index d145dbfcc..000000000 --- a/src/tests/core/arm/arm_test_common.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <tuple> -#include <unordered_map> -#include <vector> - -#include "common/common_types.h" -#include "common/memory_hook.h" -#include "core/hle/kernel/kernel.h" - -namespace Common { -struct PageTable; -} - -namespace ArmTests { - -struct WriteRecord { - WriteRecord(std::size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {} - std::size_t size; - VAddr addr; - u64 data; - bool operator==(const WriteRecord& o) const { - return std::tie(size, addr, data) == std::tie(o.size, o.addr, o.data); - } -}; - -class TestEnvironment final { -public: - /* - * Inititalise test environment - * @param mutable_memory If false, writes to memory can never be read back. - * (Memory is immutable.) - */ - explicit TestEnvironment(bool mutable_memory = false); - - /// Shutdown test environment - ~TestEnvironment(); - - /// Sets value at memory location vaddr. - void SetMemory8(VAddr vaddr, u8 value); - void SetMemory16(VAddr vaddr, u16 value); - void SetMemory32(VAddr vaddr, u32 value); - void SetMemory64(VAddr vaddr, u64 value); - - /** - * Whenever Memory::Write{8,16,32,64} is called within the test environment, - * a new write-record is made. - * @returns A vector of write records made since they were last cleared. - */ - std::vector<WriteRecord> GetWriteRecords() const; - - /// Empties the internal write-record store. - void ClearWriteRecords(); - -private: - friend struct TestMemory; - struct TestMemory final : Common::MemoryHook { - explicit TestMemory(TestEnvironment* env_) : env(env_) {} - TestEnvironment* env; - - ~TestMemory() override; - - std::optional<bool> IsValidAddress(VAddr addr) override; - - std::optional<u8> Read8(VAddr addr) override; - std::optional<u16> Read16(VAddr addr) override; - std::optional<u32> Read32(VAddr addr) override; - std::optional<u64> Read64(VAddr addr) override; - - bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override; - - bool Write8(VAddr addr, u8 data) override; - bool Write16(VAddr addr, u16 data) override; - bool Write32(VAddr addr, u32 data) override; - bool Write64(VAddr addr, u64 data) override; - - bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) override; - - std::unordered_map<VAddr, u8> data; - }; - - bool mutable_memory; - std::shared_ptr<TestMemory> test_memory; - std::vector<WriteRecord> write_records; - Common::PageTable* page_table = nullptr; - Kernel::KernelCore kernel; -}; - -} // namespace ArmTests diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp new file mode 100644 index 000000000..edced69bb --- /dev/null +++ b/src/tests/video_core/buffer_base.cpp @@ -0,0 +1,549 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <stdexcept> +#include <unordered_map> + +#include <catch2/catch.hpp> + +#include "common/alignment.h" +#include "common/common_types.h" +#include "video_core/buffer_cache/buffer_base.h" + +namespace { +using VideoCommon::BufferBase; +using Range = std::pair<u64, u64>; + +constexpr u64 PAGE = 4096; +constexpr u64 WORD = 4096 * 64; + +constexpr VAddr c = 0x1328914000; + +class RasterizerInterface { +public: + void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { + const u64 page_start{addr >> Core::Memory::PAGE_BITS}; + const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS}; + for (u64 page = page_start; page < page_end; ++page) { + int& value = page_table[page]; + value += delta; + if (value < 0) { + throw std::logic_error{"negative page"}; + } + if (value == 0) { + page_table.erase(page); + } + } + } + + [[nodiscard]] int Count(VAddr addr) const noexcept { + const auto it = page_table.find(addr >> Core::Memory::PAGE_BITS); + return it == page_table.end() ? 0 : it->second; + } + + [[nodiscard]] unsigned Count() const noexcept { + unsigned count = 0; + for (const auto [index, value] : page_table) { + count += value; + } + return count; + } + +private: + std::unordered_map<u64, int> page_table; +}; +} // Anonymous namespace + +TEST_CASE("BufferBase: Small buffer", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + REQUIRE(rasterizer.Count() == 0); + buffer.UnmarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == WORD / PAGE); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0}); + + buffer.MarkRegionAsCpuModified(c + PAGE, 1); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2}); +} + +TEST_CASE("BufferBase: Large buffer", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 32); + buffer.UnmarkRegionAsCpuModified(c, WORD * 32); + buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2}); + REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8}); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE}); + REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE}); + REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) == + Range{WORD * 3 + PAGE * 63, WORD * 4}); + + buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE); + buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); + REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) == + Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9}); + + buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE); + REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) == + Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7}); + + buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32}); + + buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE); + buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE); + + buffer.UnmarkRegionAsCpuModified(c, WORD * 32); + REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0}); +} + +TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, PAGE * 2); + REQUIRE(rasterizer.Count() == 0); + buffer.UnmarkRegionAsCpuModified(c, PAGE); + REQUIRE(rasterizer.Count() == 1); + buffer.MarkRegionAsCpuModified(c, PAGE * 2); + REQUIRE(rasterizer.Count() == 0); + buffer.UnmarkRegionAsCpuModified(c, PAGE); + buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE); + REQUIRE(rasterizer.Count() == 2); + buffer.MarkRegionAsCpuModified(c, PAGE * 2); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Basic range", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.MarkRegionAsCpuModified(c, PAGE); + int num = 0; + buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == 0U); + REQUIRE(size == PAGE); + ++num; + }); + REQUIRE(num == 1U); +} + +TEST_CASE("BufferBase: Border upload", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 2); + buffer.UnmarkRegionAsCpuModified(c, WORD * 2); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE * 2); + }); +} + +TEST_CASE("BufferBase: Border upload range", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 2); + buffer.UnmarkRegionAsCpuModified(c, WORD * 2); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE * 2); + }); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE); + }); + buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) { + REQUIRE(offset == WORD); + REQUIRE(size == PAGE); + }); +} + +TEST_CASE("BufferBase: Border upload partial range", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 2); + buffer.UnmarkRegionAsCpuModified(c, WORD * 2); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE * 2); + }); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE); + }); + buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) { + REQUIRE(offset == WORD); + REQUIRE(size == PAGE); + }); +} + +TEST_CASE("BufferBase: Partial word uploads", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, 0x9d000); + int num = 0; + buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == 0U); + REQUIRE(size == WORD); + ++num; + }); + REQUIRE(num == 1); + buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == WORD); + REQUIRE(size == WORD); + ++num; + }); + REQUIRE(num == 2); + buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) { + REQUIRE(offset == WORD * 2); + REQUIRE(size == PAGE * 0x1d); + ++num; + }); + REQUIRE(num == 3); +} + +TEST_CASE("BufferBase: Partial page upload", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + int num = 0; + buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE); + buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE); + buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 2); + REQUIRE(size == PAGE); + ++num; + }); + REQUIRE(num == 1); + buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 9); + REQUIRE(size == PAGE); + ++num; + }); + REQUIRE(num == 2); +} + +TEST_CASE("BufferBase: Partial page upload with multiple words on the right") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 8); + buffer.UnmarkRegionAsCpuModified(c, WORD * 8); + buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); + int num = 0; + buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 13); + REQUIRE(size == WORD * 7 - PAGE * 3); + ++num; + }); + REQUIRE(num == 1); + buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) { + REQUIRE(offset == WORD * 7 + PAGE * 10); + REQUIRE(size == PAGE * 3); + ++num; + }); + REQUIRE(num == 2); +} + +TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 8); + buffer.UnmarkRegionAsCpuModified(c, WORD * 8); + buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7); + int num = 0; + buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 16); + REQUIRE(size == WORD * 7 - PAGE * 3); + ++num; + }); + REQUIRE(num == 1); + buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 13); + REQUIRE(size == PAGE * 3); + ++num; + }); + REQUIRE(num == 2); +} + +TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 8); + buffer.UnmarkRegionAsCpuModified(c, WORD * 8); + buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140); + int num = 0; + buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 16); + REQUIRE(size == WORD); + ++num; + }); + REQUIRE(num == 1); + buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { + REQUIRE(offset == PAGE * 13); + REQUIRE(size == PAGE * 3); + ++num; + }); + REQUIRE(num == 2); + buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) { + REQUIRE(offset == WORD + PAGE * 16); + REQUIRE(size == PAGE * 73); + ++num; + }); + REQUIRE(num == 3); +} + +TEST_CASE("BufferBase: Empty right bits", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 2048); + buffer.UnmarkRegionAsCpuModified(c, WORD * 2048); + buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2); + buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) { + REQUIRE(offset == WORD - PAGE); + REQUIRE(size == PAGE * 2); + }); +} + +TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.MarkRegionAsCpuModified(c, PAGE); + int num = 0; + buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; }); + buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; }); + buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; }); + REQUIRE(num == 0); + buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; }); + REQUIRE(num == 1); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, 0x22000); + REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE)); + REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE)); + REQUIRE(rasterizer.Count() == 0); + REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100)); + REQUIRE(rasterizer.Count() == 1); + REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2)); + buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2); + buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2); + REQUIRE(rasterizer.Count() == 2); +} + +TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, 0x310720); + buffer.UnmarkRegionAsCpuModified(c, 0x310720); + REQUIRE(rasterizer.Count(c) == 1); + REQUIRE(rasterizer.Count(c + PAGE) == 1); + REQUIRE(rasterizer.Count(c + WORD) == 1); + REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1); +} + +TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE); + buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4); + buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable { + static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3}; + static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4}; + REQUIRE(offset == offsets.at(i)); + REQUIRE(size == sizes.at(i)); + ++i; + }); +} + +TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, 0x22000); + buffer.UnmarkRegionAsCpuModified(c, 0x22000); + REQUIRE(rasterizer.Count() == 0x22); + buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE); + buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE); + buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable { + static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21}; + static constexpr std::array<u64, 2> sizes{PAGE, PAGE}; + REQUIRE(offset == offsets.at(i)); + REQUIRE(size == sizes.at(i)); + ++i; + }); +} + +TEST_CASE("BufferBase: Single page modified range", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, PAGE); + REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); + buffer.UnmarkRegionAsCpuModified(c, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(c, PAGE)); +} + +TEST_CASE("BufferBase: Two page modified range", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, PAGE * 2); + REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2)); + buffer.UnmarkRegionAsCpuModified(c, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(c, PAGE)); +} + +TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") { + for (int offset = 0; offset < 4; ++offset) { + const VAddr address = c + WORD * offset; + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, address, WORD * 4); + REQUIRE(buffer.IsRegionCpuModified(address, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE)); + + buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE)); + REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2)); + REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2)); + + buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2)); + } +} + +TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 16); + buffer.UnmarkRegionAsCpuModified(c, WORD * 16); + REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16)); + + buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE); + REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8)); + REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2)); +} + +TEST_CASE("BufferBase: Out of bounds region query") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 16); + REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE)); + REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE)); + REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64)); + REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64)); +} + +TEST_CASE("BufferBase: Wrap word regions") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD * 2); + buffer.UnmarkRegionAsCpuModified(c, WORD * 2); + buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2); + REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2)); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8)); + + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16)); + buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE)); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2)); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16)); +} + +TEST_CASE("BufferBase: Unaligned page region query") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.MarkRegionAsCpuModified(c + 4000, 1000); + REQUIRE(buffer.IsRegionCpuModified(c, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000)); + REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1)); +} + +TEST_CASE("BufferBase: Cached write") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.CachedCpuWrite(c + PAGE, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.FlushCachedWrites(); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Multiple cached write") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.CachedCpuWrite(c + PAGE, PAGE); + buffer.CachedCpuWrite(c + PAGE * 3, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE)); + buffer.FlushCachedWrites(); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE)); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Cached write unmarked") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.CachedCpuWrite(c + PAGE, PAGE); + buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.FlushCachedWrites(); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Cached write iterated") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + buffer.CachedCpuWrite(c + PAGE, PAGE); + int num = 0; + buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); + REQUIRE(num == 0); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.FlushCachedWrites(); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} + +TEST_CASE("BufferBase: Cached write downloads") { + RasterizerInterface rasterizer; + BufferBase buffer(rasterizer, c, WORD); + buffer.UnmarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 64); + buffer.CachedCpuWrite(c + PAGE, PAGE); + REQUIRE(rasterizer.Count() == 63); + buffer.MarkRegionAsGpuModified(c + PAGE, PAGE); + int num = 0; + buffer.ForEachDownloadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); + buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); + REQUIRE(num == 0); + REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE)); + buffer.FlushCachedWrites(); + REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE)); + REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE)); + buffer.MarkRegionAsCpuModified(c, WORD); + REQUIRE(rasterizer.Count() == 0); +} diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index d85f1e9d1..9b931976a 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,12 +1,30 @@ add_subdirectory(host_shaders) add_library(video_core STATIC - buffer_cache/buffer_block.h + buffer_cache/buffer_base.h + buffer_cache/buffer_cache.cpp buffer_cache/buffer_cache.h - buffer_cache/map_interval.cpp - buffer_cache/map_interval.h + cdma_pusher.cpp + cdma_pusher.h + command_classes/codecs/codec.cpp + command_classes/codecs/codec.h + command_classes/codecs/h264.cpp + command_classes/codecs/h264.h + command_classes/codecs/vp9.cpp + command_classes/codecs/vp9.h + command_classes/codecs/vp9_types.h + command_classes/host1x.cpp + command_classes/host1x.h + command_classes/nvdec.cpp + command_classes/nvdec.h + command_classes/nvdec_common.h + command_classes/sync_manager.cpp + command_classes/sync_manager.h + command_classes/vic.cpp + command_classes/vic.h compatible_formats.cpp compatible_formats.h + delayed_destruction_ring.h dirty_flags.cpp dirty_flags.h dma_pusher.cpp @@ -29,6 +47,7 @@ add_library(video_core STATIC engines/shader_bytecode.h engines/shader_header.h engines/shader_type.h + framebuffer_config.h macro/macro.cpp macro/macro.h macro/macro_hle.cpp @@ -40,18 +59,12 @@ add_library(video_core STATIC fence_manager.h gpu.cpp gpu.h - gpu_asynch.cpp - gpu_asynch.h - gpu_synch.cpp - gpu_synch.h gpu_thread.cpp gpu_thread.h guest_driver.cpp guest_driver.h memory_manager.cpp memory_manager.h - morton.cpp - morton.h query_cache.h rasterizer_accelerated.cpp rasterizer_accelerated.h @@ -66,14 +79,10 @@ add_library(video_core STATIC renderer_opengl/gl_device.h renderer_opengl/gl_fence_manager.cpp renderer_opengl/gl_fence_manager.h - renderer_opengl/gl_framebuffer_cache.cpp - renderer_opengl/gl_framebuffer_cache.h renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.h renderer_opengl/gl_resource_manager.cpp renderer_opengl/gl_resource_manager.h - renderer_opengl/gl_sampler_cache.cpp - renderer_opengl/gl_sampler_cache.h renderer_opengl/gl_shader_cache.cpp renderer_opengl/gl_shader_cache.h renderer_opengl/gl_shader_decompiler.cpp @@ -95,10 +104,58 @@ add_library(video_core STATIC renderer_opengl/maxwell_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h - renderer_opengl/utils.cpp - renderer_opengl/utils.h - sampler_cache.cpp - sampler_cache.h + renderer_opengl/util_shaders.cpp + renderer_opengl/util_shaders.h + renderer_vulkan/blit_image.cpp + renderer_vulkan/blit_image.h + renderer_vulkan/fixed_pipeline_state.cpp + renderer_vulkan/fixed_pipeline_state.h + renderer_vulkan/maxwell_to_vk.cpp + renderer_vulkan/maxwell_to_vk.h + renderer_vulkan/renderer_vulkan.h + renderer_vulkan/renderer_vulkan.cpp + renderer_vulkan/vk_blit_screen.cpp + renderer_vulkan/vk_blit_screen.h + renderer_vulkan/vk_buffer_cache.cpp + renderer_vulkan/vk_buffer_cache.h + renderer_vulkan/vk_command_pool.cpp + renderer_vulkan/vk_command_pool.h + renderer_vulkan/vk_compute_pass.cpp + renderer_vulkan/vk_compute_pass.h + renderer_vulkan/vk_compute_pipeline.cpp + renderer_vulkan/vk_compute_pipeline.h + renderer_vulkan/vk_descriptor_pool.cpp + renderer_vulkan/vk_descriptor_pool.h + renderer_vulkan/vk_fence_manager.cpp + renderer_vulkan/vk_fence_manager.h + renderer_vulkan/vk_graphics_pipeline.cpp + renderer_vulkan/vk_graphics_pipeline.h + renderer_vulkan/vk_master_semaphore.cpp + renderer_vulkan/vk_master_semaphore.h + renderer_vulkan/vk_pipeline_cache.cpp + renderer_vulkan/vk_pipeline_cache.h + renderer_vulkan/vk_query_cache.cpp + renderer_vulkan/vk_query_cache.h + renderer_vulkan/vk_rasterizer.cpp + renderer_vulkan/vk_rasterizer.h + renderer_vulkan/vk_resource_pool.cpp + renderer_vulkan/vk_resource_pool.h + renderer_vulkan/vk_scheduler.cpp + renderer_vulkan/vk_scheduler.h + renderer_vulkan/vk_shader_decompiler.cpp + renderer_vulkan/vk_shader_decompiler.h + renderer_vulkan/vk_shader_util.cpp + renderer_vulkan/vk_shader_util.h + renderer_vulkan/vk_staging_buffer_pool.cpp + renderer_vulkan/vk_staging_buffer_pool.h + renderer_vulkan/vk_state_tracker.cpp + renderer_vulkan/vk_state_tracker.h + renderer_vulkan/vk_swapchain.cpp + renderer_vulkan/vk_swapchain.h + renderer_vulkan/vk_texture_cache.cpp + renderer_vulkan/vk_texture_cache.h + renderer_vulkan/vk_update_descriptor.cpp + renderer_vulkan/vk_update_descriptor.h shader_cache.h shader_notify.cpp shader_notify.h @@ -155,119 +212,104 @@ add_library(video_core STATIC shader/transform_feedback.h surface.cpp surface.h + texture_cache/accelerated_swizzle.cpp + texture_cache/accelerated_swizzle.h + texture_cache/decode_bc4.cpp + texture_cache/decode_bc4.h + texture_cache/descriptor_table.h + texture_cache/formatter.cpp + texture_cache/formatter.h texture_cache/format_lookup_table.cpp texture_cache/format_lookup_table.h - texture_cache/surface_base.cpp - texture_cache/surface_base.h - texture_cache/surface_params.cpp - texture_cache/surface_params.h - texture_cache/surface_view.cpp - texture_cache/surface_view.h + texture_cache/image_base.cpp + texture_cache/image_base.h + texture_cache/image_info.cpp + texture_cache/image_info.h + texture_cache/image_view_base.cpp + texture_cache/image_view_base.h + texture_cache/image_view_info.cpp + texture_cache/image_view_info.h + texture_cache/render_targets.h + texture_cache/samples_helper.h + texture_cache/slot_vector.h texture_cache/texture_cache.h + texture_cache/types.h + texture_cache/util.cpp + texture_cache/util.h textures/astc.cpp textures/astc.h - textures/convert.cpp - textures/convert.h textures/decoders.cpp textures/decoders.h textures/texture.cpp textures/texture.h video_core.cpp video_core.h + vulkan_common/vulkan_debug_callback.cpp + vulkan_common/vulkan_debug_callback.h + vulkan_common/vulkan_device.cpp + vulkan_common/vulkan_device.h + vulkan_common/vulkan_instance.cpp + vulkan_common/vulkan_instance.h + vulkan_common/vulkan_library.cpp + vulkan_common/vulkan_library.h + vulkan_common/vulkan_memory_allocator.cpp + vulkan_common/vulkan_memory_allocator.h + vulkan_common/vulkan_surface.cpp + vulkan_common/vulkan_surface.h + vulkan_common/vulkan_wrapper.cpp + vulkan_common/vulkan_wrapper.h + vulkan_common/nsight_aftermath_tracker.cpp + vulkan_common/nsight_aftermath_tracker.h ) -if (ENABLE_VULKAN) - target_sources(video_core PRIVATE - renderer_vulkan/fixed_pipeline_state.cpp - renderer_vulkan/fixed_pipeline_state.h - renderer_vulkan/maxwell_to_vk.cpp - renderer_vulkan/maxwell_to_vk.h - renderer_vulkan/nsight_aftermath_tracker.cpp - renderer_vulkan/nsight_aftermath_tracker.h - renderer_vulkan/renderer_vulkan.h - renderer_vulkan/renderer_vulkan.cpp - renderer_vulkan/vk_blit_screen.cpp - renderer_vulkan/vk_blit_screen.h - renderer_vulkan/vk_buffer_cache.cpp - renderer_vulkan/vk_buffer_cache.h - renderer_vulkan/vk_compute_pass.cpp - renderer_vulkan/vk_compute_pass.h - renderer_vulkan/vk_compute_pipeline.cpp - renderer_vulkan/vk_compute_pipeline.h - renderer_vulkan/vk_descriptor_pool.cpp - renderer_vulkan/vk_descriptor_pool.h - renderer_vulkan/vk_device.cpp - renderer_vulkan/vk_device.h - renderer_vulkan/vk_fence_manager.cpp - renderer_vulkan/vk_fence_manager.h - renderer_vulkan/vk_graphics_pipeline.cpp - renderer_vulkan/vk_graphics_pipeline.h - renderer_vulkan/vk_image.cpp - renderer_vulkan/vk_image.h - renderer_vulkan/vk_memory_manager.cpp - renderer_vulkan/vk_memory_manager.h - renderer_vulkan/vk_pipeline_cache.cpp - renderer_vulkan/vk_pipeline_cache.h - renderer_vulkan/vk_query_cache.cpp - renderer_vulkan/vk_query_cache.h - renderer_vulkan/vk_rasterizer.cpp - renderer_vulkan/vk_rasterizer.h - renderer_vulkan/vk_renderpass_cache.cpp - renderer_vulkan/vk_renderpass_cache.h - renderer_vulkan/vk_resource_manager.cpp - renderer_vulkan/vk_resource_manager.h - renderer_vulkan/vk_sampler_cache.cpp - renderer_vulkan/vk_sampler_cache.h - renderer_vulkan/vk_scheduler.cpp - renderer_vulkan/vk_scheduler.h - renderer_vulkan/vk_shader_decompiler.cpp - renderer_vulkan/vk_shader_decompiler.h - renderer_vulkan/vk_shader_util.cpp - renderer_vulkan/vk_shader_util.h - renderer_vulkan/vk_staging_buffer_pool.cpp - renderer_vulkan/vk_staging_buffer_pool.h - renderer_vulkan/vk_state_tracker.cpp - renderer_vulkan/vk_state_tracker.h - renderer_vulkan/vk_stream_buffer.cpp - renderer_vulkan/vk_stream_buffer.h - renderer_vulkan/vk_swapchain.cpp - renderer_vulkan/vk_swapchain.h - renderer_vulkan/vk_texture_cache.cpp - renderer_vulkan/vk_texture_cache.h - renderer_vulkan/vk_update_descriptor.cpp - renderer_vulkan/vk_update_descriptor.h - renderer_vulkan/wrapper.cpp - renderer_vulkan/wrapper.h - ) -endif() - create_target_directory_groups(video_core) target_link_libraries(video_core PUBLIC common core) target_link_libraries(video_core PRIVATE glad xbyak) +if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32) + add_dependencies(video_core ffmpeg-build) +endif() + +target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR}) +target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES}) + add_dependencies(video_core host_shaders) target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) - -if (ENABLE_VULKAN) - target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include) - target_compile_definitions(video_core PRIVATE HAS_VULKAN) - target_link_libraries(video_core PRIVATE sirit) -endif() +target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include) +target_link_libraries(video_core PRIVATE sirit) if (ENABLE_NSIGHT_AFTERMATH) if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK}) - message(ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided") + message(FATAL_ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided") endif() if (NOT WIN32) - message(ERROR "Nsight Aftermath doesn't support non-Windows platforms") + message(FATAL_ERROR "Nsight Aftermath doesn't support non-Windows platforms") endif() target_compile_definitions(video_core PRIVATE HAS_NSIGHT_AFTERMATH) target_include_directories(video_core PRIVATE "$ENV{NSIGHT_AFTERMATH_SDK}/include") endif() if (MSVC) - target_compile_options(video_core PRIVATE /we4267) + target_compile_options(video_core PRIVATE + /we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data + /we4456 # Declaration of 'identifier' hides previous local declaration + /we4457 # Declaration of 'identifier' hides function parameter + /we4458 # Declaration of 'identifier' hides class member + /we4459 # Declaration of 'identifier' hides global declaration + /we4715 # 'function' : not all control paths return a value + ) else() - target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion) + target_compile_options(video_core PRIVATE + -Werror=conversion + -Wno-error=sign-conversion + -Werror=pessimizing-move + -Werror=redundant-move + -Werror=shadow + -Werror=type-limits + + $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> + $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> + ) endif() diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h new file mode 100644 index 000000000..0c00ae280 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_base.h @@ -0,0 +1,590 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <bit> +#include <limits> +#include <utility> + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "core/memory.h" + +namespace VideoCommon { + +enum class BufferFlagBits { + Picked = 1 << 0, + CachedWrites = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits) + +/// Tag for creating null buffers with no storage or size +struct NullBufferParams {}; + +/** + * Range tracking buffer container. + * + * It keeps track of the modified CPU and GPU ranges on a CPU page granularity, notifying the given + * rasterizer about state changes in the tracking behavior of the buffer. + * + * The buffer size and address is forcefully aligned to CPU page boundaries. + */ +template <class RasterizerInterface> +class BufferBase { + static constexpr u64 PAGES_PER_WORD = 64; + static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE; + static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; + + /// Vector tracking modified pages tightly packed with small vector optimization + union WordsArray { + /// Returns the pointer to the words state + [[nodiscard]] const u64* Pointer(bool is_short) const noexcept { + return is_short ? &stack : heap; + } + + /// Returns the pointer to the words state + [[nodiscard]] u64* Pointer(bool is_short) noexcept { + return is_short ? &stack : heap; + } + + u64 stack = 0; ///< Small buffers storage + u64* heap; ///< Not-small buffers pointer to the storage + }; + + struct Words { + explicit Words() = default; + explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} { + if (IsShort()) { + cpu.stack = ~u64{0}; + gpu.stack = 0; + cached_cpu.stack = 0; + untracked.stack = ~u64{0}; + } else { + // Share allocation between CPU and GPU pages and set their default values + const size_t num_words = NumWords(); + u64* const alloc = new u64[num_words * 4]; + cpu.heap = alloc; + gpu.heap = alloc + num_words; + cached_cpu.heap = alloc + num_words * 2; + untracked.heap = alloc + num_words * 3; + std::fill_n(cpu.heap, num_words, ~u64{0}); + std::fill_n(gpu.heap, num_words, 0); + std::fill_n(cached_cpu.heap, num_words, 0); + std::fill_n(untracked.heap, num_words, ~u64{0}); + } + // Clean up tailing bits + const u64 last_word_size = size_bytes % BYTES_PER_WORD; + const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE); + const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD; + const u64 last_word = (~u64{0} << shift) >> shift; + cpu.Pointer(IsShort())[NumWords() - 1] = last_word; + untracked.Pointer(IsShort())[NumWords() - 1] = last_word; + } + + ~Words() { + Release(); + } + + Words& operator=(Words&& rhs) noexcept { + Release(); + size_bytes = rhs.size_bytes; + cpu = rhs.cpu; + gpu = rhs.gpu; + cached_cpu = rhs.cached_cpu; + untracked = rhs.untracked; + rhs.cpu.heap = nullptr; + return *this; + } + + Words(Words&& rhs) noexcept + : size_bytes{rhs.size_bytes}, cpu{rhs.cpu}, gpu{rhs.gpu}, + cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} { + rhs.cpu.heap = nullptr; + } + + Words& operator=(const Words&) = delete; + Words(const Words&) = delete; + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return size_bytes <= BYTES_PER_WORD; + } + + /// Returns the number of words of the buffer + [[nodiscard]] size_t NumWords() const noexcept { + return Common::DivCeil(size_bytes, BYTES_PER_WORD); + } + + /// Release buffer resources + void Release() { + if (!IsShort()) { + // CPU written words is the base for the heap allocation + delete[] cpu.heap; + } + } + + u64 size_bytes = 0; + WordsArray cpu; + WordsArray gpu; + WordsArray cached_cpu; + WordsArray untracked; + }; + + enum class Type { + CPU, + GPU, + CachedCPU, + Untracked, + }; + +public: + explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes) + : rasterizer{&rasterizer_}, cpu_addr{Common::AlignDown(cpu_addr_, BYTES_PER_PAGE)}, + words(Common::AlignUp(size_bytes + (cpu_addr_ - cpu_addr), BYTES_PER_PAGE)) {} + + explicit BufferBase(NullBufferParams) {} + + BufferBase& operator=(const BufferBase&) = delete; + BufferBase(const BufferBase&) = delete; + + BufferBase& operator=(BufferBase&&) = default; + BufferBase(BufferBase&&) = default; + + /// Returns the inclusive CPU modified range in a begin end pair + [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr, + u64 query_size) const noexcept { + const u64 offset = query_cpu_addr - cpu_addr; + return ModifiedRegion<Type::CPU>(offset, query_size); + } + + /// Returns the inclusive GPU modified range in a begin end pair + [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr, + u64 query_size) const noexcept { + const u64 offset = query_cpu_addr - cpu_addr; + return ModifiedRegion<Type::GPU>(offset, query_size); + } + + /// Returns true if a region has been modified from the CPU + [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept { + const u64 offset = query_cpu_addr - cpu_addr; + return IsRegionModified<Type::CPU>(offset, query_size); + } + + /// Returns true if a region has been modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept { + const u64 offset = query_cpu_addr - cpu_addr; + return IsRegionModified<Type::GPU>(offset, query_size); + } + + /// Mark region as CPU modified, notifying the rasterizer about this change + void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) { + ChangeRegionState<Type::CPU, true>(dirty_cpu_addr, size); + } + + /// Unmark region as CPU modified, notifying the rasterizer about this change + void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) { + ChangeRegionState<Type::CPU, false>(dirty_cpu_addr, size); + } + + /// Mark region as modified from the host GPU + void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept { + ChangeRegionState<Type::GPU, true>(dirty_cpu_addr, size); + } + + /// Unmark region as modified from the host GPU + void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept { + ChangeRegionState<Type::GPU, false>(dirty_cpu_addr, size); + } + + /// Mark region as modified from the CPU + /// but don't mark it as modified until FlusHCachedWrites is called. + void CachedCpuWrite(VAddr dirty_cpu_addr, u64 size) { + flags |= BufferFlagBits::CachedWrites; + ChangeRegionState<Type::CachedCPU, true>(dirty_cpu_addr, size); + } + + /// Flushes cached CPU writes, and notify the rasterizer about the deltas + void FlushCachedWrites() noexcept { + flags &= ~BufferFlagBits::CachedWrites; + const u64 num_words = NumWords(); + const u64* const cached_words = Array<Type::CachedCPU>(); + u64* const untracked_words = Array<Type::Untracked>(); + u64* const cpu_words = Array<Type::CPU>(); + for (u64 word_index = 0; word_index < num_words; ++word_index) { + const u64 cached_bits = cached_words[word_index]; + NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits); + untracked_words[word_index] |= cached_bits; + cpu_words[word_index] |= cached_bits; + } + } + + /// Call 'func' for each CPU modified range and unmark those pages as CPU modified + template <typename Func> + void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) { + ForEachModifiedRange<Type::CPU>(query_cpu_range, size, func); + } + + /// Call 'func' for each GPU modified range and unmark those pages as GPU modified + template <typename Func> + void ForEachDownloadRange(VAddr query_cpu_range, u64 size, Func&& func) { + ForEachModifiedRange<Type::GPU>(query_cpu_range, size, func); + } + + /// Call 'func' for each GPU modified range and unmark those pages as GPU modified + template <typename Func> + void ForEachDownloadRange(Func&& func) { + ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), func); + } + + /// Mark buffer as picked + void Pick() noexcept { + flags |= BufferFlagBits::Picked; + } + + /// Unmark buffer as picked + void Unpick() noexcept { + flags &= ~BufferFlagBits::Picked; + } + + /// Increases the likeliness of this being a stream buffer + void IncreaseStreamScore(int score) noexcept { + stream_score += score; + } + + /// Returns the likeliness of this being a stream buffer + [[nodiscard]] int StreamScore() const noexcept { + return stream_score; + } + + /// Returns true when vaddr -> vaddr+size is fully contained in the buffer + [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept { + return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes(); + } + + /// Returns true if the buffer has been marked as picked + [[nodiscard]] bool IsPicked() const noexcept { + return True(flags & BufferFlagBits::Picked); + } + + /// Returns true when the buffer has pending cached writes + [[nodiscard]] bool HasCachedWrites() const noexcept { + return True(flags & BufferFlagBits::CachedWrites); + } + + /// Returns the base CPU address of the buffer + [[nodiscard]] VAddr CpuAddr() const noexcept { + return cpu_addr; + } + + /// Returns the offset relative to the given CPU address + /// @pre IsInBounds returns true + [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept { + return static_cast<u32>(other_cpu_addr - cpu_addr); + } + + /// Returns the size in bytes of the buffer + [[nodiscard]] u64 SizeBytes() const noexcept { + return words.size_bytes; + } + +private: + template <Type type> + u64* Array() noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::CachedCPU) { + return words.cached_cpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + template <Type type> + const u64* Array() const noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::CachedCPU) { + return words.cached_cpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + /** + * Change the state of a range of pages + * + * @param dirty_addr Base address to mark or unmark as modified + * @param size Size in bytes to mark or unmark as modified + */ + template <Type type, bool enable> + void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) { + const s64 difference = dirty_addr - cpu_addr; + const u64 offset = std::max<s64>(difference, 0); + size += std::min<s64>(difference, 0); + if (offset >= SizeBytes() || size < 0) { + return; + } + u64* const untracked_words = Array<Type::Untracked>(); + u64* const state_words = Array<type>(); + const u64 offset_end = std::min(offset + size, SizeBytes()); + const u64 begin_page_index = offset / BYTES_PER_PAGE; + const u64 begin_word_index = begin_page_index / PAGES_PER_WORD; + const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE); + const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD); + u64 page_index = begin_page_index % PAGES_PER_WORD; + u64 word_index = begin_word_index; + while (word_index < end_word_index) { + const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD; + const u64 left_offset = + std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD; + const u64 right_offset = page_index; + u64 bits = ~u64{0}; + bits = (bits >> right_offset) << right_offset; + bits = (bits << left_offset) >> left_offset; + if constexpr (type == Type::CPU || type == Type::CachedCPU) { + NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits); + } + if constexpr (enable) { + state_words[word_index] |= bits; + if constexpr (type == Type::CPU || type == Type::CachedCPU) { + untracked_words[word_index] |= bits; + } + } else { + state_words[word_index] &= ~bits; + if constexpr (type == Type::CPU || type == Type::CachedCPU) { + untracked_words[word_index] &= ~bits; + } + } + page_index = 0; + ++word_index; + } + } + + /** + * Notify rasterizer about changes in the CPU tracking state of a word in the buffer + * + * @param word_index Index to the word to notify to the rasterizer + * @param current_bits Current state of the word + * @param new_bits New state of the word + * + * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages + */ + template <bool add_to_rasterizer> + void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const { + u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits; + VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; + while (changed_bits != 0) { + const int empty_bits = std::countr_zero(changed_bits); + addr += empty_bits * BYTES_PER_PAGE; + changed_bits >>= empty_bits; + + const u32 continuous_bits = std::countr_one(changed_bits); + const u64 size = continuous_bits * BYTES_PER_PAGE; + const VAddr begin_addr = addr; + addr += size; + changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0; + rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1); + } + } + + /** + * Loop over each page in the given range, turn off those bits and notify the rasterizer if + * needed. Call the given function on each turned off range. + * + * @param query_cpu_range Base CPU address to loop over + * @param size Size in bytes of the CPU range to loop over + * @param func Function to call for each turned off region + */ + template <Type type, typename Func> + void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) { + static_assert(type != Type::Untracked); + + const s64 difference = query_cpu_range - cpu_addr; + const u64 query_begin = std::max<s64>(difference, 0); + size += std::min<s64>(difference, 0); + if (query_begin >= SizeBytes() || size < 0) { + return; + } + u64* const untracked_words = Array<Type::Untracked>(); + u64* const state_words = Array<type>(); + const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes()); + u64* const words_begin = state_words + query_begin / BYTES_PER_WORD; + u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD); + + const auto modified = [](u64 word) { return word != 0; }; + const auto first_modified_word = std::find_if(words_begin, words_end, modified); + if (first_modified_word == words_end) { + // Exit early when the buffer is not modified + return; + } + const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified); + + const u64 word_index_begin = std::distance(state_words, first_modified_word); + const u64 word_index_end = std::distance(state_words, last_modified_word); + + const unsigned local_page_begin = std::countr_zero(*first_modified_word); + const unsigned local_page_end = + static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]); + const u64 word_page_begin = word_index_begin * PAGES_PER_WORD; + const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD; + const u64 query_page_begin = query_begin / BYTES_PER_PAGE; + const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE); + const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin); + const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end); + const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD; + const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1; + + u64 page_begin = first_word_page_begin; + u64 current_base = 0; + u64 current_size = 0; + bool on_going = false; + for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) { + const bool is_last_word = word_index + 1 == word_index_end; + const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD; + const u64 right_offset = page_begin; + const u64 left_offset = PAGES_PER_WORD - page_end; + u64 bits = ~u64{0}; + bits = (bits >> right_offset) << right_offset; + bits = (bits << left_offset) >> left_offset; + + const u64 current_word = state_words[word_index] & bits; + state_words[word_index] &= ~bits; + + if constexpr (type == Type::CPU) { + const u64 current_bits = untracked_words[word_index] & bits; + untracked_words[word_index] &= ~bits; + NotifyRasterizer<true>(word_index, current_bits, ~u64{0}); + } + // Exclude CPU modified pages when visiting GPU pages + const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0); + u64 page = page_begin; + page_begin = 0; + + while (page < page_end) { + const int empty_bits = std::countr_zero(word >> page); + if (on_going && empty_bits != 0) { + InvokeModifiedRange(func, current_size, current_base); + current_size = 0; + on_going = false; + } + page += empty_bits; + + const int continuous_bits = std::countr_one(word >> page); + if (!on_going && continuous_bits != 0) { + current_base = word_index * PAGES_PER_WORD + page; + on_going = true; + } + current_size += continuous_bits; + page += continuous_bits; + } + } + if (on_going && current_size > 0) { + InvokeModifiedRange(func, current_size, current_base); + } + } + + template <typename Func> + void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) { + const u64 current_size_bytes = current_size * BYTES_PER_PAGE; + const u64 offset_begin = current_base * BYTES_PER_PAGE; + const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes()); + func(offset_begin, offset_end - offset_begin); + } + + /** + * Returns true when a region has been modified + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template <Type type> + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { + static_assert(type != Type::Untracked); + + const u64* const untracked_words = Array<Type::Untracked>(); + const u64* const state_words = Array<type>(); + const u64 num_query_words = size / BYTES_PER_WORD + 1; + const u64 word_begin = offset / BYTES_PER_WORD; + const u64 word_end = std::min(word_begin + num_query_words, NumWords()); + const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE); + u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD; + for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) { + const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0; + const u64 word = state_words[word_index] & ~off_word; + if (word == 0) { + continue; + } + const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit); + const u64 local_page_end = page_end % PAGES_PER_WORD; + const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD; + if (((word >> page_index) << page_index) << page_end_shift != 0) { + return true; + } + } + return false; + } + + /** + * Returns a begin end pair with the inclusive modified region + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template <Type type> + [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept { + static_assert(type != Type::Untracked); + + const u64* const untracked_words = Array<Type::Untracked>(); + const u64* const state_words = Array<type>(); + const u64 num_query_words = size / BYTES_PER_WORD + 1; + const u64 word_begin = offset / BYTES_PER_WORD; + const u64 word_end = std::min(word_begin + num_query_words, NumWords()); + const u64 page_base = offset / BYTES_PER_PAGE; + const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE); + u64 begin = std::numeric_limits<u64>::max(); + u64 end = 0; + for (u64 word_index = word_begin; word_index < word_end; ++word_index) { + const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0; + const u64 word = state_words[word_index] & ~off_word; + if (word == 0) { + continue; + } + const u64 local_page_begin = std::countr_zero(word); + const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word); + const u64 page_index = word_index * PAGES_PER_WORD; + const u64 page_begin = std::max(page_index + local_page_begin, page_base); + const u64 page_end = std::min(page_index + local_page_end, page_limit); + begin = std::min(begin, page_begin); + end = std::max(end, page_end); + } + static constexpr std::pair<u64, u64> EMPTY{0, 0}; + return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY; + } + + /// Returns the number of words of the buffer + [[nodiscard]] size_t NumWords() const noexcept { + return words.NumWords(); + } + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return words.IsShort(); + } + + RasterizerInterface* rasterizer = nullptr; + VAddr cpu_addr = 0; + Words words; + BufferFlagBits flags{}; + int stream_score = 0; +}; + +} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_block.h b/src/video_core/buffer_cache/buffer_block.h deleted file mode 100644 index e64170e66..000000000 --- a/src/video_core/buffer_cache/buffer_block.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <unordered_set> -#include <utility> - -#include "common/alignment.h" -#include "common/common_types.h" -#include "video_core/gpu.h" - -namespace VideoCommon { - -class BufferBlock { -public: - bool Overlaps(VAddr start, VAddr end) const { - return (cpu_addr < end) && (cpu_addr_end > start); - } - - bool IsInside(VAddr other_start, VAddr other_end) const { - return cpu_addr <= other_start && other_end <= cpu_addr_end; - } - - std::size_t Offset(VAddr in_addr) const { - return static_cast<std::size_t>(in_addr - cpu_addr); - } - - VAddr CpuAddr() const { - return cpu_addr; - } - - VAddr CpuAddrEnd() const { - return cpu_addr_end; - } - - void SetCpuAddr(VAddr new_addr) { - cpu_addr = new_addr; - cpu_addr_end = new_addr + size; - } - - std::size_t Size() const { - return size; - } - - u64 Epoch() const { - return epoch; - } - - void SetEpoch(u64 new_epoch) { - epoch = new_epoch; - } - -protected: - explicit BufferBlock(VAddr cpu_addr_, std::size_t size_) : size{size_} { - SetCpuAddr(cpu_addr_); - } - -private: - VAddr cpu_addr{}; - VAddr cpu_addr_end{}; - std::size_t size{}; - u64 epoch{}; -}; - -} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp new file mode 100644 index 000000000..ab32294c8 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -0,0 +1,13 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/microprofile.h" + +namespace VideoCommon { + +MICROPROFILE_DEFINE(GPU_PrepareBuffers, "GPU", "Prepare buffers", MP_RGB(224, 128, 128)); +MICROPROFILE_DEFINE(GPU_BindUploadBuffers, "GPU", "Bind and upload buffers", MP_RGB(224, 128, 128)); +MICROPROFILE_DEFINE(GPU_DownloadMemory, "GPU", "Download buffers", MP_RGB(224, 128, 128)); + +} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b5dc68902..2a6844ab1 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -4,597 +4,1289 @@ #pragma once -#include <list> +#include <algorithm> +#include <array> +#include <deque> #include <memory> #include <mutex> +#include <span> #include <unordered_map> -#include <unordered_set> -#include <utility> #include <vector> #include <boost/container/small_vector.hpp> -#include <boost/icl/interval_set.hpp> -#include <boost/intrusive/set.hpp> -#include "common/alignment.h" -#include "common/assert.h" #include "common/common_types.h" -#include "common/logging/log.h" -#include "core/core.h" +#include "common/div_ceil.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" #include "core/memory.h" #include "core/settings.h" -#include "video_core/buffer_cache/buffer_block.h" -#include "video_core/buffer_cache/map_interval.h" +#include "video_core/buffer_cache/buffer_base.h" +#include "video_core/delayed_destruction_ring.h" +#include "video_core/dirty_flags.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" +#include "video_core/texture_cache/slot_vector.h" +#include "video_core/texture_cache/types.h" namespace VideoCommon { -template <typename Buffer, typename BufferType, typename StreamBuffer> +MICROPROFILE_DECLARE(GPU_PrepareBuffers); +MICROPROFILE_DECLARE(GPU_BindUploadBuffers); +MICROPROFILE_DECLARE(GPU_DownloadMemory); + +using BufferId = SlotId; + +constexpr u32 NUM_VERTEX_BUFFERS = 32; +constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; +constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; +constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; +constexpr u32 NUM_STORAGE_BUFFERS = 16; +constexpr u32 NUM_STAGES = 5; + +template <typename P> class BufferCache { - using IntervalSet = boost::icl::interval_set<VAddr>; - using IntervalType = typename IntervalSet::interval_type; - using VectorMapInterval = boost::container::small_vector<MapInterval*, 1>; + // Page size for caching purposes. + // This is unrelated to the CPU page size and it can be changed as it seems optimal. + static constexpr u32 PAGE_BITS = 16; + static constexpr u64 PAGE_SIZE = u64{1} << PAGE_BITS; + + static constexpr bool IS_OPENGL = P::IS_OPENGL; + static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = + P::HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS; + static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = + P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT; + static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX; + static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX; + static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS; + + static constexpr BufferId NULL_BUFFER_ID{0}; + + using Maxwell = Tegra::Engines::Maxwell3D::Regs; + + using Runtime = typename P::Runtime; + using Buffer = typename P::Buffer; + + struct Empty {}; + + struct OverlapResult { + std::vector<BufferId> ids; + VAddr begin; + VAddr end; + bool has_stream_leap = false; + }; - static constexpr u64 WRITE_PAGE_BIT = 11; - static constexpr u64 BLOCK_PAGE_BITS = 21; - static constexpr u64 BLOCK_PAGE_SIZE = 1ULL << BLOCK_PAGE_BITS; + struct Binding { + VAddr cpu_addr{}; + u32 size{}; + BufferId buffer_id; + }; -public: - struct BufferInfo { - BufferType handle; - u64 offset; - u64 address; + static constexpr Binding NULL_BINDING{ + .cpu_addr = 0, + .size = 0, + .buffer_id = NULL_BUFFER_ID, }; - BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, - bool is_written = false, bool use_fast_cbuf = false) { - std::lock_guard lock{mutex}; +public: + static constexpr u32 SKIP_CACHE_SIZE = 4096; - auto& memory_manager = system.GPU().MemoryManager(); - const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr); - if (!cpu_addr_opt) { - return GetEmptyBuffer(size); - } - const VAddr cpu_addr = *cpu_addr_opt; - - // Cache management is a big overhead, so only cache entries with a given size. - // TODO: Figure out which size is the best for given games. - constexpr std::size_t max_stream_size = 0x800; - if (use_fast_cbuf || size < max_stream_size) { - if (!is_written && !IsRegionWritten(cpu_addr, cpu_addr + size - 1)) { - const bool is_granular = memory_manager.IsGranularRange(gpu_addr, size); - if (use_fast_cbuf) { - u8* dest; - if (is_granular) { - dest = memory_manager.GetPointer(gpu_addr); - } else { - staging_buffer.resize(size); - dest = staging_buffer.data(); - memory_manager.ReadBlockUnsafe(gpu_addr, dest, size); - } - return ConstBufferUpload(dest, size); - } - if (is_granular) { - u8* const host_ptr = memory_manager.GetPointer(gpu_addr); - return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) { - std::memcpy(dest, host_ptr, size); - }); - } else { - return StreamBufferUpload( - size, alignment, [&memory_manager, gpu_addr, size](u8* dest) { - memory_manager.ReadBlockUnsafe(gpu_addr, dest, size); - }); - } - } - } + explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_, + Runtime& runtime_); - Buffer* const block = GetBlock(cpu_addr, size); - MapInterval* const map = MapAddress(block, gpu_addr, cpu_addr, size); - if (!map) { - return GetEmptyBuffer(size); - } - if (is_written) { - map->MarkAsModified(true, GetModifiedTicks()); - if (Settings::IsGPULevelHigh() && - Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - MarkForAsyncFlush(map); - } - if (!map->is_written) { - map->is_written = true; - MarkRegionAsWritten(map->start, map->end - 1); - } - } + void TickFrame(); - return BufferInfo{block->Handle(), block->Offset(cpu_addr), block->Address()}; - } + void WriteMemory(VAddr cpu_addr, u64 size); - /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset. - BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size, - std::size_t alignment = 4) { - std::lock_guard lock{mutex}; - return StreamBufferUpload(size, alignment, [raw_pointer, size](u8* dest) { - std::memcpy(dest, raw_pointer, size); - }); - } + void CachedWriteMemory(VAddr cpu_addr, u64 size); - /// Prepares the buffer cache for data uploading - /// @param max_size Maximum number of bytes that will be uploaded - /// @return True when a stream buffer invalidation was required, false otherwise - bool Map(std::size_t max_size) { - std::lock_guard lock{mutex}; + void DownloadMemory(VAddr cpu_addr, u64 size); - bool invalidated; - std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4); - buffer_offset = buffer_offset_base; + void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size); - return invalidated; - } + void UpdateGraphicsBuffers(bool is_indexed); - /// Finishes the upload stream - void Unmap() { - std::lock_guard lock{mutex}; - stream_buffer->Unmap(buffer_offset - buffer_offset_base); - } + void UpdateComputeBuffers(); - /// Function called at the end of each frame, inteded for deferred operations - void TickFrame() { - ++epoch; + void BindHostGeometryBuffers(bool is_indexed); - while (!pending_destruction.empty()) { - // Delay at least 4 frames before destruction. - // This is due to triple buffering happening on some drivers. - static constexpr u64 epochs_to_destroy = 5; - if (pending_destruction.front()->Epoch() + epochs_to_destroy > epoch) { - break; - } - pending_destruction.pop(); - } - } + void BindHostStageBuffers(size_t stage); - /// Write any cached resources overlapping the specified region back to memory - void FlushRegion(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + void BindHostComputeBuffers(); - VectorMapInterval objects = GetMapsInRange(addr, size); - std::sort(objects.begin(), objects.end(), - [](MapInterval* lhs, MapInterval* rhs) { return lhs->ticks < rhs->ticks; }); - for (MapInterval* object : objects) { - if (object->is_modified && object->is_registered) { - mutex.unlock(); - FlushMap(object); - mutex.lock(); - } - } - } + void SetEnabledUniformBuffers(size_t stage, u32 enabled); - bool MustFlushRegion(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + void SetEnabledComputeUniformBuffers(u32 enabled); - const VectorMapInterval objects = GetMapsInRange(addr, size); - return std::any_of(objects.cbegin(), objects.cend(), [](const MapInterval* map) { - return map->is_modified && map->is_registered; - }); - } + void UnbindGraphicsStorageBuffers(size_t stage); - /// Mark the specified region as being invalidated - void InvalidateRegion(VAddr addr, u64 size) { - std::lock_guard lock{mutex}; + void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, + bool is_written); - for (auto& object : GetMapsInRange(addr, size)) { - if (object->is_registered) { - Unregister(object); - } - } - } + void UnbindComputeStorageBuffers(); - void OnCPUWrite(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, + bool is_written); - for (MapInterval* object : GetMapsInRange(addr, size)) { - if (object->is_memory_marked && object->is_registered) { - UnmarkMemory(object); - object->is_sync_pending = true; - marked_for_unregister.emplace_back(object); - } - } - } + void FlushCachedWrites(); - void SyncGuestHost() { - std::lock_guard lock{mutex}; + /// Return true when there are uncommitted buffers to be downloaded + [[nodiscard]] bool HasUncommittedFlushes() const noexcept; - for (auto& object : marked_for_unregister) { - if (object->is_registered) { - object->is_sync_pending = false; - Unregister(object); - } + /// Return true when the caller should wait for async downloads + [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept; + + /// Commit asynchronous downloads + void CommitAsyncFlushes(); + + /// Pop asynchronous downloads + void PopAsyncFlushes(); + + /// Return true when a CPU region is modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); + + std::mutex mutex; + +private: + template <typename Func> + static void ForEachEnabledBit(u32 enabled_mask, Func&& func) { + for (u32 index = 0; enabled_mask != 0; ++index, enabled_mask >>= 1) { + const int disabled_bits = std::countr_zero(enabled_mask); + index += disabled_bits; + enabled_mask >>= disabled_bits; + func(index); } - marked_for_unregister.clear(); } - void CommitAsyncFlushes() { - if (uncommitted_flushes) { - auto commit_list = std::make_shared<std::list<MapInterval*>>(); - for (MapInterval* map : *uncommitted_flushes) { - if (map->is_registered && map->is_modified) { - // TODO(Blinkhawk): Implement backend asynchronous flushing - // AsyncFlushMap(map) - commit_list->push_back(map); - } - } - if (!commit_list->empty()) { - committed_flushes.push_back(commit_list); - } else { - committed_flushes.emplace_back(); + template <typename Func> + void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) { + const u64 page_end = Common::DivCeil(cpu_addr + size, PAGE_SIZE); + for (u64 page = cpu_addr >> PAGE_BITS; page < page_end;) { + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + ++page; + continue; } - } else { - committed_flushes.emplace_back(); + Buffer& buffer = slot_buffers[buffer_id]; + func(buffer_id, buffer); + + const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); + page = Common::DivCeil(end_addr, PAGE_SIZE); } - uncommitted_flushes.reset(); } - bool ShouldWaitAsyncFlushes() const { - return !committed_flushes.empty() && committed_flushes.front() != nullptr; + static bool IsRangeGranular(VAddr cpu_addr, size_t size) { + return (cpu_addr & ~Core::Memory::PAGE_MASK) == + ((cpu_addr + size) & ~Core::Memory::PAGE_MASK); } - bool HasUncommittedFlushes() const { - return uncommitted_flushes != nullptr; - } + void BindHostIndexBuffer(); - void PopAsyncFlushes() { - if (committed_flushes.empty()) { - return; - } - auto& flush_list = committed_flushes.front(); - if (!flush_list) { - committed_flushes.pop_front(); - return; - } - for (MapInterval* map : *flush_list) { - if (map->is_registered) { - // TODO(Blinkhawk): Replace this for reading the asynchronous flush - FlushMap(map); - } - } - committed_flushes.pop_front(); - } + void BindHostVertexBuffers(); - virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0; + void BindHostGraphicsUniformBuffers(size_t stage); -protected: - explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, - std::unique_ptr<StreamBuffer> stream_buffer) - : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)} {} + void BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind); - ~BufferCache() = default; + void BindHostGraphicsStorageBuffers(size_t stage); - virtual std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) = 0; + void BindHostTransformFeedbackBuffers(); - virtual BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) { - return {}; - } + void BindHostComputeUniformBuffers(); - /// Register an object into the cache - MapInterval* Register(MapInterval new_map, bool inherit_written = false) { - const VAddr cpu_addr = new_map.start; - if (!cpu_addr) { - LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}", - new_map.gpu_addr); - return nullptr; - } - const std::size_t size = new_map.end - new_map.start; - new_map.is_registered = true; - rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1); - new_map.is_memory_marked = true; - if (inherit_written) { - MarkRegionAsWritten(new_map.start, new_map.end - 1); - new_map.is_written = true; - } - MapInterval* const storage = mapped_addresses_allocator.Allocate(); - *storage = new_map; - mapped_addresses.insert(*storage); - return storage; - } + void BindHostComputeStorageBuffers(); - void UnmarkMemory(MapInterval* map) { - if (!map->is_memory_marked) { - return; - } - const std::size_t size = map->end - map->start; - rasterizer.UpdatePagesCachedCount(map->start, size, -1); - map->is_memory_marked = false; - } - - /// Unregisters an object from the cache - void Unregister(MapInterval* map) { - UnmarkMemory(map); - map->is_registered = false; - if (map->is_sync_pending) { - map->is_sync_pending = false; - marked_for_unregister.remove(map); + void DoUpdateGraphicsBuffers(bool is_indexed); + + void DoUpdateComputeBuffers(); + + void UpdateIndexBuffer(); + + void UpdateVertexBuffers(); + + void UpdateVertexBuffer(u32 index); + + void UpdateUniformBuffers(size_t stage); + + void UpdateStorageBuffers(size_t stage); + + void UpdateTransformFeedbackBuffers(); + + void UpdateTransformFeedbackBuffer(u32 index); + + void UpdateComputeUniformBuffers(); + + void UpdateComputeStorageBuffers(); + + void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size); + + [[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size); + + [[nodiscard]] OverlapResult ResolveOverlaps(VAddr cpu_addr, u32 wanted_size); + + void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); + + [[nodiscard]] BufferId CreateBuffer(VAddr cpu_addr, u32 wanted_size); + + void Register(BufferId buffer_id); + + void Unregister(BufferId buffer_id); + + template <bool insert> + void ChangeRegister(BufferId buffer_id); + + void SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size); + + void SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size); + + void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, + std::span<BufferCopy> copies); + + void ImmediateUploadMemory(Buffer& buffer, u64 largest_copy, + std::span<const BufferCopy> copies); + + void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies); + + void DeleteBuffer(BufferId buffer_id); + + void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id); + + void NotifyBufferDeletion(); + + [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const; + + [[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size); + + [[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity); + + [[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept; + + VideoCore::RasterizerInterface& rasterizer; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; + Core::Memory::Memory& cpu_memory; + Runtime& runtime; + + SlotVector<Buffer> slot_buffers; + DelayedDestructionRing<Buffer, 8> delayed_destruction_ring; + + u32 last_index_count = 0; + + Binding index_buffer; + std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers; + std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers; + std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers; + std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers; + + std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers; + std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers; + + std::array<u32, NUM_STAGES> enabled_uniform_buffers{}; + u32 enabled_compute_uniform_buffers = 0; + + std::array<u32, NUM_STAGES> enabled_storage_buffers{}; + std::array<u32, NUM_STAGES> written_storage_buffers{}; + u32 enabled_compute_storage_buffers = 0; + u32 written_compute_storage_buffers = 0; + + std::array<u32, NUM_STAGES> fast_bound_uniform_buffers{}; + + bool has_deleted_buffers = false; + + std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty> + dirty_uniform_buffers{}; + + std::vector<BufferId> cached_write_buffer_ids; + + // TODO: This data structure is not optimal and it should be reworked + std::vector<BufferId> uncommitted_downloads; + std::deque<std::vector<BufferId>> committed_downloads; + + size_t immediate_buffer_capacity = 0; + std::unique_ptr<u8[]> immediate_buffer_alloc; + + std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table; +}; + +template <class P> +BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_, + Runtime& runtime_) + : rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_}, + gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, runtime{runtime_} { + // Ensure the first slot is used for the null buffer + void(slot_buffers.insert(runtime, NullBufferParams{})); +} + +template <class P> +void BufferCache<P>::TickFrame() { + delayed_destruction_ring.Tick(); +} + +template <class P> +void BufferCache<P>::WriteMemory(VAddr cpu_addr, u64 size) { + ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { + buffer.MarkRegionAsCpuModified(cpu_addr, size); + }); +} + +template <class P> +void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) { + ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) { + if (!buffer.HasCachedWrites()) { + cached_write_buffer_ids.push_back(buffer_id); } - if (map->is_written) { - UnmarkRegionAsWritten(map->start, map->end - 1); + buffer.CachedCpuWrite(cpu_addr, size); + }); +} + +template <class P> +void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) { + ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { + boost::container::small_vector<BufferCopy, 1> copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { + copies.push_back(BufferCopy{ + .src_offset = range_offset, + .dst_offset = total_size_bytes, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }); + if (total_size_bytes == 0) { + return; } - const auto it = mapped_addresses.find(*map); - ASSERT(it != mapped_addresses.end()); - mapped_addresses.erase(it); - mapped_addresses_allocator.Release(map); - } - -private: - MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) { - const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size); - if (overlaps.empty()) { - auto& memory_manager = system.GPU().MemoryManager(); - const VAddr cpu_addr_end = cpu_addr + size; - if (memory_manager.IsGranularRange(gpu_addr, size)) { - u8* host_ptr = memory_manager.GetPointer(gpu_addr); - block->Upload(block->Offset(cpu_addr), size, host_ptr); - } else { - staging_buffer.resize(size); - memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size); - block->Upload(block->Offset(cpu_addr), size, staging_buffer.data()); + MICROPROFILE_SCOPE(GPU_DownloadMemory); + + if constexpr (USE_MEMORY_MAPS) { + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + const u8* const mapped_memory = download_staging.mapped_span.data(); + const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); + for (BufferCopy& copy : copies) { + // Modify copies to have the staging offset in mind + copy.dst_offset += download_staging.offset; } - return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr)); - } - - const VAddr cpu_addr_end = cpu_addr + size; - if (overlaps.size() == 1) { - MapInterval* const current_map = overlaps[0]; - if (current_map->IsInside(cpu_addr, cpu_addr_end)) { - return current_map; + runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); + runtime.Finish(); + for (const BufferCopy& copy : copies) { + const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; + // Undo the modified offset + const u64 dst_offset = copy.dst_offset - download_staging.offset; + const u8* copy_mapped_memory = mapped_memory + dst_offset; + cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size); + } + } else { + const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); + for (const BufferCopy& copy : copies) { + buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); + const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; + cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size); } } - VAddr new_start = cpu_addr; - VAddr new_end = cpu_addr_end; - bool write_inheritance = false; - bool modified_inheritance = false; - // Calculate new buffer parameters - for (MapInterval* overlap : overlaps) { - new_start = std::min(overlap->start, new_start); - new_end = std::max(overlap->end, new_end); - write_inheritance |= overlap->is_written; - modified_inheritance |= overlap->is_modified; + }); +} + +template <class P> +void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, + u32 size) { + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + if (!cpu_addr) { + uniform_buffers[stage][index] = NULL_BINDING; + return; + } + const Binding binding{ + .cpu_addr = *cpu_addr, + .size = size, + .buffer_id = BufferId{}, + }; + uniform_buffers[stage][index] = binding; +} + +template <class P> +void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) { + MICROPROFILE_SCOPE(GPU_PrepareBuffers); + do { + has_deleted_buffers = false; + DoUpdateGraphicsBuffers(is_indexed); + } while (has_deleted_buffers); +} + +template <class P> +void BufferCache<P>::UpdateComputeBuffers() { + MICROPROFILE_SCOPE(GPU_PrepareBuffers); + do { + has_deleted_buffers = false; + DoUpdateComputeBuffers(); + } while (has_deleted_buffers); +} + +template <class P> +void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) { + MICROPROFILE_SCOPE(GPU_BindUploadBuffers); + if (is_indexed) { + BindHostIndexBuffer(); + } else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) { + const auto& regs = maxwell3d.regs; + if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) { + runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count); } - GPUVAddr new_gpu_addr = gpu_addr + new_start - cpu_addr; - for (auto& overlap : overlaps) { - Unregister(overlap); + } + BindHostVertexBuffers(); + BindHostTransformFeedbackBuffers(); +} + +template <class P> +void BufferCache<P>::BindHostStageBuffers(size_t stage) { + MICROPROFILE_SCOPE(GPU_BindUploadBuffers); + BindHostGraphicsUniformBuffers(stage); + BindHostGraphicsStorageBuffers(stage); +} + +template <class P> +void BufferCache<P>::BindHostComputeBuffers() { + MICROPROFILE_SCOPE(GPU_BindUploadBuffers); + BindHostComputeUniformBuffers(); + BindHostComputeStorageBuffers(); +} + +template <class P> +void BufferCache<P>::SetEnabledUniformBuffers(size_t stage, u32 enabled) { + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + if (enabled_uniform_buffers[stage] != enabled) { + dirty_uniform_buffers[stage] = ~u32{0}; } - UpdateBlock(block, new_start, new_end, overlaps); - - const MapInterval new_map{new_start, new_end, new_gpu_addr}; - MapInterval* const map = Register(new_map, write_inheritance); - if (!map) { - return nullptr; + } + enabled_uniform_buffers[stage] = enabled; +} + +template <class P> +void BufferCache<P>::SetEnabledComputeUniformBuffers(u32 enabled) { + enabled_compute_uniform_buffers = enabled; +} + +template <class P> +void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) { + enabled_storage_buffers[stage] = 0; + written_storage_buffers[stage] = 0; +} + +template <class P> +void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, + u32 cbuf_offset, bool is_written) { + enabled_storage_buffers[stage] |= 1U << ssbo_index; + written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index; + + const auto& cbufs = maxwell3d.state.shader_stages[stage]; + const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset; + storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr); +} + +template <class P> +void BufferCache<P>::UnbindComputeStorageBuffers() { + enabled_compute_storage_buffers = 0; + written_compute_storage_buffers = 0; +} + +template <class P> +void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, + bool is_written) { + enabled_compute_storage_buffers |= 1U << ssbo_index; + written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index; + + const auto& launch_desc = kepler_compute.launch_description; + ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0); + + const auto& cbufs = launch_desc.const_buffer_config; + const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset; + compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr); +} + +template <class P> +void BufferCache<P>::FlushCachedWrites() { + for (const BufferId buffer_id : cached_write_buffer_ids) { + slot_buffers[buffer_id].FlushCachedWrites(); + } + cached_write_buffer_ids.clear(); +} + +template <class P> +bool BufferCache<P>::HasUncommittedFlushes() const noexcept { + return !uncommitted_downloads.empty(); +} + +template <class P> +bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept { + return !committed_downloads.empty() && !committed_downloads.front().empty(); +} + +template <class P> +void BufferCache<P>::CommitAsyncFlushes() { + // This is intentionally passing the value by copy + committed_downloads.push_front(uncommitted_downloads); + uncommitted_downloads.clear(); +} + +template <class P> +void BufferCache<P>::PopAsyncFlushes() { + if (committed_downloads.empty()) { + return; + } + auto scope_exit_pop_download = detail::ScopeExit([this] { committed_downloads.pop_back(); }); + const std::span<const BufferId> download_ids = committed_downloads.back(); + if (download_ids.empty()) { + return; + } + MICROPROFILE_SCOPE(GPU_DownloadMemory); + + boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + for (const BufferId buffer_id : download_ids) { + slot_buffers[buffer_id].ForEachDownloadRange([&](u64 range_offset, u64 range_size) { + downloads.push_back({ + BufferCopy{ + .src_offset = range_offset, + .dst_offset = total_size_bytes, + .size = range_size, + }, + buffer_id, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }); + } + if (downloads.empty()) { + return; + } + if constexpr (USE_MEMORY_MAPS) { + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + for (auto& [copy, buffer_id] : downloads) { + // Have in mind the staging buffer offset for the copy + copy.dst_offset += download_staging.offset; + const std::array copies{copy}; + runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies); } - if (modified_inheritance) { - map->MarkAsModified(true, GetModifiedTicks()); - if (Settings::IsGPULevelHigh() && - Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - MarkForAsyncFlush(map); - } + runtime.Finish(); + for (const auto [copy, buffer_id] : downloads) { + const Buffer& buffer = slot_buffers[buffer_id]; + const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; + // Undo the modified offset + const u64 dst_offset = copy.dst_offset - download_staging.offset; + const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset; + cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size); + } + } else { + const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); + for (const auto [copy, buffer_id] : downloads) { + Buffer& buffer = slot_buffers[buffer_id]; + buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); + const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; + cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); } - return map; } - - void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) { - const IntervalType base_interval{start, end}; - IntervalSet interval_set{}; - interval_set.add(base_interval); - for (auto& overlap : overlaps) { - const IntervalType subtract{overlap->start, overlap->end}; - interval_set.subtract(subtract); +} + +template <class P> +bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { + const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); + for (u64 page = addr >> PAGE_BITS; page < page_end;) { + const BufferId image_id = page_table[page]; + if (!image_id) { + ++page; + continue; } - for (auto& interval : interval_set) { - const std::size_t size = interval.upper() - interval.lower(); - if (size == 0) { - continue; - } - staging_buffer.resize(size); - system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size); - block->Upload(block->Offset(interval.lower()), size, staging_buffer.data()); + Buffer& buffer = slot_buffers[image_id]; + if (buffer.IsRegionGpuModified(addr, size)) { + return true; } + const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); + page = Common::DivCeil(end_addr, PAGE_SIZE); } - - VectorMapInterval GetMapsInRange(VAddr addr, std::size_t size) { - VectorMapInterval result; - if (size == 0) { - return result; + return false; +} + +template <class P> +void BufferCache<P>::BindHostIndexBuffer() { + Buffer& buffer = slot_buffers[index_buffer.buffer_id]; + const u32 offset = buffer.Offset(index_buffer.cpu_addr); + const u32 size = index_buffer.size; + SynchronizeBuffer(buffer, index_buffer.cpu_addr, size); + if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) { + runtime.BindIndexBuffer(buffer, offset, size); + } else { + runtime.BindIndexBuffer(maxwell3d.regs.draw.topology, maxwell3d.regs.index_array.format, + maxwell3d.regs.index_array.first, maxwell3d.regs.index_array.count, + buffer, offset, size); + } +} + +template <class P> +void BufferCache<P>::BindHostVertexBuffers() { + auto& flags = maxwell3d.dirty.flags; + for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { + const Binding& binding = vertex_buffers[index]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); + if (!flags[Dirty::VertexBuffer0 + index]) { + continue; } + flags[Dirty::VertexBuffer0 + index] = false; + + const u32 stride = maxwell3d.regs.vertex_array[index].stride; + const u32 offset = buffer.Offset(binding.cpu_addr); + runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride); + } +} - const VAddr addr_end = addr + size; - auto it = mapped_addresses.lower_bound(addr); - if (it != mapped_addresses.begin()) { - --it; +template <class P> +void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) { + u32 dirty = ~0U; + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + dirty = std::exchange(dirty_uniform_buffers[stage], 0); + } + u32 binding_index = 0; + ForEachEnabledBit(enabled_uniform_buffers[stage], [&](u32 index) { + const bool needs_bind = ((dirty >> index) & 1) != 0; + BindHostGraphicsUniformBuffer(stage, index, binding_index, needs_bind); + if constexpr (NEEDS_BIND_UNIFORM_INDEX) { + ++binding_index; } - while (it != mapped_addresses.end() && it->start < addr_end) { - if (it->Overlaps(addr, addr_end)) { - result.push_back(&*it); + }); +} + +template <class P> +void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, + bool needs_bind) { + const Binding& binding = uniform_buffers[stage][index]; + const VAddr cpu_addr = binding.cpu_addr; + const u32 size = binding.size; + Buffer& buffer = slot_buffers[binding.buffer_id]; + if (size <= SKIP_CACHE_SIZE && !buffer.IsRegionGpuModified(cpu_addr, size)) { + if constexpr (IS_OPENGL) { + if (runtime.HasFastBufferSubData()) { + // Fast path for Nvidia + if (!HasFastUniformBufferBound(stage, binding_index)) { + // We only have to bind when the currently bound buffer is not the fast version + runtime.BindFastUniformBuffer(stage, binding_index, size); + } + const auto span = ImmediateBufferWithData(cpu_addr, size); + runtime.PushFastUniformBuffer(stage, binding_index, span); + return; } - ++it; } - return result; - } + fast_bound_uniform_buffers[stage] |= 1U << binding_index; - /// Returns a ticks counter used for tracking when cached objects were last modified - u64 GetModifiedTicks() { - return ++modified_ticks; + // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan + const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size); + cpu_memory.ReadBlockUnsafe(cpu_addr, span.data(), size); + return; } - - void FlushMap(MapInterval* map) { - const auto it = blocks.find(map->start >> BLOCK_PAGE_BITS); - ASSERT_OR_EXECUTE(it != blocks.end(), return;); - - std::shared_ptr<Buffer> block = it->second; - - const std::size_t size = map->end - map->start; - staging_buffer.resize(size); - block->Download(block->Offset(map->start), size, staging_buffer.data()); - system.Memory().WriteBlockUnsafe(map->start, staging_buffer.data(), size); - map->MarkAsModified(false, 0); + // Classic cached path + SynchronizeBuffer(buffer, cpu_addr, size); + if (!needs_bind && !HasFastUniformBufferBound(stage, binding_index)) { + // Skip binding if it's not needed and if the bound buffer is not the fast version + // This exists to avoid instances where the fast buffer is bound and a GPU write happens + return; } + fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); - template <typename Callable> - BufferInfo StreamBufferUpload(std::size_t size, std::size_t alignment, Callable&& callable) { - AlignBuffer(alignment); - const std::size_t uploaded_offset = buffer_offset; - callable(buffer_ptr); - - buffer_ptr += size; - buffer_offset += size; - return BufferInfo{stream_buffer->Handle(), uploaded_offset, stream_buffer->Address()}; + const u32 offset = buffer.Offset(cpu_addr); + if constexpr (NEEDS_BIND_UNIFORM_INDEX) { + runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size); + } else { + runtime.BindUniformBuffer(buffer, offset, size); } +} + +template <class P> +void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { + u32 binding_index = 0; + ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) { + const Binding& binding = storage_buffers[stage][index]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + const u32 size = binding.size; + SynchronizeBuffer(buffer, binding.cpu_addr, size); + + const u32 offset = buffer.Offset(binding.cpu_addr); + const bool is_written = ((written_storage_buffers[stage] >> index) & 1) != 0; + if constexpr (NEEDS_BIND_STORAGE_INDEX) { + runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); + ++binding_index; + } else { + runtime.BindStorageBuffer(buffer, offset, size, is_written); + } + }); +} - void AlignBuffer(std::size_t alignment) { - // Align the offset, not the mapped pointer - const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment); - buffer_ptr += offset_aligned - buffer_offset; - buffer_offset = offset_aligned; +template <class P> +void BufferCache<P>::BindHostTransformFeedbackBuffers() { + if (maxwell3d.regs.tfb_enabled == 0) { + return; } + for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { + const Binding& binding = transform_feedback_buffers[index]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + const u32 size = binding.size; + SynchronizeBuffer(buffer, binding.cpu_addr, size); + + const u32 offset = buffer.Offset(binding.cpu_addr); + runtime.BindTransformFeedbackBuffer(index, buffer, offset, size); + } +} - std::shared_ptr<Buffer> EnlargeBlock(std::shared_ptr<Buffer> buffer) { - const std::size_t old_size = buffer->Size(); - const std::size_t new_size = old_size + BLOCK_PAGE_SIZE; - const VAddr cpu_addr = buffer->CpuAddr(); - std::shared_ptr<Buffer> new_buffer = CreateBlock(cpu_addr, new_size); - new_buffer->CopyFrom(*buffer, 0, 0, old_size); - QueueDestruction(std::move(buffer)); - - const VAddr cpu_addr_end = cpu_addr + new_size - 1; - const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS; - for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) { - blocks.insert_or_assign(page_start, new_buffer); +template <class P> +void BufferCache<P>::BindHostComputeUniformBuffers() { + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + // Mark all uniform buffers as dirty + dirty_uniform_buffers.fill(~u32{0}); + } + u32 binding_index = 0; + ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) { + const Binding& binding = compute_uniform_buffers[index]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + const u32 size = binding.size; + SynchronizeBuffer(buffer, binding.cpu_addr, size); + + const u32 offset = buffer.Offset(binding.cpu_addr); + if constexpr (NEEDS_BIND_UNIFORM_INDEX) { + runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size); + ++binding_index; + } else { + runtime.BindUniformBuffer(buffer, offset, size); } + }); +} + +template <class P> +void BufferCache<P>::BindHostComputeStorageBuffers() { + u32 binding_index = 0; + ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) { + const Binding& binding = compute_storage_buffers[index]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + const u32 size = binding.size; + SynchronizeBuffer(buffer, binding.cpu_addr, size); + + const u32 offset = buffer.Offset(binding.cpu_addr); + const bool is_written = ((written_compute_storage_buffers >> index) & 1) != 0; + if constexpr (NEEDS_BIND_STORAGE_INDEX) { + runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); + ++binding_index; + } else { + runtime.BindStorageBuffer(buffer, offset, size, is_written); + } + }); +} - return new_buffer; +template <class P> +void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { + if (is_indexed) { + UpdateIndexBuffer(); } + UpdateVertexBuffers(); + UpdateTransformFeedbackBuffers(); + for (size_t stage = 0; stage < NUM_STAGES; ++stage) { + UpdateUniformBuffers(stage); + UpdateStorageBuffers(stage); + } +} + +template <class P> +void BufferCache<P>::DoUpdateComputeBuffers() { + UpdateComputeUniformBuffers(); + UpdateComputeStorageBuffers(); +} + +template <class P> +void BufferCache<P>::UpdateIndexBuffer() { + // We have to check for the dirty flags and index count + // The index count is currently changed without updating the dirty flags + const auto& index_array = maxwell3d.regs.index_array; + auto& flags = maxwell3d.dirty.flags; + if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) { + return; + } + flags[Dirty::IndexBuffer] = false; + last_index_count = index_array.count; + + const GPUVAddr gpu_addr_begin = index_array.StartAddress(); + const GPUVAddr gpu_addr_end = index_array.EndAddress(); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin); + const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); + const u32 draw_size = index_array.count * index_array.FormatSizeInBytes(); + const u32 size = std::min(address_size, draw_size); + if (size == 0 || !cpu_addr) { + index_buffer = NULL_BINDING; + return; + } + index_buffer = Binding{ + .cpu_addr = *cpu_addr, + .size = size, + .buffer_id = FindBuffer(*cpu_addr, size), + }; +} - std::shared_ptr<Buffer> MergeBlocks(std::shared_ptr<Buffer> first, - std::shared_ptr<Buffer> second) { - const std::size_t size_1 = first->Size(); - const std::size_t size_2 = second->Size(); - const VAddr first_addr = first->CpuAddr(); - const VAddr second_addr = second->CpuAddr(); - const VAddr new_addr = std::min(first_addr, second_addr); - const std::size_t new_size = size_1 + size_2; - - std::shared_ptr<Buffer> new_buffer = CreateBlock(new_addr, new_size); - new_buffer->CopyFrom(*first, 0, new_buffer->Offset(first_addr), size_1); - new_buffer->CopyFrom(*second, 0, new_buffer->Offset(second_addr), size_2); - QueueDestruction(std::move(first)); - QueueDestruction(std::move(second)); +template <class P> +void BufferCache<P>::UpdateVertexBuffers() { + auto& flags = maxwell3d.dirty.flags; + if (!maxwell3d.dirty.flags[Dirty::VertexBuffers]) { + return; + } + flags[Dirty::VertexBuffers] = false; - const VAddr cpu_addr_end = new_addr + new_size - 1; - const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS; - for (u64 page_start = new_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) { - blocks.insert_or_assign(page_start, new_buffer); - } - return new_buffer; + for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { + UpdateVertexBuffer(index); } +} - Buffer* GetBlock(VAddr cpu_addr, std::size_t size) { - std::shared_ptr<Buffer> found; +template <class P> +void BufferCache<P>::UpdateVertexBuffer(u32 index) { + if (!maxwell3d.dirty.flags[Dirty::VertexBuffer0 + index]) { + return; + } + const auto& array = maxwell3d.regs.vertex_array[index]; + const auto& limit = maxwell3d.regs.vertex_array_limit[index]; + const GPUVAddr gpu_addr_begin = array.StartAddress(); + const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1; + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin); + const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); + const u32 size = address_size; // TODO: Analyze stride and number of vertices + if (array.enable == 0 || size == 0 || !cpu_addr) { + vertex_buffers[index] = NULL_BINDING; + return; + } + vertex_buffers[index] = Binding{ + .cpu_addr = *cpu_addr, + .size = size, + .buffer_id = FindBuffer(*cpu_addr, size), + }; +} + +template <class P> +void BufferCache<P>::UpdateUniformBuffers(size_t stage) { + ForEachEnabledBit(enabled_uniform_buffers[stage], [&](u32 index) { + Binding& binding = uniform_buffers[stage][index]; + if (binding.buffer_id) { + // Already updated + return; + } + // Mark as dirty + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + dirty_uniform_buffers[stage] |= 1U << index; + } + // Resolve buffer + binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); + }); +} + +template <class P> +void BufferCache<P>::UpdateStorageBuffers(size_t stage) { + const u32 written_mask = written_storage_buffers[stage]; + ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) { + // Resolve buffer + Binding& binding = storage_buffers[stage][index]; + const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); + binding.buffer_id = buffer_id; + // Mark buffer as written if needed + if (((written_mask >> index) & 1) != 0) { + MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size); + } + }); +} - const VAddr cpu_addr_end = cpu_addr + size - 1; - const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS; - for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) { - auto it = blocks.find(page_start); - if (it == blocks.end()) { - if (found) { - found = EnlargeBlock(found); - continue; - } - const VAddr start_addr = page_start << BLOCK_PAGE_BITS; - found = CreateBlock(start_addr, BLOCK_PAGE_SIZE); - blocks.insert_or_assign(page_start, found); - continue; - } - if (!found) { - found = it->second; - continue; - } - if (found != it->second) { - found = MergeBlocks(std::move(found), it->second); +template <class P> +void BufferCache<P>::UpdateTransformFeedbackBuffers() { + if (maxwell3d.regs.tfb_enabled == 0) { + return; + } + for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { + UpdateTransformFeedbackBuffer(index); + } +} + +template <class P> +void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) { + const auto& binding = maxwell3d.regs.tfb_bindings[index]; + const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset; + const u32 size = binding.buffer_size; + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) { + transform_feedback_buffers[index] = NULL_BINDING; + return; + } + const BufferId buffer_id = FindBuffer(*cpu_addr, size); + transform_feedback_buffers[index] = Binding{ + .cpu_addr = *cpu_addr, + .size = size, + .buffer_id = buffer_id, + }; + MarkWrittenBuffer(buffer_id, *cpu_addr, size); +} + +template <class P> +void BufferCache<P>::UpdateComputeUniformBuffers() { + ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) { + Binding& binding = compute_uniform_buffers[index]; + binding = NULL_BINDING; + const auto& launch_desc = kepler_compute.launch_description; + if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) { + const auto& cbuf = launch_desc.const_buffer_config[index]; + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(cbuf.Address()); + if (cpu_addr) { + binding.cpu_addr = *cpu_addr; + binding.size = cbuf.size; } } - return found.get(); + binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); + }); +} + +template <class P> +void BufferCache<P>::UpdateComputeStorageBuffers() { + ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) { + // Resolve buffer + Binding& binding = compute_storage_buffers[index]; + const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); + binding.buffer_id = buffer_id; + // Mark as written if needed + if (((written_compute_storage_buffers >> index) & 1) != 0) { + MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size); + } + }); +} + +template <class P> +void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { + Buffer& buffer = slot_buffers[buffer_id]; + buffer.MarkRegionAsGpuModified(cpu_addr, size); + + const bool is_accuracy_high = Settings::IsGPULevelHigh(); + const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); + if (!is_accuracy_high || !is_async) { + return; + } + if (std::ranges::find(uncommitted_downloads, buffer_id) != uncommitted_downloads.end()) { + // Already inserted + return; } + uncommitted_downloads.push_back(buffer_id); +} - void MarkRegionAsWritten(VAddr start, VAddr end) { - const u64 page_end = end >> WRITE_PAGE_BIT; - for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) { - if (const auto [it, inserted] = written_pages.emplace(page_start, 1); !inserted) { - ++it->second; - } +template <class P> +BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) { + if (cpu_addr == 0) { + return NULL_BUFFER_ID; + } + const u64 page = cpu_addr >> PAGE_BITS; + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + return CreateBuffer(cpu_addr, size); + } + const Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(cpu_addr, size)) { + return buffer_id; + } + return CreateBuffer(cpu_addr, size); +} + +template <class P> +typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu_addr, + u32 wanted_size) { + static constexpr int STREAM_LEAP_THRESHOLD = 16; + std::vector<BufferId> overlap_ids; + VAddr begin = cpu_addr; + VAddr end = cpu_addr + wanted_size; + int stream_score = 0; + bool has_stream_leap = false; + for (; cpu_addr >> PAGE_BITS < Common::DivCeil(end, PAGE_SIZE); cpu_addr += PAGE_SIZE) { + const BufferId overlap_id = page_table[cpu_addr >> PAGE_BITS]; + if (!overlap_id) { + continue; + } + Buffer& overlap = slot_buffers[overlap_id]; + if (overlap.IsPicked()) { + continue; + } + overlap_ids.push_back(overlap_id); + overlap.Pick(); + const VAddr overlap_cpu_addr = overlap.CpuAddr(); + if (overlap_cpu_addr < begin) { + cpu_addr = begin = overlap_cpu_addr; + } + end = std::max(end, overlap_cpu_addr + overlap.SizeBytes()); + + stream_score += overlap.StreamScore(); + if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) { + // When this memory region has been joined a bunch of times, we assume it's being used + // as a stream buffer. Increase the size to skip constantly recreating buffers. + has_stream_leap = true; + end += PAGE_SIZE * 256; } } - - void UnmarkRegionAsWritten(VAddr start, VAddr end) { - const u64 page_end = end >> WRITE_PAGE_BIT; - for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) { - auto it = written_pages.find(page_start); - if (it != written_pages.end()) { - if (it->second > 1) { - --it->second; - } else { - written_pages.erase(it); - } - } + return OverlapResult{ + .ids = std::move(overlap_ids), + .begin = begin, + .end = end, + .has_stream_leap = has_stream_leap, + }; +} + +template <class P> +void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, + bool accumulate_stream_score) { + Buffer& new_buffer = slot_buffers[new_buffer_id]; + Buffer& overlap = slot_buffers[overlap_id]; + if (accumulate_stream_score) { + new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1); + } + std::vector<BufferCopy> copies; + const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr(); + overlap.ForEachDownloadRange([&](u64 begin, u64 range_size) { + copies.push_back(BufferCopy{ + .src_offset = begin, + .dst_offset = dst_base_offset + begin, + .size = range_size, + }); + new_buffer.UnmarkRegionAsCpuModified(begin, range_size); + new_buffer.MarkRegionAsGpuModified(begin, range_size); + }); + if (!copies.empty()) { + runtime.CopyBuffer(slot_buffers[new_buffer_id], overlap, copies); + } + ReplaceBufferDownloads(overlap_id, new_buffer_id); + DeleteBuffer(overlap_id); +} + +template <class P> +BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) { + const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size); + const u32 size = static_cast<u32>(overlap.end - overlap.begin); + const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); + for (const BufferId overlap_id : overlap.ids) { + JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); + } + Register(new_buffer_id); + return new_buffer_id; +} + +template <class P> +void BufferCache<P>::Register(BufferId buffer_id) { + ChangeRegister<true>(buffer_id); +} + +template <class P> +void BufferCache<P>::Unregister(BufferId buffer_id) { + ChangeRegister<false>(buffer_id); +} + +template <class P> +template <bool insert> +void BufferCache<P>::ChangeRegister(BufferId buffer_id) { + const Buffer& buffer = slot_buffers[buffer_id]; + const VAddr cpu_addr_begin = buffer.CpuAddr(); + const VAddr cpu_addr_end = cpu_addr_begin + buffer.SizeBytes(); + const u64 page_begin = cpu_addr_begin / PAGE_SIZE; + const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE); + for (u64 page = page_begin; page != page_end; ++page) { + if constexpr (insert) { + page_table[page] = buffer_id; + } else { + page_table[page] = BufferId{}; } } +} - bool IsRegionWritten(VAddr start, VAddr end) const { - const u64 page_end = end >> WRITE_PAGE_BIT; - for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) { - if (written_pages.count(page_start) > 0) { - return true; +template <class P> +void BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { + if (buffer.CpuAddr() == 0) { + return; + } + SynchronizeBufferImpl(buffer, cpu_addr, size); +} + +template <class P> +void BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size) { + boost::container::small_vector<BufferCopy, 4> copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + buffer.ForEachUploadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { + copies.push_back(BufferCopy{ + .src_offset = total_size_bytes, + .dst_offset = range_offset, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }); + if (total_size_bytes == 0) { + return; + } + const std::span<BufferCopy> copies_span(copies.data(), copies.size()); + UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); +} + +template <class P> +void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, + std::span<BufferCopy> copies) { + if constexpr (USE_MEMORY_MAPS) { + MappedUploadMemory(buffer, total_size_bytes, copies); + } else { + ImmediateUploadMemory(buffer, largest_copy, copies); + } +} + +template <class P> +void BufferCache<P>::ImmediateUploadMemory(Buffer& buffer, u64 largest_copy, + std::span<const BufferCopy> copies) { + std::span<u8> immediate_buffer; + for (const BufferCopy& copy : copies) { + std::span<const u8> upload_span; + const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; + if (IsRangeGranular(cpu_addr, copy.size)) { + upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size); + } else { + if (immediate_buffer.empty()) { + immediate_buffer = ImmediateBuffer(largest_copy); } + cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); + upload_span = immediate_buffer.subspan(0, copy.size); } - return false; + buffer.ImmediateUpload(copy.dst_offset, upload_span); } - - void QueueDestruction(std::shared_ptr<Buffer> buffer) { - buffer->SetEpoch(epoch); - pending_destruction.push(std::move(buffer)); +} + +template <class P> +void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, + std::span<BufferCopy> copies) { + auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes); + const std::span<u8> staging_pointer = upload_staging.mapped_span; + for (BufferCopy& copy : copies) { + u8* const src_pointer = staging_pointer.data() + copy.src_offset; + const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; + cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size); + + // Apply the staging offset + copy.src_offset += upload_staging.offset; } - - void MarkForAsyncFlush(MapInterval* map) { - if (!uncommitted_flushes) { - uncommitted_flushes = std::make_shared<std::unordered_set<MapInterval*>>(); + runtime.CopyBuffer(buffer, upload_staging.buffer, copies); +} + +template <class P> +void BufferCache<P>::DeleteBuffer(BufferId buffer_id) { + const auto scalar_replace = [buffer_id](Binding& binding) { + if (binding.buffer_id == buffer_id) { + binding.buffer_id = BufferId{}; + } + }; + const auto replace = [scalar_replace](std::span<Binding> bindings) { + std::ranges::for_each(bindings, scalar_replace); + }; + scalar_replace(index_buffer); + replace(vertex_buffers); + std::ranges::for_each(uniform_buffers, replace); + std::ranges::for_each(storage_buffers, replace); + replace(transform_feedback_buffers); + replace(compute_uniform_buffers); + replace(compute_storage_buffers); + std::erase(cached_write_buffer_ids, buffer_id); + + // Mark the whole buffer as CPU written to stop tracking CPU writes + Buffer& buffer = slot_buffers[buffer_id]; + buffer.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes()); + + Unregister(buffer_id); + delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); + + NotifyBufferDeletion(); +} + +template <class P> +void BufferCache<P>::ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id) { + const auto replace = [old_buffer_id, new_buffer_id](std::vector<BufferId>& buffers) { + std::ranges::replace(buffers, old_buffer_id, new_buffer_id); + if (auto it = std::ranges::find(buffers, new_buffer_id); it != buffers.end()) { + buffers.erase(std::remove(it + 1, buffers.end(), new_buffer_id), buffers.end()); } - uncommitted_flushes->insert(map); + }; + replace(uncommitted_downloads); + std::ranges::for_each(committed_downloads, replace); +} + +template <class P> +void BufferCache<P>::NotifyBufferDeletion() { + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + dirty_uniform_buffers.fill(~u32{0}); } + auto& flags = maxwell3d.dirty.flags; + flags[Dirty::IndexBuffer] = true; + flags[Dirty::VertexBuffers] = true; + for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { + flags[Dirty::VertexBuffer0 + index] = true; + } + has_deleted_buffers = true; +} + +template <class P> +typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr) const { + const GPUVAddr gpu_addr = gpu_memory.Read<u64>(ssbo_addr); + const u32 size = gpu_memory.Read<u32>(ssbo_addr + 8); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + if (!cpu_addr || size == 0) { + return NULL_BINDING; + } + // HACK(Rodrigo): This is the number of bytes bound in host beyond the guest API's range. + // It exists due to some games like Astral Chain operate out of bounds. + // Binding the whole map range would be technically correct, but games have large maps that make + // this approach unaffordable for now. + static constexpr u32 arbitrary_extra_bytes = 0xc000; + const u32 bytes_to_map_end = static_cast<u32>(gpu_memory.BytesToMapEnd(gpu_addr)); + const Binding binding{ + .cpu_addr = *cpu_addr, + .size = std::min(size + arbitrary_extra_bytes, bytes_to_map_end), + .buffer_id = BufferId{}, + }; + return binding; +} + +template <class P> +std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size_t size) { + u8* const base_pointer = cpu_memory.GetPointer(cpu_addr); + if (IsRangeGranular(cpu_addr, size) || + base_pointer + size == cpu_memory.GetPointer(cpu_addr + size)) { + return std::span(base_pointer, size); + } else { + const std::span<u8> span = ImmediateBuffer(size); + cpu_memory.ReadBlockUnsafe(cpu_addr, span.data(), size); + return span; + } +} - VideoCore::RasterizerInterface& rasterizer; - Core::System& system; - - std::unique_ptr<StreamBuffer> stream_buffer; - BufferType stream_buffer_handle; - - u8* buffer_ptr = nullptr; - u64 buffer_offset = 0; - u64 buffer_offset_base = 0; - - MapIntervalAllocator mapped_addresses_allocator; - boost::intrusive::set<MapInterval, boost::intrusive::compare<MapIntervalCompare>> - mapped_addresses; - - std::unordered_map<u64, u32> written_pages; - std::unordered_map<u64, std::shared_ptr<Buffer>> blocks; - - std::queue<std::shared_ptr<Buffer>> pending_destruction; - u64 epoch = 0; - u64 modified_ticks = 0; - - std::vector<u8> staging_buffer; - - std::list<MapInterval*> marked_for_unregister; - - std::shared_ptr<std::unordered_set<MapInterval*>> uncommitted_flushes; - std::list<std::shared_ptr<std::list<MapInterval*>>> committed_flushes; - - std::recursive_mutex mutex; -}; +template <class P> +std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) { + if (wanted_capacity > immediate_buffer_capacity) { + immediate_buffer_capacity = wanted_capacity; + immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity); + } + return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity); +} + +template <class P> +bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { + if constexpr (IS_OPENGL) { + return ((fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; + } else { + // Only OpenGL has fast uniform buffers + return false; + } +} } // namespace VideoCommon diff --git a/src/video_core/buffer_cache/map_interval.cpp b/src/video_core/buffer_cache/map_interval.cpp deleted file mode 100644 index 62587e18a..000000000 --- a/src/video_core/buffer_cache/map_interval.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <array> -#include <cstddef> -#include <memory> - -#include "video_core/buffer_cache/map_interval.h" - -namespace VideoCommon { - -MapIntervalAllocator::MapIntervalAllocator() { - FillFreeList(first_chunk); -} - -MapIntervalAllocator::~MapIntervalAllocator() = default; - -void MapIntervalAllocator::AllocateNewChunk() { - *new_chunk = std::make_unique<Chunk>(); - FillFreeList(**new_chunk); - new_chunk = &(*new_chunk)->next; -} - -void MapIntervalAllocator::FillFreeList(Chunk& chunk) { - const std::size_t old_size = free_list.size(); - free_list.resize(old_size + chunk.data.size()); - std::transform(chunk.data.rbegin(), chunk.data.rend(), free_list.begin() + old_size, - [](MapInterval& interval) { return &interval; }); -} - -} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/map_interval.h b/src/video_core/buffer_cache/map_interval.h deleted file mode 100644 index fe0bcd1d8..000000000 --- a/src/video_core/buffer_cache/map_interval.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <cstddef> -#include <memory> -#include <vector> - -#include <boost/intrusive/set_hook.hpp> - -#include "common/common_types.h" -#include "video_core/gpu.h" - -namespace VideoCommon { - -struct MapInterval : public boost::intrusive::set_base_hook<boost::intrusive::optimize_size<true>> { - MapInterval() = default; - - /*implicit*/ MapInterval(VAddr start_) noexcept : start{start_} {} - - explicit MapInterval(VAddr start_, VAddr end_, GPUVAddr gpu_addr_) noexcept - : start{start_}, end{end_}, gpu_addr{gpu_addr_} {} - - bool IsInside(VAddr other_start, VAddr other_end) const noexcept { - return start <= other_start && other_end <= end; - } - - bool Overlaps(VAddr other_start, VAddr other_end) const noexcept { - return start < other_end && other_start < end; - } - - void MarkAsModified(bool is_modified_, u64 ticks_) noexcept { - is_modified = is_modified_; - ticks = ticks_; - } - - boost::intrusive::set_member_hook<> member_hook_; - VAddr start = 0; - VAddr end = 0; - GPUVAddr gpu_addr = 0; - u64 ticks = 0; - bool is_written = false; - bool is_modified = false; - bool is_registered = false; - bool is_memory_marked = false; - bool is_sync_pending = false; -}; - -struct MapIntervalCompare { - constexpr bool operator()(const MapInterval& lhs, const MapInterval& rhs) const noexcept { - return lhs.start < rhs.start; - } -}; - -class MapIntervalAllocator { -public: - MapIntervalAllocator(); - ~MapIntervalAllocator(); - - MapInterval* Allocate() { - if (free_list.empty()) { - AllocateNewChunk(); - } - MapInterval* const interval = free_list.back(); - free_list.pop_back(); - return interval; - } - - void Release(MapInterval* interval) { - free_list.push_back(interval); - } - -private: - struct Chunk { - std::unique_ptr<Chunk> next; - std::array<MapInterval, 0x8000> data; - }; - - void AllocateNewChunk(); - - void FillFreeList(Chunk& chunk); - - std::vector<MapInterval*> free_list; - std::unique_ptr<Chunk>* new_chunk = &first_chunk.next; - - Chunk first_chunk; -}; - -} // namespace VideoCommon diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp new file mode 100644 index 000000000..33b3c060b --- /dev/null +++ b/src/video_core/cdma_pusher.cpp @@ -0,0 +1,170 @@ +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#include <bit> +#include "command_classes/host1x.h" +#include "command_classes/nvdec.h" +#include "command_classes/vic.h" +#include "video_core/cdma_pusher.h" +#include "video_core/command_classes/nvdec_common.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +CDmaPusher::CDmaPusher(GPU& gpu_) + : gpu{gpu_}, nvdec_processor(std::make_shared<Nvdec>(gpu)), + vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)), + host1x_processor(std::make_unique<Host1x>(gpu)), + sync_manager(std::make_unique<SyncptIncrManager>(gpu)) {} + +CDmaPusher::~CDmaPusher() = default; + +void CDmaPusher::Push(ChCommandHeaderList&& entries) { + cdma_queue.push(std::move(entries)); +} + +void CDmaPusher::DispatchCalls() { + while (!cdma_queue.empty()) { + Step(); + } +} + +void CDmaPusher::Step() { + const auto entries{cdma_queue.front()}; + cdma_queue.pop(); + + std::vector<u32> values(entries.size()); + std::memcpy(values.data(), entries.data(), entries.size() * sizeof(u32)); + + for (const u32 value : values) { + if (mask != 0) { + const auto lbs = static_cast<u32>(std::countr_zero(mask)); + mask &= ~(1U << lbs); + ExecuteCommand(static_cast<u32>(offset + lbs), value); + continue; + } else if (count != 0) { + --count; + ExecuteCommand(static_cast<u32>(offset), value); + if (incrementing) { + ++offset; + } + continue; + } + const auto mode = static_cast<ChSubmissionMode>((value >> 28) & 0xf); + switch (mode) { + case ChSubmissionMode::SetClass: { + mask = value & 0x3f; + offset = (value >> 16) & 0xfff; + current_class = static_cast<ChClassId>((value >> 6) & 0x3ff); + break; + } + case ChSubmissionMode::Incrementing: + case ChSubmissionMode::NonIncrementing: + count = value & 0xffff; + offset = (value >> 16) & 0xfff; + incrementing = mode == ChSubmissionMode::Incrementing; + break; + case ChSubmissionMode::Mask: + mask = value & 0xffff; + offset = (value >> 16) & 0xfff; + break; + case ChSubmissionMode::Immediate: { + const u32 data = value & 0xfff; + offset = (value >> 16) & 0xfff; + ExecuteCommand(static_cast<u32>(offset), data); + break; + } + default: + UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", static_cast<u32>(mode)); + break; + } + } +} + +void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) { + switch (current_class) { + case ChClassId::NvDec: + ThiStateWrite(nvdec_thi_state, state_offset, {data}); + switch (static_cast<ThiMethod>(state_offset)) { + case ThiMethod::IncSyncpt: { + LOG_DEBUG(Service_NVDRV, "NVDEC Class IncSyncpt Method"); + const auto syncpoint_id = static_cast<u32>(data & 0xFF); + const auto cond = static_cast<u32>((data >> 8) & 0xFF); + if (cond == 0) { + sync_manager->Increment(syncpoint_id); + } else { + sync_manager->SignalDone( + sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id)); + } + break; + } + case ThiMethod::SetMethod1: + LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}", + static_cast<u32>(nvdec_thi_state.method_0)); + nvdec_processor->ProcessMethod(static_cast<Nvdec::Method>(nvdec_thi_state.method_0), + {data}); + break; + default: + break; + } + break; + case ChClassId::GraphicsVic: + ThiStateWrite(vic_thi_state, static_cast<u32>(state_offset), {data}); + switch (static_cast<ThiMethod>(state_offset)) { + case ThiMethod::IncSyncpt: { + LOG_DEBUG(Service_NVDRV, "VIC Class IncSyncpt Method"); + const auto syncpoint_id = static_cast<u32>(data & 0xFF); + const auto cond = static_cast<u32>((data >> 8) & 0xFF); + if (cond == 0) { + sync_manager->Increment(syncpoint_id); + } else { + sync_manager->SignalDone( + sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id)); + } + break; + } + case ThiMethod::SetMethod1: + LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})", + static_cast<u32>(vic_thi_state.method_0), data); + vic_processor->ProcessMethod(static_cast<Vic::Method>(vic_thi_state.method_0), {data}); + break; + default: + break; + } + break; + case ChClassId::Host1x: + // This device is mainly for syncpoint synchronization + LOG_DEBUG(Service_NVDRV, "Host1X Class Method"); + host1x_processor->ProcessMethod(static_cast<Host1x::Method>(state_offset), {data}); + break; + default: + UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class)); + break; + } +} + +void CDmaPusher::ThiStateWrite(ThiRegisters& state, u32 state_offset, + const std::vector<u32>& arguments) { + u8* const state_offset_ptr = reinterpret_cast<u8*>(&state) + sizeof(u32) * state_offset; + std::memcpy(state_offset_ptr, arguments.data(), sizeof(u32) * arguments.size()); +} + +} // namespace Tegra diff --git a/src/video_core/cdma_pusher.h b/src/video_core/cdma_pusher.h new file mode 100644 index 000000000..e5f212c1a --- /dev/null +++ b/src/video_core/cdma_pusher.h @@ -0,0 +1,136 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <unordered_map> +#include <vector> +#include <queue> + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "video_core/command_classes/sync_manager.h" + +namespace Tegra { + +class GPU; +class Nvdec; +class Vic; +class Host1x; + +enum class ChSubmissionMode : u32 { + SetClass = 0, + Incrementing = 1, + NonIncrementing = 2, + Mask = 3, + Immediate = 4, + Restart = 5, + Gather = 6, +}; + +enum class ChClassId : u32 { + NoClass = 0x0, + Host1x = 0x1, + VideoEncodeMpeg = 0x20, + VideoEncodeNvEnc = 0x21, + VideoStreamingVi = 0x30, + VideoStreamingIsp = 0x32, + VideoStreamingIspB = 0x34, + VideoStreamingViI2c = 0x36, + GraphicsVic = 0x5d, + Graphics3D = 0x60, + GraphicsGpu = 0x61, + Tsec = 0xe0, + TsecB = 0xe1, + NvJpg = 0xc0, + NvDec = 0xf0 +}; + +enum class ChMethod : u32 { + Empty = 0, + SetMethod = 0x10, + SetData = 0x11, +}; + +union ChCommandHeader { + u32 raw; + BitField<0, 16, u32> value; + BitField<16, 12, ChMethod> method_offset; + BitField<28, 4, ChSubmissionMode> submission_mode; +}; +static_assert(sizeof(ChCommandHeader) == sizeof(u32), "ChCommand header is an invalid size"); + +struct ChCommand { + ChClassId class_id{}; + int method_offset{}; + std::vector<u32> arguments; +}; + +using ChCommandHeaderList = std::vector<ChCommandHeader>; +using ChCommandList = std::vector<ChCommand>; + +struct ThiRegisters { + u32_le increment_syncpt{}; + INSERT_PADDING_WORDS(1); + u32_le increment_syncpt_error{}; + u32_le ctx_switch_incremement_syncpt{}; + INSERT_PADDING_WORDS(4); + u32_le ctx_switch{}; + INSERT_PADDING_WORDS(1); + u32_le ctx_syncpt_eof{}; + INSERT_PADDING_WORDS(5); + u32_le method_0{}; + u32_le method_1{}; + INSERT_PADDING_WORDS(12); + u32_le int_status{}; + u32_le int_mask{}; +}; + +enum class ThiMethod : u32 { + IncSyncpt = offsetof(ThiRegisters, increment_syncpt) / sizeof(u32), + SetMethod0 = offsetof(ThiRegisters, method_0) / sizeof(u32), + SetMethod1 = offsetof(ThiRegisters, method_1) / sizeof(u32), +}; + +class CDmaPusher { +public: + explicit CDmaPusher(GPU& gpu_); + ~CDmaPusher(); + + /// Push NVDEC command buffer entries into queue + void Push(ChCommandHeaderList&& entries); + + /// Process queued command buffer entries + void DispatchCalls(); + + /// Process one queue element + void Step(); + + /// Invoke command class devices to execute the command based on the current state + void ExecuteCommand(u32 state_offset, u32 data); + +private: + /// Write arguments value to the ThiRegisters member at the specified offset + void ThiStateWrite(ThiRegisters& state, u32 state_offset, const std::vector<u32>& arguments); + + GPU& gpu; + std::shared_ptr<Tegra::Nvdec> nvdec_processor; + std::unique_ptr<Tegra::Vic> vic_processor; + std::unique_ptr<Tegra::Host1x> host1x_processor; + std::unique_ptr<SyncptIncrManager> sync_manager; + ChClassId current_class{}; + ThiRegisters vic_thi_state{}; + ThiRegisters nvdec_thi_state{}; + + s32 count{}; + s32 offset{}; + u32 mask{}; + bool incrementing{}; + + // Queue of command lists to be processed + std::queue<ChCommandHeaderList> cdma_queue; +}; + +} // namespace Tegra diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp new file mode 100644 index 000000000..39bc923a5 --- /dev/null +++ b/src/video_core/command_classes/codecs/codec.cpp @@ -0,0 +1,129 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <fstream> +#include <vector> +#include "common/assert.h" +#include "video_core/command_classes/codecs/codec.h" +#include "video_core/command_classes/codecs/h264.h" +#include "video_core/command_classes/codecs/vp9.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +extern "C" { +#include <libavutil/opt.h> +} + +namespace Tegra { + +void AVFrameDeleter(AVFrame* ptr) { + av_frame_unref(ptr); + av_free(ptr); +} + +Codec::Codec(GPU& gpu_) + : gpu(gpu_), h264_decoder(std::make_unique<Decoder::H264>(gpu)), + vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {} + +Codec::~Codec() { + if (!initialized) { + return; + } + // Free libav memory + AVFrame* av_frame{nullptr}; + avcodec_send_packet(av_codec_ctx, nullptr); + av_frame = av_frame_alloc(); + avcodec_receive_frame(av_codec_ctx, av_frame); + avcodec_flush_buffers(av_codec_ctx); + + av_frame_unref(av_frame); + av_free(av_frame); + avcodec_close(av_codec_ctx); +} + +void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { + LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", codec); + current_codec = codec; +} + +void Codec::StateWrite(u32 offset, u64 arguments) { + u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u64); + std::memcpy(state_offset, &arguments, sizeof(u64)); +} + +void Codec::Decode() { + bool is_first_frame = false; + + if (!initialized) { + if (current_codec == NvdecCommon::VideoCodec::H264) { + av_codec = avcodec_find_decoder(AV_CODEC_ID_H264); + } else if (current_codec == NvdecCommon::VideoCodec::Vp9) { + av_codec = avcodec_find_decoder(AV_CODEC_ID_VP9); + } else { + LOG_ERROR(Service_NVDRV, "Unknown video codec {}", current_codec); + return; + } + + av_codec_ctx = avcodec_alloc_context3(av_codec); + av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); + + // TODO(ameerj): libavcodec gpu hw acceleration + + const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); + if (av_error < 0) { + LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); + avcodec_close(av_codec_ctx); + return; + } + initialized = true; + is_first_frame = true; + } + bool vp9_hidden_frame = false; + + AVPacket packet{}; + av_init_packet(&packet); + std::vector<u8> frame_data; + + if (current_codec == NvdecCommon::VideoCodec::H264) { + frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame); + } else if (current_codec == NvdecCommon::VideoCodec::Vp9) { + frame_data = vp9_decoder->ComposeFrameHeader(state); + vp9_hidden_frame = vp9_decoder->WasFrameHidden(); + } + + packet.data = frame_data.data(); + packet.size = static_cast<int>(frame_data.size()); + + avcodec_send_packet(av_codec_ctx, &packet); + + if (!vp9_hidden_frame) { + // Only receive/store visible frames + AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; + avcodec_receive_frame(av_codec_ctx, frame.get()); + av_frames.push(std::move(frame)); + // Limit queue to 10 frames. Workaround for ZLA decode and queue spam + if (av_frames.size() > 10) { + av_frames.pop(); + } + } +} + +AVFramePtr Codec::GetCurrentFrame() { + // Sometimes VIC will request more frames than have been decoded. + // in this case, return a nullptr and don't overwrite previous frame data + if (av_frames.empty()) { + return AVFramePtr{nullptr, AVFrameDeleter}; + } + + AVFramePtr frame = std::move(av_frames.front()); + av_frames.pop(); + return frame; +} + +NvdecCommon::VideoCodec Codec::GetCurrentCodec() const { + return current_codec; +} + +} // namespace Tegra diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h new file mode 100644 index 000000000..8a2a6c360 --- /dev/null +++ b/src/video_core/command_classes/codecs/codec.h @@ -0,0 +1,70 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <queue> +#include "common/common_types.h" +#include "video_core/command_classes/nvdec_common.h" + +extern "C" { +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <libavcodec/avcodec.h> +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif +} + +namespace Tegra { +class GPU; +struct VicRegisters; + +void AVFrameDeleter(AVFrame* ptr); +using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>; + +namespace Decoder { +class H264; +class VP9; +} // namespace Decoder + +class Codec { +public: + explicit Codec(GPU& gpu); + ~Codec(); + + /// Sets NVDEC video stream codec + void SetTargetCodec(NvdecCommon::VideoCodec codec); + + /// Populate NvdecRegisters state with argument value at the provided offset + void StateWrite(u32 offset, u64 arguments); + + /// Call decoders to construct headers, decode AVFrame with ffmpeg + void Decode(); + + /// Returns next decoded frame + [[nodiscard]] AVFramePtr GetCurrentFrame(); + + /// Returns the value of current_codec + [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const; + +private: + bool initialized{}; + NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; + + AVCodec* av_codec{nullptr}; + AVCodecContext* av_codec_ctx{nullptr}; + + GPU& gpu; + std::unique_ptr<Decoder::H264> h264_decoder; + std::unique_ptr<Decoder::VP9> vp9_decoder; + + NvdecCommon::NvdecRegisters state{}; + std::queue<AVFramePtr> av_frames{}; +}; + +} // namespace Tegra diff --git a/src/video_core/command_classes/codecs/h264.cpp b/src/video_core/command_classes/codecs/h264.cpp new file mode 100644 index 000000000..fea6aed98 --- /dev/null +++ b/src/video_core/command_classes/codecs/h264.cpp @@ -0,0 +1,293 @@ +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#include <array> +#include <bit> +#include "video_core/command_classes/codecs/h264.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +namespace Tegra::Decoder { +namespace { +// ZigZag LUTs from libavcodec. +constexpr std::array<u8, 64> zig_zag_direct{ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, + 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, + 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, +}; + +constexpr std::array<u8, 16> zig_zag_scan{ + 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4, + 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4, +}; +} // Anonymous namespace + +H264::H264(GPU& gpu_) : gpu(gpu_) {} + +H264::~H264() = default; + +const std::vector<u8>& H264::ComposeFrameHeader(const NvdecCommon::NvdecRegisters& state, + bool is_first_frame) { + H264DecoderContext context{}; + gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext)); + + const s32 frame_number = static_cast<s32>((context.h264_parameter_set.flags >> 46) & 0x1ffff); + if (!is_first_frame && frame_number != 0) { + frame.resize(context.frame_data_size); + + gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size()); + } else { + /// Encode header + H264BitWriter writer{}; + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(7, 5); + writer.WriteU(100, 8); + writer.WriteU(0, 8); + writer.WriteU(31, 8); + writer.WriteUe(0); + const auto chroma_format_idc = + static_cast<u32>((context.h264_parameter_set.flags >> 12) & 3); + writer.WriteUe(chroma_format_idc); + if (chroma_format_idc == 3) { + writer.WriteBit(false); + } + + writer.WriteUe(0); + writer.WriteUe(0); + writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag + writer.WriteBit(false); // Scaling matrix present flag + + const auto order_cnt_type = static_cast<u32>((context.h264_parameter_set.flags >> 14) & 3); + writer.WriteUe(static_cast<u32>((context.h264_parameter_set.flags >> 8) & 0xf)); + writer.WriteUe(order_cnt_type); + if (order_cnt_type == 0) { + writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt); + } else if (order_cnt_type == 1) { + writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0); + + writer.WriteSe(0); + writer.WriteSe(0); + writer.WriteUe(0); + } + + const s32 pic_height = context.h264_parameter_set.pic_height_in_map_units / + (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2); + + writer.WriteUe(16); + writer.WriteBit(false); + writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1); + writer.WriteUe(pic_height - 1); + writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0); + + if (!context.h264_parameter_set.frame_mbs_only_flag) { + writer.WriteBit(((context.h264_parameter_set.flags >> 0) & 1) != 0); + } + + writer.WriteBit(((context.h264_parameter_set.flags >> 1) & 1) != 0); + writer.WriteBit(false); // Frame cropping flag + writer.WriteBit(false); // VUI parameter present flag + + writer.End(); + + // H264 PPS + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(8, 5); + + writer.WriteUe(0); + writer.WriteUe(0); + + writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0); + writer.WriteBit(false); + writer.WriteUe(0); + writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active); + writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active); + writer.WriteBit(((context.h264_parameter_set.flags >> 2) & 1) != 0); + writer.WriteU(static_cast<s32>((context.h264_parameter_set.flags >> 32) & 0x3), 2); + s32 pic_init_qp = static_cast<s32>((context.h264_parameter_set.flags >> 16) & 0x3f); + pic_init_qp = (pic_init_qp << 26) >> 26; + writer.WriteSe(pic_init_qp); + writer.WriteSe(0); + s32 chroma_qp_index_offset = + static_cast<s32>((context.h264_parameter_set.flags >> 22) & 0x1f); + chroma_qp_index_offset = (chroma_qp_index_offset << 27) >> 27; + + writer.WriteSe(chroma_qp_index_offset); + writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_flag != 0); + writer.WriteBit(((context.h264_parameter_set.flags >> 3) & 1) != 0); + writer.WriteBit(context.h264_parameter_set.redundant_pic_count_flag != 0); + writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0); + + writer.WriteBit(true); + + for (s32 index = 0; index < 6; index++) { + writer.WriteBit(true); + const auto matrix_x4 = + std::vector<u8>(context.scaling_matrix_4.begin(), context.scaling_matrix_4.end()); + writer.WriteScalingList(matrix_x4, index * 16, 16); + } + + if (context.h264_parameter_set.transform_8x8_mode_flag) { + for (s32 index = 0; index < 2; index++) { + writer.WriteBit(true); + const auto matrix_x8 = std::vector<u8>(context.scaling_matrix_8.begin(), + context.scaling_matrix_8.end()); + + writer.WriteScalingList(matrix_x8, index * 64, 64); + } + } + + s32 chroma_qp_index_offset2 = + static_cast<s32>((context.h264_parameter_set.flags >> 27) & 0x1f); + chroma_qp_index_offset2 = (chroma_qp_index_offset2 << 27) >> 27; + + writer.WriteSe(chroma_qp_index_offset2); + + writer.End(); + + const auto& encoded_header = writer.GetByteArray(); + frame.resize(encoded_header.size() + context.frame_data_size); + std::memcpy(frame.data(), encoded_header.data(), encoded_header.size()); + + gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, + frame.data() + encoded_header.size(), + context.frame_data_size); + } + + return frame; +} + +H264BitWriter::H264BitWriter() = default; + +H264BitWriter::~H264BitWriter() = default; + +void H264BitWriter::WriteU(s32 value, s32 value_sz) { + WriteBits(value, value_sz); +} + +void H264BitWriter::WriteSe(s32 value) { + WriteExpGolombCodedInt(value); +} + +void H264BitWriter::WriteUe(u32 value) { + WriteExpGolombCodedUInt(value); +} + +void H264BitWriter::End() { + WriteBit(true); + Flush(); +} + +void H264BitWriter::WriteBit(bool state) { + WriteBits(state ? 1 : 0, 1); +} + +void H264BitWriter::WriteScalingList(const std::vector<u8>& list, s32 start, s32 count) { + std::vector<u8> scan(count); + if (count == 16) { + std::memcpy(scan.data(), zig_zag_scan.data(), scan.size()); + } else { + std::memcpy(scan.data(), zig_zag_direct.data(), scan.size()); + } + u8 last_scale = 8; + + for (s32 index = 0; index < count; index++) { + const u8 value = list[start + scan[index]]; + const s32 delta_scale = static_cast<s32>(value - last_scale); + + WriteSe(delta_scale); + + last_scale = value; + } +} + +std::vector<u8>& H264BitWriter::GetByteArray() { + return byte_array; +} + +const std::vector<u8>& H264BitWriter::GetByteArray() const { + return byte_array; +} + +void H264BitWriter::WriteBits(s32 value, s32 bit_count) { + s32 value_pos = 0; + + s32 remaining = bit_count; + + while (remaining > 0) { + s32 copy_size = remaining; + + const s32 free_bits = GetFreeBufferBits(); + + if (copy_size > free_bits) { + copy_size = free_bits; + } + + const s32 mask = (1 << copy_size) - 1; + + const s32 src_shift = (bit_count - value_pos) - copy_size; + const s32 dst_shift = (buffer_size - buffer_pos) - copy_size; + + buffer |= ((value >> src_shift) & mask) << dst_shift; + + value_pos += copy_size; + buffer_pos += copy_size; + remaining -= copy_size; + } +} + +void H264BitWriter::WriteExpGolombCodedInt(s32 value) { + const s32 sign = value <= 0 ? 0 : 1; + if (value < 0) { + value = -value; + } + value = (value << 1) - sign; + WriteExpGolombCodedUInt(value); +} + +void H264BitWriter::WriteExpGolombCodedUInt(u32 value) { + const s32 size = 32 - std::countl_zero(value + 1); + WriteBits(1, size); + + value -= (1U << (size - 1)) - 1; + WriteBits(static_cast<s32>(value), size - 1); +} + +s32 H264BitWriter::GetFreeBufferBits() { + if (buffer_pos == buffer_size) { + Flush(); + } + + return buffer_size - buffer_pos; +} + +void H264BitWriter::Flush() { + if (buffer_pos == 0) { + return; + } + byte_array.push_back(static_cast<u8>(buffer)); + + buffer = 0; + buffer_pos = 0; +} +} // namespace Tegra::Decoder diff --git a/src/video_core/command_classes/codecs/h264.h b/src/video_core/command_classes/codecs/h264.h new file mode 100644 index 000000000..0f3a1d9f3 --- /dev/null +++ b/src/video_core/command_classes/codecs/h264.h @@ -0,0 +1,118 @@ +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#pragma once + +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/command_classes/nvdec_common.h" + +namespace Tegra { +class GPU; +namespace Decoder { + +class H264BitWriter { +public: + H264BitWriter(); + ~H264BitWriter(); + + /// The following Write methods are based on clause 9.1 in the H.264 specification. + /// WriteSe and WriteUe write in the Exp-Golomb-coded syntax + void WriteU(s32 value, s32 value_sz); + void WriteSe(s32 value); + void WriteUe(u32 value); + + /// Finalize the bitstream + void End(); + + /// append a bit to the stream, equivalent value to the state parameter + void WriteBit(bool state); + + /// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification + /// Writes the scaling matrices of the sream + void WriteScalingList(const std::vector<u8>& list, s32 start, s32 count); + + /// Return the bitstream as a vector. + [[nodiscard]] std::vector<u8>& GetByteArray(); + [[nodiscard]] const std::vector<u8>& GetByteArray() const; + +private: + void WriteBits(s32 value, s32 bit_count); + void WriteExpGolombCodedInt(s32 value); + void WriteExpGolombCodedUInt(u32 value); + [[nodiscard]] s32 GetFreeBufferBits(); + void Flush(); + + s32 buffer_size{8}; + + s32 buffer{}; + s32 buffer_pos{}; + std::vector<u8> byte_array; +}; + +class H264 { +public: + explicit H264(GPU& gpu); + ~H264(); + + /// Compose the H264 header of the frame for FFmpeg decoding + [[nodiscard]] const std::vector<u8>& ComposeFrameHeader( + const NvdecCommon::NvdecRegisters& state, bool is_first_frame = false); + +private: + struct H264ParameterSet { + u32 log2_max_pic_order_cnt{}; + u32 delta_pic_order_always_zero_flag{}; + u32 frame_mbs_only_flag{}; + u32 pic_width_in_mbs{}; + u32 pic_height_in_map_units{}; + INSERT_PADDING_WORDS(1); + u32 entropy_coding_mode_flag{}; + u32 bottom_field_pic_order_flag{}; + u32 num_refidx_l0_default_active{}; + u32 num_refidx_l1_default_active{}; + u32 deblocking_filter_control_flag{}; + u32 redundant_pic_count_flag{}; + u32 transform_8x8_mode_flag{}; + INSERT_PADDING_WORDS(9); + u64 flags{}; + u32 frame_number{}; + u32 frame_number2{}; + }; + static_assert(sizeof(H264ParameterSet) == 0x68, "H264ParameterSet is an invalid size"); + + struct H264DecoderContext { + INSERT_PADDING_BYTES(0x48); + u32 frame_data_size{}; + INSERT_PADDING_BYTES(0xc); + H264ParameterSet h264_parameter_set{}; + INSERT_PADDING_BYTES(0x100); + std::array<u8, 0x60> scaling_matrix_4; + std::array<u8, 0x80> scaling_matrix_8; + }; + static_assert(sizeof(H264DecoderContext) == 0x2a0, "H264DecoderContext is an invalid size"); + + std::vector<u8> frame; + GPU& gpu; +}; + +} // namespace Decoder +} // namespace Tegra diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp new file mode 100644 index 000000000..59e586695 --- /dev/null +++ b/src/video_core/command_classes/codecs/vp9.cpp @@ -0,0 +1,989 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> // for std::memcpy +#include <numeric> +#include "video_core/command_classes/codecs/vp9.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" + +namespace Tegra::Decoder { +namespace { +// Default compressed header probabilities once frame context resets +constexpr Vp9EntropyProbs default_probs{ + .y_mode_prob{ + 65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78, + 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29, + }, + .partition_prob{ + 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0, + 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0, + 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0, + 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0, + }, + .coef_probs{ + 195, 29, 183, 84, 49, 136, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 31, 107, 169, 35, 99, 159, 17, 82, 140, 8, 66, 114, 2, 44, 76, 1, 19, 32, + 40, 132, 201, 29, 114, 187, 13, 91, 157, 7, 75, 127, 3, 58, 95, 1, 28, 47, + 69, 142, 221, 42, 122, 201, 15, 91, 159, 6, 67, 121, 1, 42, 77, 1, 17, 31, + 102, 148, 228, 67, 117, 204, 17, 82, 154, 6, 59, 114, 2, 39, 75, 1, 15, 29, + 156, 57, 233, 119, 57, 212, 58, 48, 163, 29, 40, 124, 12, 30, 81, 3, 12, 31, + 191, 107, 226, 124, 117, 204, 25, 99, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 148, 210, 37, 126, 194, 8, 93, 157, 2, 68, 118, 1, 39, 69, 1, 17, 33, + 41, 151, 213, 27, 123, 193, 3, 82, 144, 1, 58, 105, 1, 32, 60, 1, 13, 26, + 59, 159, 220, 23, 126, 198, 4, 88, 151, 1, 66, 114, 1, 38, 71, 1, 18, 34, + 114, 136, 232, 51, 114, 207, 11, 83, 155, 3, 56, 105, 1, 33, 65, 1, 17, 34, + 149, 65, 234, 121, 57, 215, 61, 49, 166, 28, 36, 114, 12, 25, 76, 3, 16, 42, + 214, 49, 220, 132, 63, 188, 42, 65, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, 137, 221, 104, 131, 216, 49, 111, 192, 21, 87, 155, 2, 49, 87, 1, 16, 28, + 89, 163, 230, 90, 137, 220, 29, 100, 183, 10, 70, 135, 2, 42, 81, 1, 17, 33, + 108, 167, 237, 55, 133, 222, 15, 97, 179, 4, 72, 135, 1, 45, 85, 1, 19, 38, + 124, 146, 240, 66, 124, 224, 17, 88, 175, 4, 58, 122, 1, 36, 75, 1, 18, 37, + 141, 79, 241, 126, 70, 227, 66, 58, 182, 30, 44, 136, 12, 34, 96, 2, 20, 47, + 229, 99, 249, 143, 111, 235, 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 82, 158, 236, 94, 146, 224, 25, 117, 191, 9, 87, 149, 3, 56, 99, 1, 33, 57, + 83, 167, 237, 68, 145, 222, 10, 103, 177, 2, 72, 131, 1, 41, 79, 1, 20, 39, + 99, 167, 239, 47, 141, 224, 10, 104, 178, 2, 73, 133, 1, 44, 85, 1, 22, 47, + 127, 145, 243, 71, 129, 228, 17, 93, 177, 3, 61, 124, 1, 41, 84, 1, 21, 52, + 157, 78, 244, 140, 72, 231, 69, 58, 184, 31, 44, 137, 14, 38, 105, 8, 23, 61, + 125, 34, 187, 52, 41, 133, 6, 31, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 37, 109, 153, 51, 102, 147, 23, 87, 128, 8, 67, 101, 1, 41, 63, 1, 19, 29, + 31, 154, 185, 17, 127, 175, 6, 96, 145, 2, 73, 114, 1, 51, 82, 1, 28, 45, + 23, 163, 200, 10, 131, 185, 2, 93, 148, 1, 67, 111, 1, 41, 69, 1, 14, 24, + 29, 176, 217, 12, 145, 201, 3, 101, 156, 1, 69, 111, 1, 39, 63, 1, 14, 23, + 57, 192, 233, 25, 154, 215, 6, 109, 167, 3, 78, 118, 1, 48, 69, 1, 21, 29, + 202, 105, 245, 108, 106, 216, 18, 90, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 172, 219, 64, 149, 206, 14, 117, 177, 5, 90, 141, 2, 61, 95, 1, 37, 57, + 33, 179, 220, 11, 140, 198, 1, 89, 148, 1, 60, 104, 1, 33, 57, 1, 12, 21, + 30, 181, 221, 8, 141, 198, 1, 87, 145, 1, 58, 100, 1, 31, 55, 1, 12, 20, + 32, 186, 224, 7, 142, 198, 1, 86, 143, 1, 58, 100, 1, 31, 55, 1, 12, 22, + 57, 192, 227, 20, 143, 204, 3, 96, 154, 1, 68, 112, 1, 42, 69, 1, 19, 32, + 212, 35, 215, 113, 47, 169, 29, 48, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 74, 129, 203, 106, 120, 203, 49, 107, 178, 19, 84, 144, 4, 50, 84, 1, 15, 25, + 71, 172, 217, 44, 141, 209, 15, 102, 173, 6, 76, 133, 2, 51, 89, 1, 24, 42, + 64, 185, 231, 31, 148, 216, 8, 103, 175, 3, 74, 131, 1, 46, 81, 1, 18, 30, + 65, 196, 235, 25, 157, 221, 5, 105, 174, 1, 67, 120, 1, 38, 69, 1, 15, 30, + 65, 204, 238, 30, 156, 224, 7, 107, 177, 2, 70, 124, 1, 42, 73, 1, 18, 34, + 225, 86, 251, 144, 104, 235, 42, 99, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, 175, 239, 112, 165, 229, 29, 136, 200, 12, 103, 162, 6, 77, 123, 2, 53, 84, + 75, 183, 239, 30, 155, 221, 3, 106, 171, 1, 74, 128, 1, 44, 76, 1, 17, 28, + 73, 185, 240, 27, 159, 222, 2, 107, 172, 1, 75, 127, 1, 42, 73, 1, 17, 29, + 62, 190, 238, 21, 159, 222, 2, 107, 172, 1, 72, 122, 1, 40, 71, 1, 18, 32, + 61, 199, 240, 27, 161, 226, 4, 113, 180, 1, 76, 129, 1, 46, 80, 1, 23, 41, + 7, 27, 153, 5, 30, 95, 1, 16, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 50, 75, 127, 57, 75, 124, 27, 67, 108, 10, 54, 86, 1, 33, 52, 1, 12, 18, + 43, 125, 151, 26, 108, 148, 7, 83, 122, 2, 59, 89, 1, 38, 60, 1, 17, 27, + 23, 144, 163, 13, 112, 154, 2, 75, 117, 1, 50, 81, 1, 31, 51, 1, 14, 23, + 18, 162, 185, 6, 123, 171, 1, 78, 125, 1, 51, 86, 1, 31, 54, 1, 14, 23, + 15, 199, 227, 3, 150, 204, 1, 91, 146, 1, 55, 95, 1, 30, 53, 1, 11, 20, + 19, 55, 240, 19, 59, 196, 3, 52, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 166, 207, 104, 153, 199, 31, 123, 181, 14, 101, 152, 5, 72, 106, 1, 36, 52, + 35, 176, 211, 12, 131, 190, 2, 88, 144, 1, 60, 101, 1, 36, 60, 1, 16, 28, + 28, 183, 213, 8, 134, 191, 1, 86, 142, 1, 56, 96, 1, 30, 53, 1, 12, 20, + 20, 190, 215, 4, 135, 192, 1, 84, 139, 1, 53, 91, 1, 28, 49, 1, 11, 20, + 13, 196, 216, 2, 137, 192, 1, 86, 143, 1, 57, 99, 1, 32, 56, 1, 13, 24, + 211, 29, 217, 96, 47, 156, 22, 43, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 78, 120, 193, 111, 116, 186, 46, 102, 164, 15, 80, 128, 2, 49, 76, 1, 18, 28, + 71, 161, 203, 42, 132, 192, 10, 98, 150, 3, 69, 109, 1, 44, 70, 1, 18, 29, + 57, 186, 211, 30, 140, 196, 4, 93, 146, 1, 62, 102, 1, 38, 65, 1, 16, 27, + 47, 199, 217, 14, 145, 196, 1, 88, 142, 1, 57, 98, 1, 36, 62, 1, 15, 26, + 26, 219, 229, 5, 155, 207, 1, 94, 151, 1, 60, 104, 1, 36, 62, 1, 16, 28, + 233, 29, 248, 146, 47, 220, 43, 52, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 100, 163, 232, 179, 161, 222, 63, 142, 204, 37, 113, 174, 26, 89, 137, 18, 68, 97, + 85, 181, 230, 32, 146, 209, 7, 100, 164, 3, 71, 121, 1, 45, 77, 1, 18, 30, + 65, 187, 230, 20, 148, 207, 2, 97, 159, 1, 68, 116, 1, 40, 70, 1, 14, 29, + 40, 194, 227, 8, 147, 204, 1, 94, 155, 1, 65, 112, 1, 39, 66, 1, 14, 26, + 16, 208, 228, 3, 151, 207, 1, 98, 160, 1, 67, 117, 1, 41, 74, 1, 17, 31, + 17, 38, 140, 7, 34, 80, 1, 17, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 37, 75, 128, 41, 76, 128, 26, 66, 116, 12, 52, 94, 2, 32, 55, 1, 10, 16, + 50, 127, 154, 37, 109, 152, 16, 82, 121, 5, 59, 85, 1, 35, 54, 1, 13, 20, + 40, 142, 167, 17, 110, 157, 2, 71, 112, 1, 44, 72, 1, 27, 45, 1, 11, 17, + 30, 175, 188, 9, 124, 169, 1, 74, 116, 1, 48, 78, 1, 30, 49, 1, 11, 18, + 10, 222, 223, 2, 150, 194, 1, 83, 128, 1, 48, 79, 1, 27, 45, 1, 11, 17, + 36, 41, 235, 29, 36, 193, 10, 27, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, 165, 222, 177, 162, 215, 110, 135, 195, 57, 113, 168, 23, 83, 120, 10, 49, 61, + 85, 190, 223, 36, 139, 200, 5, 90, 146, 1, 60, 103, 1, 38, 65, 1, 18, 30, + 72, 202, 223, 23, 141, 199, 2, 86, 140, 1, 56, 97, 1, 36, 61, 1, 16, 27, + 55, 218, 225, 13, 145, 200, 1, 86, 141, 1, 57, 99, 1, 35, 61, 1, 13, 22, + 15, 235, 212, 1, 132, 184, 1, 84, 139, 1, 57, 97, 1, 34, 56, 1, 14, 23, + 181, 21, 201, 61, 37, 123, 10, 38, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 47, 106, 172, 95, 104, 173, 42, 93, 159, 18, 77, 131, 4, 50, 81, 1, 17, 23, + 62, 147, 199, 44, 130, 189, 28, 102, 154, 18, 75, 115, 2, 44, 65, 1, 12, 19, + 55, 153, 210, 24, 130, 194, 3, 93, 146, 1, 61, 97, 1, 31, 50, 1, 10, 16, + 49, 186, 223, 17, 148, 204, 1, 96, 142, 1, 53, 83, 1, 26, 44, 1, 11, 17, + 13, 217, 212, 2, 136, 180, 1, 78, 124, 1, 50, 83, 1, 29, 49, 1, 14, 23, + 197, 13, 247, 82, 17, 222, 25, 17, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 126, 186, 247, 234, 191, 243, 176, 177, 234, 104, 158, 220, 66, 128, 186, 55, 90, 137, + 111, 197, 242, 46, 158, 219, 9, 104, 171, 2, 65, 125, 1, 44, 80, 1, 17, 91, + 104, 208, 245, 39, 168, 224, 3, 109, 162, 1, 79, 124, 1, 50, 102, 1, 43, 102, + 84, 220, 246, 31, 177, 231, 2, 115, 180, 1, 79, 134, 1, 55, 77, 1, 60, 79, + 43, 243, 240, 8, 180, 217, 1, 115, 166, 1, 84, 121, 1, 51, 67, 1, 16, 6, + }, + .switchable_interp_prob{235, 162, 36, 255, 34, 3, 149, 144}, + .inter_mode_prob{ + 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94, + 66, 0, 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0, + }, + .intra_inter_prob{9, 102, 187, 225}, + .comp_inter_prob{9, 102, 187, 225, 0}, + .single_ref_prob{33, 16, 77, 74, 142, 142, 172, 170, 238, 247}, + .comp_ref_prob{50, 126, 123, 221, 226}, + .tx_32x32_prob{3, 136, 37, 5, 52, 13}, + .tx_16x16_prob{20, 152, 15, 101}, + .tx_8x8_prob{100, 66}, + .skip_probs{192, 128, 64}, + .joints{32, 64, 96}, + .sign{128, 128}, + .classes{ + 224, 144, 192, 168, 192, 176, 192, 198, 198, 245, + 216, 128, 176, 160, 176, 176, 192, 198, 198, 208, + }, + .class_0{216, 208}, + .prob_bits{ + 136, 140, 148, 160, 176, 192, 224, 234, 234, 240, + 136, 140, 148, 160, 176, 192, 224, 234, 234, 240, + }, + .class_0_fr{128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64}, + .fr{64, 96, 64, 64, 96, 64}, + .class_0_hp{160, 160}, + .high_precision{128, 128}, +}; + +constexpr std::array<s32, 256> norm_lut{ + 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +constexpr std::array<s32, 254> map_lut{ + 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212, + 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247, + 248, 249, 250, 251, 252, 253, 19, +}; + +// 6.2.14 Tile size calculation + +[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) { + const s32 sb64_cols = (frame_width + 63) / 64; + s32 min_log2 = 0; + + while ((64 << min_log2) < sb64_cols) { + min_log2++; + } + + return min_log2; +} + +[[nodiscard]] s32 CalcMaxLog2TileCols(s32 frame_width) { + const s32 sb64_cols = (frame_width + 63) / 64; + s32 max_log2 = 1; + + while ((sb64_cols >> max_log2) >= 4) { + max_log2++; + } + + return max_log2 - 1; +} + +// Recenters probability. Based on section 6.3.6 of VP9 Specification +[[nodiscard]] s32 RecenterNonNeg(s32 new_prob, s32 old_prob) { + if (new_prob > old_prob * 2) { + return new_prob; + } + + if (new_prob >= old_prob) { + return (new_prob - old_prob) * 2; + } + + return (old_prob - new_prob) * 2 - 1; +} + +// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification +[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) { + new_prob--; + old_prob--; + + std::size_t index{}; + + if (old_prob * 2 <= 0xff) { + index = static_cast<std::size_t>(std::max(0, RecenterNonNeg(new_prob, old_prob) - 1)); + } else { + index = static_cast<std::size_t>( + std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1)); + } + + return map_lut[index]; +} +} // Anonymous namespace + +VP9::VP9(GPU& gpu_) : gpu{gpu_} {} + +VP9::~VP9() = default; + +void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) { + const bool update = new_prob != old_prob; + + writer.Write(update, diff_update_probability); + + if (update) { + WriteProbabilityDelta(writer, new_prob, old_prob); + } +} +template <typename T, std::size_t N> +void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, + const std::array<T, N>& old_prob) { + for (std::size_t offset = 0; offset < new_prob.size(); ++offset) { + WriteProbabilityUpdate(writer, new_prob[offset], old_prob[offset]); + } +} + +template <typename T, std::size_t N> +void VP9::WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, + const std::array<T, N>& old_prob) { + for (std::size_t offset = 0; offset < new_prob.size(); offset += 4) { + WriteProbabilityUpdate(writer, new_prob[offset + 0], old_prob[offset + 0]); + WriteProbabilityUpdate(writer, new_prob[offset + 1], old_prob[offset + 1]); + WriteProbabilityUpdate(writer, new_prob[offset + 2], old_prob[offset + 2]); + } +} + +void VP9::WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) { + const int delta = RemapProbability(new_prob, old_prob); + + EncodeTermSubExp(writer, delta); +} + +void VP9::EncodeTermSubExp(VpxRangeEncoder& writer, s32 value) { + if (WriteLessThan(writer, value, 16)) { + writer.Write(value, 4); + } else if (WriteLessThan(writer, value, 32)) { + writer.Write(value - 16, 4); + } else if (WriteLessThan(writer, value, 64)) { + writer.Write(value - 32, 5); + } else { + value -= 64; + + constexpr s32 size = 8; + + const s32 mask = (1 << size) - 191; + + const s32 delta = value - mask; + + if (delta < 0) { + writer.Write(value, size - 1); + } else { + writer.Write(delta / 2 + mask, size - 1); + writer.Write(delta & 1, 1); + } + } +} + +bool VP9::WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test) { + const bool is_lt = value < test; + writer.Write(!is_lt); + return is_lt; +} + +void VP9::WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode, + const std::array<u8, 1728>& new_prob, + const std::array<u8, 1728>& old_prob) { + constexpr u32 block_bytes = 2 * 2 * 6 * 6 * 3; + + const auto needs_update = [&](u32 base_index) { + return !std::equal(new_prob.begin() + base_index, + new_prob.begin() + base_index + block_bytes, + old_prob.begin() + base_index); + }; + + for (u32 block_index = 0; block_index < 4; block_index++) { + const u32 base_index = block_index * block_bytes; + const bool update = needs_update(base_index); + writer.Write(update); + + if (update) { + u32 index = base_index; + for (s32 i = 0; i < 2; i++) { + for (s32 j = 0; j < 2; j++) { + for (s32 k = 0; k < 6; k++) { + for (s32 l = 0; l < 6; l++) { + if (k != 0 || l < 3) { + WriteProbabilityUpdate(writer, new_prob[index + 0], + old_prob[index + 0]); + WriteProbabilityUpdate(writer, new_prob[index + 1], + old_prob[index + 1]); + WriteProbabilityUpdate(writer, new_prob[index + 2], + old_prob[index + 2]); + } + index += 3; + } + } + } + } + } + if (block_index == static_cast<u32>(tx_mode)) { + break; + } + } +} + +void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) { + const bool update = new_prob != old_prob; + writer.Write(update, diff_update_probability); + + if (update) { + writer.Write(new_prob >> 1, 7); + } +} + +Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) { + PictureInfo picture_info{}; + gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo)); + Vp9PictureInfo vp9_info = picture_info.Convert(); + + InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy); + + // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following + // order: last, golden, altref, current. It may be worthwhile to track the updates done here + // to avoid buffering frame data needed for reference frame updating in the header composition. + std::memcpy(vp9_info.frame_offsets.data(), state.surface_luma_offset.data(), 4 * sizeof(u64)); + + return vp9_info; +} + +void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) { + EntropyProbs entropy{}; + gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs)); + entropy.Convert(dst); +} + +Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) { + Vp9FrameContainer current_frame{}; + { + gpu.SyncGuestHost(); + current_frame.info = GetVp9PictureInfo(state); + current_frame.bit_stream.resize(current_frame.info.bitstream_size); + gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(), + current_frame.info.bitstream_size); + } + // Buffer two frames, saving the last show frame info + if (!next_next_frame.bit_stream.empty()) { + Vp9FrameContainer temp{ + .info = current_frame.info, + .bit_stream = std::move(current_frame.bit_stream), + }; + next_next_frame.info.show_frame = current_frame.info.last_frame_shown; + current_frame.info = next_next_frame.info; + current_frame.bit_stream = std::move(next_next_frame.bit_stream); + next_next_frame = std::move(temp); + + if (!next_frame.bit_stream.empty()) { + Vp9FrameContainer temp2{ + .info = current_frame.info, + .bit_stream = std::move(current_frame.bit_stream), + }; + next_frame.info.show_frame = current_frame.info.last_frame_shown; + current_frame.info = next_frame.info; + current_frame.bit_stream = std::move(next_frame.bit_stream); + next_frame = std::move(temp2); + } else { + next_frame.info = current_frame.info; + next_frame.bit_stream = std::move(current_frame.bit_stream); + } + } else { + next_next_frame.info = current_frame.info; + next_next_frame.bit_stream = std::move(current_frame.bit_stream); + } + return current_frame; +} + +std::vector<u8> VP9::ComposeCompressedHeader() { + VpxRangeEncoder writer{}; + const bool update_probs = current_frame_info.show_frame && !current_frame_info.is_key_frame; + if (!current_frame_info.lossless) { + if (static_cast<u32>(current_frame_info.transform_mode) >= 3) { + writer.Write(3, 2); + writer.Write(current_frame_info.transform_mode == 4); + } else { + writer.Write(current_frame_info.transform_mode, 2); + } + } + + if (current_frame_info.transform_mode == 4) { + // tx_mode_probs() in the spec + WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_8x8_prob, + prev_frame_probs.tx_8x8_prob); + WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_16x16_prob, + prev_frame_probs.tx_16x16_prob); + WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_32x32_prob, + prev_frame_probs.tx_32x32_prob); + if (update_probs) { + prev_frame_probs.tx_8x8_prob = current_frame_info.entropy.tx_8x8_prob; + prev_frame_probs.tx_16x16_prob = current_frame_info.entropy.tx_16x16_prob; + prev_frame_probs.tx_32x32_prob = current_frame_info.entropy.tx_32x32_prob; + } + } + // read_coef_probs() in the spec + WriteCoefProbabilityUpdate(writer, current_frame_info.transform_mode, + current_frame_info.entropy.coef_probs, prev_frame_probs.coef_probs); + // read_skip_probs() in the spec + WriteProbabilityUpdate(writer, current_frame_info.entropy.skip_probs, + prev_frame_probs.skip_probs); + + if (update_probs) { + prev_frame_probs.coef_probs = current_frame_info.entropy.coef_probs; + prev_frame_probs.skip_probs = current_frame_info.entropy.skip_probs; + } + + if (!current_frame_info.intra_only) { + // read_inter_probs() in the spec + WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.inter_mode_prob, + prev_frame_probs.inter_mode_prob); + + if (current_frame_info.interp_filter == 4) { + // read_interp_filter_probs() in the spec + WriteProbabilityUpdate(writer, current_frame_info.entropy.switchable_interp_prob, + prev_frame_probs.switchable_interp_prob); + if (update_probs) { + prev_frame_probs.switchable_interp_prob = + current_frame_info.entropy.switchable_interp_prob; + } + } + + // read_is_inter_probs() in the spec + WriteProbabilityUpdate(writer, current_frame_info.entropy.intra_inter_prob, + prev_frame_probs.intra_inter_prob); + + // frame_reference_mode() in the spec + if ((current_frame_info.ref_frame_sign_bias[1] & 1) != + (current_frame_info.ref_frame_sign_bias[2] & 1) || + (current_frame_info.ref_frame_sign_bias[1] & 1) != + (current_frame_info.ref_frame_sign_bias[3] & 1)) { + if (current_frame_info.reference_mode >= 1) { + writer.Write(1, 1); + writer.Write(current_frame_info.reference_mode == 2); + } else { + writer.Write(0, 1); + } + } + + // frame_reference_mode_probs() in the spec + if (current_frame_info.reference_mode == 2) { + WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_inter_prob, + prev_frame_probs.comp_inter_prob); + if (update_probs) { + prev_frame_probs.comp_inter_prob = current_frame_info.entropy.comp_inter_prob; + } + } + + if (current_frame_info.reference_mode != 1) { + WriteProbabilityUpdate(writer, current_frame_info.entropy.single_ref_prob, + prev_frame_probs.single_ref_prob); + if (update_probs) { + prev_frame_probs.single_ref_prob = current_frame_info.entropy.single_ref_prob; + } + } + + if (current_frame_info.reference_mode != 0) { + WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_ref_prob, + prev_frame_probs.comp_ref_prob); + if (update_probs) { + prev_frame_probs.comp_ref_prob = current_frame_info.entropy.comp_ref_prob; + } + } + + // read_y_mode_probs + for (std::size_t index = 0; index < current_frame_info.entropy.y_mode_prob.size(); + ++index) { + WriteProbabilityUpdate(writer, current_frame_info.entropy.y_mode_prob[index], + prev_frame_probs.y_mode_prob[index]); + } + + // read_partition_probs + WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.partition_prob, + prev_frame_probs.partition_prob); + + // mv_probs + for (s32 i = 0; i < 3; i++) { + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.joints[i], + prev_frame_probs.joints[i]); + } + if (update_probs) { + prev_frame_probs.inter_mode_prob = current_frame_info.entropy.inter_mode_prob; + prev_frame_probs.intra_inter_prob = current_frame_info.entropy.intra_inter_prob; + prev_frame_probs.y_mode_prob = current_frame_info.entropy.y_mode_prob; + prev_frame_probs.partition_prob = current_frame_info.entropy.partition_prob; + prev_frame_probs.joints = current_frame_info.entropy.joints; + } + + for (s32 i = 0; i < 2; i++) { + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.sign[i], + prev_frame_probs.sign[i]); + for (s32 j = 0; j < 10; j++) { + const int index = i * 10 + j; + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.classes[index], + prev_frame_probs.classes[index]); + } + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0[i], + prev_frame_probs.class_0[i]); + + for (s32 j = 0; j < 10; j++) { + const int index = i * 10 + j; + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.prob_bits[index], + prev_frame_probs.prob_bits[index]); + } + } + + for (s32 i = 0; i < 2; i++) { + for (s32 j = 0; j < 2; j++) { + for (s32 k = 0; k < 3; k++) { + const int index = i * 2 * 3 + j * 3 + k; + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_fr[index], + prev_frame_probs.class_0_fr[index]); + } + } + + for (s32 j = 0; j < 3; j++) { + const int index = i * 3 + j; + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.fr[index], + prev_frame_probs.fr[index]); + } + } + + if (current_frame_info.allow_high_precision_mv) { + for (s32 index = 0; index < 2; index++) { + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_hp[index], + prev_frame_probs.class_0_hp[index]); + WriteMvProbabilityUpdate(writer, current_frame_info.entropy.high_precision[index], + prev_frame_probs.high_precision[index]); + } + } + + // save previous probs + if (update_probs) { + prev_frame_probs.sign = current_frame_info.entropy.sign; + prev_frame_probs.classes = current_frame_info.entropy.classes; + prev_frame_probs.class_0 = current_frame_info.entropy.class_0; + prev_frame_probs.prob_bits = current_frame_info.entropy.prob_bits; + prev_frame_probs.class_0_fr = current_frame_info.entropy.class_0_fr; + prev_frame_probs.fr = current_frame_info.entropy.fr; + prev_frame_probs.class_0_hp = current_frame_info.entropy.class_0_hp; + prev_frame_probs.high_precision = current_frame_info.entropy.high_precision; + } + } + writer.End(); + return writer.GetBuffer(); +} + +VpxBitStreamWriter VP9::ComposeUncompressedHeader() { + VpxBitStreamWriter uncomp_writer{}; + + uncomp_writer.WriteU(2, 2); // Frame marker. + uncomp_writer.WriteU(0, 2); // Profile. + uncomp_writer.WriteBit(false); // Show existing frame. + uncomp_writer.WriteBit(!current_frame_info.is_key_frame); // is key frame? + uncomp_writer.WriteBit(current_frame_info.show_frame); // show frame? + uncomp_writer.WriteBit(current_frame_info.error_resilient_mode); // error reslience + + if (current_frame_info.is_key_frame) { + uncomp_writer.WriteU(frame_sync_code, 24); + uncomp_writer.WriteU(0, 3); // Color space. + uncomp_writer.WriteU(0, 1); // Color range. + uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16); + uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16); + uncomp_writer.WriteBit(false); // Render and frame size different. + + // Reset context + prev_frame_probs = default_probs; + swap_next_golden = false; + loop_filter_ref_deltas.fill(0); + loop_filter_mode_deltas.fill(0); + + // allow frames offsets to stabilize before checking for golden frames + grace_period = 4; + + // On key frames, all frame slots are set to the current frame, + // so the value of the selected slot doesn't really matter. + frame_ctxs.fill({current_frame_number, false, default_probs}); + + // intra only, meaning the frame can be recreated with no other references + current_frame_info.intra_only = true; + + } else { + + if (!current_frame_info.show_frame) { + uncomp_writer.WriteBit(current_frame_info.intra_only); + if (!current_frame_info.last_frame_was_key) { + swap_next_golden = !swap_next_golden; + } + } else { + current_frame_info.intra_only = false; + } + if (!current_frame_info.error_resilient_mode) { + uncomp_writer.WriteU(0, 2); // Reset frame context. + } + + // Last, Golden, Altref frames + std::array<s32, 3> ref_frame_index{0, 1, 2}; + + // Set when next frame is hidden + // altref and golden references are swapped + if (swap_next_golden) { + ref_frame_index = std::array<s32, 3>{0, 2, 1}; + } + + // update Last Frame + u64 refresh_frame_flags = 1; + + // golden frame may refresh, determined if the next golden frame offset is changed + bool golden_refresh = false; + if (grace_period <= 0) { + for (s32 index = 1; index < 3; ++index) { + if (current_frame_info.frame_offsets[index] != + next_frame.info.frame_offsets[index]) { + current_frame_info.refresh_frame[index] = true; + golden_refresh = true; + grace_period = 3; + } + } + } + + if (current_frame_info.show_frame && + (!next_frame.info.show_frame || next_frame.info.is_key_frame)) { + // Update golden frame + refresh_frame_flags = swap_next_golden ? 2 : 4; + } + + if (!current_frame_info.show_frame) { + // Update altref + refresh_frame_flags = swap_next_golden ? 2 : 4; + } else if (golden_refresh) { + refresh_frame_flags = 3; + } + + if (current_frame_info.intra_only) { + uncomp_writer.WriteU(frame_sync_code, 24); + uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); + uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16); + uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16); + uncomp_writer.WriteBit(false); // Render and frame size different. + } else { + uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); + + for (s32 index = 1; index < 4; index++) { + uncomp_writer.WriteU(ref_frame_index[index - 1], 3); + uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1); + } + + uncomp_writer.WriteBit(true); // Frame size with refs. + uncomp_writer.WriteBit(false); // Render and frame size different. + uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv); + uncomp_writer.WriteBit(current_frame_info.interp_filter == 4); + + if (current_frame_info.interp_filter != 4) { + uncomp_writer.WriteU(current_frame_info.interp_filter, 2); + } + } + } + + if (!current_frame_info.error_resilient_mode) { + uncomp_writer.WriteBit(true); // Refresh frame context. where do i get this info from? + uncomp_writer.WriteBit(true); // Frame parallel decoding mode. + } + + int frame_ctx_idx = 0; + if (!current_frame_info.show_frame) { + frame_ctx_idx = 1; + } + + uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. + prev_frame_probs = + frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header + frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy}; + + uncomp_writer.WriteU(current_frame_info.first_level, 6); + uncomp_writer.WriteU(current_frame_info.sharpness_level, 3); + uncomp_writer.WriteBit(current_frame_info.mode_ref_delta_enabled); + + if (current_frame_info.mode_ref_delta_enabled) { + // check if ref deltas are different, update accordingly + std::array<bool, 4> update_loop_filter_ref_deltas; + std::array<bool, 2> update_loop_filter_mode_deltas; + + bool loop_filter_delta_update = false; + + for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) { + const s8 old_deltas = loop_filter_ref_deltas[index]; + const s8 new_deltas = current_frame_info.ref_deltas[index]; + const bool differing_delta = old_deltas != new_deltas; + + update_loop_filter_ref_deltas[index] = differing_delta; + loop_filter_delta_update |= differing_delta; + } + + for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) { + const s8 old_deltas = loop_filter_mode_deltas[index]; + const s8 new_deltas = current_frame_info.mode_deltas[index]; + const bool differing_delta = old_deltas != new_deltas; + + update_loop_filter_mode_deltas[index] = differing_delta; + loop_filter_delta_update |= differing_delta; + } + + uncomp_writer.WriteBit(loop_filter_delta_update); + + if (loop_filter_delta_update) { + for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) { + uncomp_writer.WriteBit(update_loop_filter_ref_deltas[index]); + + if (update_loop_filter_ref_deltas[index]) { + uncomp_writer.WriteS(current_frame_info.ref_deltas[index], 6); + } + } + + for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) { + uncomp_writer.WriteBit(update_loop_filter_mode_deltas[index]); + + if (update_loop_filter_mode_deltas[index]) { + uncomp_writer.WriteS(current_frame_info.mode_deltas[index], 6); + } + } + // save new deltas + loop_filter_ref_deltas = current_frame_info.ref_deltas; + loop_filter_mode_deltas = current_frame_info.mode_deltas; + } + } + + uncomp_writer.WriteU(current_frame_info.base_q_index, 8); + + uncomp_writer.WriteDeltaQ(current_frame_info.y_dc_delta_q); + uncomp_writer.WriteDeltaQ(current_frame_info.uv_dc_delta_q); + uncomp_writer.WriteDeltaQ(current_frame_info.uv_ac_delta_q); + + uncomp_writer.WriteBit(false); // Segmentation enabled (TODO). + + const s32 min_tile_cols_log2 = CalcMinLog2TileCols(current_frame_info.frame_size.width); + const s32 max_tile_cols_log2 = CalcMaxLog2TileCols(current_frame_info.frame_size.width); + + const s32 tile_cols_log2_diff = current_frame_info.log2_tile_cols - min_tile_cols_log2; + const s32 tile_cols_log2_inc_mask = (1 << tile_cols_log2_diff) - 1; + + // If it's less than the maximum, we need to add an extra 0 on the bitstream + // to indicate that it should stop reading. + if (current_frame_info.log2_tile_cols < max_tile_cols_log2) { + uncomp_writer.WriteU(tile_cols_log2_inc_mask << 1, tile_cols_log2_diff + 1); + } else { + uncomp_writer.WriteU(tile_cols_log2_inc_mask, tile_cols_log2_diff); + } + + const bool tile_rows_log2_is_nonzero = current_frame_info.log2_tile_rows != 0; + + uncomp_writer.WriteBit(tile_rows_log2_is_nonzero); + + if (tile_rows_log2_is_nonzero) { + uncomp_writer.WriteBit(current_frame_info.log2_tile_rows > 1); + } + + return uncomp_writer; +} + +const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters& state) { + std::vector<u8> bitstream; + { + Vp9FrameContainer curr_frame = GetCurrentFrame(state); + current_frame_info = curr_frame.info; + bitstream = std::move(curr_frame.bit_stream); + } + + // The uncompressed header routine sets PrevProb parameters needed for the compressed header + auto uncomp_writer = ComposeUncompressedHeader(); + std::vector<u8> compressed_header = ComposeCompressedHeader(); + + uncomp_writer.WriteU(static_cast<s32>(compressed_header.size()), 16); + uncomp_writer.Flush(); + std::vector<u8> uncompressed_header = uncomp_writer.GetByteArray(); + + // Write headers and frame to buffer + frame.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size()); + std::memcpy(frame.data(), uncompressed_header.data(), uncompressed_header.size()); + std::memcpy(frame.data() + uncompressed_header.size(), compressed_header.data(), + compressed_header.size()); + std::memcpy(frame.data() + uncompressed_header.size() + compressed_header.size(), + bitstream.data(), bitstream.size()); + + // keep track of frame number + current_frame_number++; + grace_period--; + + // don't display hidden frames + hidden = !current_frame_info.show_frame; + return frame; +} + +VpxRangeEncoder::VpxRangeEncoder() { + Write(false); +} + +VpxRangeEncoder::~VpxRangeEncoder() = default; + +void VpxRangeEncoder::Write(s32 value, s32 value_size) { + for (s32 bit = value_size - 1; bit >= 0; bit--) { + Write(((value >> bit) & 1) != 0); + } +} + +void VpxRangeEncoder::Write(bool bit) { + Write(bit, half_probability); +} + +void VpxRangeEncoder::Write(bool bit, s32 probability) { + u32 local_range = range; + const u32 split = 1 + (((local_range - 1) * static_cast<u32>(probability)) >> 8); + local_range = split; + + if (bit) { + low_value += split; + local_range = range - split; + } + + s32 shift = norm_lut[local_range]; + local_range <<= shift; + count += shift; + + if (count >= 0) { + const s32 offset = shift - count; + + if (((low_value << (offset - 1)) >> 31) != 0) { + const s32 current_pos = static_cast<s32>(base_stream.GetPosition()); + base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos); + while (PeekByte() == 0xff) { + base_stream.WriteByte(0); + + base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos); + } + base_stream.WriteByte(static_cast<u8>((PeekByte() + 1))); + base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin); + } + base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset)))); + + low_value <<= offset; + shift = count; + low_value &= 0xffffff; + count -= 8; + } + + low_value <<= shift; + range = local_range; +} + +void VpxRangeEncoder::End() { + for (std::size_t index = 0; index < 32; ++index) { + Write(false); + } +} + +u8 VpxRangeEncoder::PeekByte() { + const u8 value = base_stream.ReadByte(); + base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos); + + return value; +} + +VpxBitStreamWriter::VpxBitStreamWriter() = default; + +VpxBitStreamWriter::~VpxBitStreamWriter() = default; + +void VpxBitStreamWriter::WriteU(u32 value, u32 value_size) { + WriteBits(value, value_size); +} + +void VpxBitStreamWriter::WriteS(s32 value, u32 value_size) { + const bool sign = value < 0; + if (sign) { + value = -value; + } + + WriteBits(static_cast<u32>(value << 1) | (sign ? 1 : 0), value_size + 1); +} + +void VpxBitStreamWriter::WriteDeltaQ(u32 value) { + const bool delta_coded = value != 0; + WriteBit(delta_coded); + + if (delta_coded) { + WriteBits(value, 4); + } +} + +void VpxBitStreamWriter::WriteBits(u32 value, u32 bit_count) { + s32 value_pos = 0; + s32 remaining = bit_count; + + while (remaining > 0) { + s32 copy_size = remaining; + + const s32 free = GetFreeBufferBits(); + + if (copy_size > free) { + copy_size = free; + } + + const s32 mask = (1 << copy_size) - 1; + + const s32 src_shift = (bit_count - value_pos) - copy_size; + const s32 dst_shift = (buffer_size - buffer_pos) - copy_size; + + buffer |= ((value >> src_shift) & mask) << dst_shift; + + value_pos += copy_size; + buffer_pos += copy_size; + remaining -= copy_size; + } +} + +void VpxBitStreamWriter::WriteBit(bool state) { + WriteBits(state ? 1 : 0, 1); +} + +s32 VpxBitStreamWriter::GetFreeBufferBits() { + if (buffer_pos == buffer_size) { + Flush(); + } + + return buffer_size - buffer_pos; +} + +void VpxBitStreamWriter::Flush() { + if (buffer_pos == 0) { + return; + } + byte_array.push_back(static_cast<u8>(buffer)); + buffer = 0; + buffer_pos = 0; +} + +std::vector<u8>& VpxBitStreamWriter::GetByteArray() { + return byte_array; +} + +const std::vector<u8>& VpxBitStreamWriter::GetByteArray() const { + return byte_array; +} + +} // namespace Tegra::Decoder diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h new file mode 100644 index 000000000..8396c8105 --- /dev/null +++ b/src/video_core/command_classes/codecs/vp9.h @@ -0,0 +1,197 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> + +#include "common/common_types.h" +#include "common/stream.h" +#include "video_core/command_classes/codecs/vp9_types.h" +#include "video_core/command_classes/nvdec_common.h" + +namespace Tegra { +class GPU; +enum class FrameType { KeyFrame = 0, InterFrame = 1 }; +namespace Decoder { + +/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the +/// VP9 header bitstreams. + +class VpxRangeEncoder { +public: + VpxRangeEncoder(); + ~VpxRangeEncoder(); + + VpxRangeEncoder(const VpxRangeEncoder&) = delete; + VpxRangeEncoder& operator=(const VpxRangeEncoder&) = delete; + + VpxRangeEncoder(VpxRangeEncoder&&) = default; + VpxRangeEncoder& operator=(VpxRangeEncoder&&) = default; + + /// Writes the rightmost value_size bits from value into the stream + void Write(s32 value, s32 value_size); + + /// Writes a single bit with half probability + void Write(bool bit); + + /// Writes a bit to the base_stream encoded with probability + void Write(bool bit, s32 probability); + + /// Signal the end of the bitstream + void End(); + + [[nodiscard]] std::vector<u8>& GetBuffer() { + return base_stream.GetBuffer(); + } + + [[nodiscard]] const std::vector<u8>& GetBuffer() const { + return base_stream.GetBuffer(); + } + +private: + u8 PeekByte(); + Common::Stream base_stream{}; + u32 low_value{}; + u32 range{0xff}; + s32 count{-24}; + s32 half_probability{128}; +}; + +class VpxBitStreamWriter { +public: + VpxBitStreamWriter(); + ~VpxBitStreamWriter(); + + VpxBitStreamWriter(const VpxBitStreamWriter&) = delete; + VpxBitStreamWriter& operator=(const VpxBitStreamWriter&) = delete; + + VpxBitStreamWriter(VpxBitStreamWriter&&) = default; + VpxBitStreamWriter& operator=(VpxBitStreamWriter&&) = default; + + /// Write an unsigned integer value + void WriteU(u32 value, u32 value_size); + + /// Write a signed integer value + void WriteS(s32 value, u32 value_size); + + /// Based on 6.2.10 of VP9 Spec, writes a delta coded value + void WriteDeltaQ(u32 value); + + /// Write a single bit. + void WriteBit(bool state); + + /// Pushes current buffer into buffer_array, resets buffer + void Flush(); + + /// Returns byte_array + [[nodiscard]] std::vector<u8>& GetByteArray(); + + /// Returns const byte_array + [[nodiscard]] const std::vector<u8>& GetByteArray() const; + +private: + /// Write bit_count bits from value into buffer + void WriteBits(u32 value, u32 bit_count); + + /// Gets next available position in buffer, invokes Flush() if buffer is full + s32 GetFreeBufferBits(); + + s32 buffer_size{8}; + + s32 buffer{}; + s32 buffer_pos{}; + std::vector<u8> byte_array; +}; + +class VP9 { +public: + explicit VP9(GPU& gpu_); + ~VP9(); + + VP9(const VP9&) = delete; + VP9& operator=(const VP9&) = delete; + + VP9(VP9&&) = default; + VP9& operator=(VP9&&) = delete; + + /// Composes the VP9 frame from the GPU state information. Based on the official VP9 spec + /// documentation + [[nodiscard]] const std::vector<u8>& ComposeFrameHeader( + const NvdecCommon::NvdecRegisters& state); + + /// Returns true if the most recent frame was a hidden frame. + [[nodiscard]] bool WasFrameHidden() const { + return hidden; + } + +private: + /// Generates compressed header probability updates in the bitstream writer + template <typename T, std::size_t N> + void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, + const std::array<T, N>& old_prob); + + /// Generates compressed header probability updates in the bitstream writer + /// If probs are not equal, WriteProbabilityDelta is invoked + void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); + + /// Generates compressed header probability deltas in the bitstream writer + void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); + + /// Inverse of 6.3.4 Decode term subexp + void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value); + + /// Writes if the value is less than the test value + bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test); + + /// Writes probability updates for the Coef probabilities + void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode, + const std::array<u8, 1728>& new_prob, + const std::array<u8, 1728>& old_prob); + + /// Write probabilities for 4-byte aligned structures + template <typename T, std::size_t N> + void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, + const std::array<T, N>& old_prob); + + /// Write motion vector probability updates. 6.3.17 in the spec + void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); + + /// Returns VP9 information from NVDEC provided offset and size + [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state); + + /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct + void InsertEntropy(u64 offset, Vp9EntropyProbs& dst); + + /// Returns frame to be decoded after buffering + [[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state); + + /// Use NVDEC providied information to compose the headers for the current frame + [[nodiscard]] std::vector<u8> ComposeCompressedHeader(); + [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader(); + + GPU& gpu; + std::vector<u8> frame; + + std::array<s8, 4> loop_filter_ref_deltas{}; + std::array<s8, 2> loop_filter_mode_deltas{}; + + bool hidden = false; + s64 current_frame_number = -2; // since we buffer 2 frames + s32 grace_period = 6; // frame offsets need to stabilize + std::array<FrameContexts, 4> frame_ctxs{}; + Vp9FrameContainer next_frame{}; + Vp9FrameContainer next_next_frame{}; + bool swap_next_golden{}; + + Vp9PictureInfo current_frame_info{}; + Vp9EntropyProbs prev_frame_probs{}; + + s32 diff_update_probability = 252; + s32 frame_sync_code = 0x498342; +}; + +} // namespace Decoder +} // namespace Tegra diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h new file mode 100644 index 000000000..139501a1c --- /dev/null +++ b/src/video_core/command_classes/codecs/vp9_types.h @@ -0,0 +1,302 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstring> +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Tegra { +class GPU; + +namespace Decoder { +struct Vp9FrameDimensions { + s16 width{}; + s16 height{}; + s16 luma_pitch{}; + s16 chroma_pitch{}; +}; +static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size"); + +enum FrameFlags : u32 { + IsKeyFrame = 1 << 0, + LastFrameIsKeyFrame = 1 << 1, + FrameSizeChanged = 1 << 2, + ErrorResilientMode = 1 << 3, + LastShowFrame = 1 << 4, + IntraOnly = 1 << 5, +}; + +enum class TxSize { + Tx4x4 = 0, // 4x4 transform + Tx8x8 = 1, // 8x8 transform + Tx16x16 = 2, // 16x16 transform + Tx32x32 = 3, // 32x32 transform + TxSizes = 4 +}; + +enum class TxMode { + Only4X4 = 0, // Only 4x4 transform used + Allow8X8 = 1, // Allow block transform size up to 8x8 + Allow16X16 = 2, // Allow block transform size up to 16x16 + Allow32X32 = 3, // Allow block transform size up to 32x32 + TxModeSelect = 4, // Transform specified for each block + TxModes = 5 +}; + +struct Segmentation { + u8 enabled{}; + u8 update_map{}; + u8 temporal_update{}; + u8 abs_delta{}; + std::array<u32, 8> feature_mask{}; + std::array<std::array<s16, 4>, 8> feature_data{}; +}; +static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size"); + +struct LoopFilter { + u8 mode_ref_delta_enabled{}; + std::array<s8, 4> ref_deltas{}; + std::array<s8, 2> mode_deltas{}; +}; +static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size"); + +struct Vp9EntropyProbs { + std::array<u8, 36> y_mode_prob{}; + std::array<u8, 64> partition_prob{}; + std::array<u8, 1728> coef_probs{}; + std::array<u8, 8> switchable_interp_prob{}; + std::array<u8, 28> inter_mode_prob{}; + std::array<u8, 4> intra_inter_prob{}; + std::array<u8, 5> comp_inter_prob{}; + std::array<u8, 10> single_ref_prob{}; + std::array<u8, 5> comp_ref_prob{}; + std::array<u8, 6> tx_32x32_prob{}; + std::array<u8, 4> tx_16x16_prob{}; + std::array<u8, 2> tx_8x8_prob{}; + std::array<u8, 3> skip_probs{}; + std::array<u8, 3> joints{}; + std::array<u8, 2> sign{}; + std::array<u8, 20> classes{}; + std::array<u8, 2> class_0{}; + std::array<u8, 20> prob_bits{}; + std::array<u8, 12> class_0_fr{}; + std::array<u8, 6> fr{}; + std::array<u8, 2> class_0_hp{}; + std::array<u8, 2> high_precision{}; +}; +static_assert(sizeof(Vp9EntropyProbs) == 0x7B4, "Vp9EntropyProbs is an invalid size"); + +struct Vp9PictureInfo { + bool is_key_frame{}; + bool intra_only{}; + bool last_frame_was_key{}; + bool frame_size_changed{}; + bool error_resilient_mode{}; + bool last_frame_shown{}; + bool show_frame{}; + std::array<s8, 4> ref_frame_sign_bias{}; + s32 base_q_index{}; + s32 y_dc_delta_q{}; + s32 uv_dc_delta_q{}; + s32 uv_ac_delta_q{}; + bool lossless{}; + s32 transform_mode{}; + bool allow_high_precision_mv{}; + s32 interp_filter{}; + s32 reference_mode{}; + s8 comp_fixed_ref{}; + std::array<s8, 2> comp_var_ref{}; + s32 log2_tile_cols{}; + s32 log2_tile_rows{}; + bool segment_enabled{}; + bool segment_map_update{}; + bool segment_map_temporal_update{}; + s32 segment_abs_delta{}; + std::array<u32, 8> segment_feature_enable{}; + std::array<std::array<s16, 4>, 8> segment_feature_data{}; + bool mode_ref_delta_enabled{}; + bool use_prev_in_find_mv_refs{}; + std::array<s8, 4> ref_deltas{}; + std::array<s8, 2> mode_deltas{}; + Vp9EntropyProbs entropy{}; + Vp9FrameDimensions frame_size{}; + u8 first_level{}; + u8 sharpness_level{}; + u32 bitstream_size{}; + std::array<u64, 4> frame_offsets{}; + std::array<bool, 4> refresh_frame{}; +}; + +struct Vp9FrameContainer { + Vp9PictureInfo info{}; + std::vector<u8> bit_stream; +}; + +struct PictureInfo { + INSERT_PADDING_WORDS(12); + u32 bitstream_size{}; + INSERT_PADDING_WORDS(5); + Vp9FrameDimensions last_frame_size{}; + Vp9FrameDimensions golden_frame_size{}; + Vp9FrameDimensions alt_frame_size{}; + Vp9FrameDimensions current_frame_size{}; + u32 vp9_flags{}; + std::array<s8, 4> ref_frame_sign_bias{}; + u8 first_level{}; + u8 sharpness_level{}; + u8 base_q_index{}; + u8 y_dc_delta_q{}; + u8 uv_ac_delta_q{}; + u8 uv_dc_delta_q{}; + u8 lossless{}; + u8 tx_mode{}; + u8 allow_high_precision_mv{}; + u8 interp_filter{}; + u8 reference_mode{}; + s8 comp_fixed_ref{}; + std::array<s8, 2> comp_var_ref{}; + u8 log2_tile_cols{}; + u8 log2_tile_rows{}; + Segmentation segmentation{}; + LoopFilter loop_filter{}; + INSERT_PADDING_BYTES(5); + u32 surface_params{}; + INSERT_PADDING_WORDS(3); + + [[nodiscard]] Vp9PictureInfo Convert() const { + return { + .is_key_frame = (vp9_flags & FrameFlags::IsKeyFrame) != 0, + .intra_only = (vp9_flags & FrameFlags::IntraOnly) != 0, + .last_frame_was_key = (vp9_flags & FrameFlags::LastFrameIsKeyFrame) != 0, + .frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0, + .error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0, + .last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0, + .ref_frame_sign_bias = ref_frame_sign_bias, + .base_q_index = base_q_index, + .y_dc_delta_q = y_dc_delta_q, + .uv_dc_delta_q = uv_dc_delta_q, + .uv_ac_delta_q = uv_ac_delta_q, + .lossless = lossless != 0, + .transform_mode = tx_mode, + .allow_high_precision_mv = allow_high_precision_mv != 0, + .interp_filter = interp_filter, + .reference_mode = reference_mode, + .comp_fixed_ref = comp_fixed_ref, + .comp_var_ref = comp_var_ref, + .log2_tile_cols = log2_tile_cols, + .log2_tile_rows = log2_tile_rows, + .segment_enabled = segmentation.enabled != 0, + .segment_map_update = segmentation.update_map != 0, + .segment_map_temporal_update = segmentation.temporal_update != 0, + .segment_abs_delta = segmentation.abs_delta, + .segment_feature_enable = segmentation.feature_mask, + .segment_feature_data = segmentation.feature_data, + .mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0, + .use_prev_in_find_mv_refs = !(vp9_flags == (FrameFlags::ErrorResilientMode)) && + !(vp9_flags == (FrameFlags::FrameSizeChanged)) && + !(vp9_flags == (FrameFlags::IntraOnly)) && + (vp9_flags == (FrameFlags::LastShowFrame)) && + !(vp9_flags == (FrameFlags::LastFrameIsKeyFrame)), + .ref_deltas = loop_filter.ref_deltas, + .mode_deltas = loop_filter.mode_deltas, + .frame_size = current_frame_size, + .first_level = first_level, + .sharpness_level = sharpness_level, + .bitstream_size = bitstream_size, + }; + } +}; +static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size"); + +struct EntropyProbs { + INSERT_PADDING_BYTES(1024); + std::array<u8, 28> inter_mode_prob{}; + std::array<u8, 4> intra_inter_prob{}; + INSERT_PADDING_BYTES(80); + std::array<u8, 2> tx_8x8_prob{}; + std::array<u8, 4> tx_16x16_prob{}; + std::array<u8, 6> tx_32x32_prob{}; + std::array<u8, 4> y_mode_prob_e8{}; + std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7{}; + INSERT_PADDING_BYTES(64); + std::array<u8, 64> partition_prob{}; + INSERT_PADDING_BYTES(10); + std::array<u8, 8> switchable_interp_prob{}; + std::array<u8, 5> comp_inter_prob{}; + std::array<u8, 3> skip_probs{}; + INSERT_PADDING_BYTES(1); + std::array<u8, 3> joints{}; + std::array<u8, 2> sign{}; + std::array<u8, 2> class_0{}; + std::array<u8, 6> fr{}; + std::array<u8, 2> class_0_hp{}; + std::array<u8, 2> high_precision{}; + std::array<u8, 20> classes{}; + std::array<u8, 12> class_0_fr{}; + std::array<u8, 20> pred_bits{}; + std::array<u8, 10> single_ref_prob{}; + std::array<u8, 5> comp_ref_prob{}; + INSERT_PADDING_BYTES(17); + std::array<u8, 2304> coef_probs{}; + + void Convert(Vp9EntropyProbs& fc) { + fc.inter_mode_prob = inter_mode_prob; + fc.intra_inter_prob = intra_inter_prob; + fc.tx_8x8_prob = tx_8x8_prob; + fc.tx_16x16_prob = tx_16x16_prob; + fc.tx_32x32_prob = tx_32x32_prob; + + for (std::size_t i = 0; i < 4; i++) { + for (std::size_t j = 0; j < 9; j++) { + fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i]; + } + } + + fc.partition_prob = partition_prob; + fc.switchable_interp_prob = switchable_interp_prob; + fc.comp_inter_prob = comp_inter_prob; + fc.skip_probs = skip_probs; + fc.joints = joints; + fc.sign = sign; + fc.class_0 = class_0; + fc.fr = fr; + fc.class_0_hp = class_0_hp; + fc.high_precision = high_precision; + fc.classes = classes; + fc.class_0_fr = class_0_fr; + fc.prob_bits = pred_bits; + fc.single_ref_prob = single_ref_prob; + fc.comp_ref_prob = comp_ref_prob; + + // Skip the 4th element as it goes unused + for (std::size_t i = 0; i < coef_probs.size(); i += 4) { + const std::size_t j = i - i / 4; + fc.coef_probs[j] = coef_probs[i]; + fc.coef_probs[j + 1] = coef_probs[i + 1]; + fc.coef_probs[j + 2] = coef_probs[i + 2]; + } + } +}; +static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size"); + +enum class Ref { Last, Golden, AltRef }; + +struct RefPoolElement { + s64 frame{}; + Ref ref{}; + bool refresh{}; +}; + +struct FrameContexts { + s64 from{}; + bool adapted{}; + Vp9EntropyProbs probs{}; +}; + +}; // namespace Decoder +}; // namespace Tegra diff --git a/src/video_core/command_classes/host1x.cpp b/src/video_core/command_classes/host1x.cpp new file mode 100644 index 000000000..b12494528 --- /dev/null +++ b/src/video_core/command_classes/host1x.cpp @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/command_classes/host1x.h" +#include "video_core/gpu.h" + +Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {} + +Tegra::Host1x::~Host1x() = default; + +void Tegra::Host1x::ProcessMethod(Method method, u32 argument) { + switch (method) { + case Method::LoadSyncptPayload32: + syncpoint_value = argument; + break; + case Method::WaitSyncpt: + case Method::WaitSyncpt32: + Execute(argument); + break; + default: + UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method)); + break; + } +} + +void Tegra::Host1x::Execute(u32 data) { + gpu.WaitFence(data, syncpoint_value); +} diff --git a/src/video_core/command_classes/host1x.h b/src/video_core/command_classes/host1x.h new file mode 100644 index 000000000..7e94799dd --- /dev/null +++ b/src/video_core/command_classes/host1x.h @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Tegra { +class GPU; +class Nvdec; + +class Host1x { +public: + enum class Method : u32 { + WaitSyncpt = 0x8, + LoadSyncptPayload32 = 0x4e, + WaitSyncpt32 = 0x50, + }; + + explicit Host1x(GPU& gpu); + ~Host1x(); + + /// Writes the method into the state, Invoke Execute() if encountered + void ProcessMethod(Method method, u32 argument); + +private: + /// For Host1x, execute is waiting on a syncpoint previously written into the state + void Execute(u32 data); + + u32 syncpoint_value{}; + GPU& gpu; +}; + +} // namespace Tegra diff --git a/src/video_core/command_classes/nvdec.cpp b/src/video_core/command_classes/nvdec.cpp new file mode 100644 index 000000000..79e1f4e13 --- /dev/null +++ b/src/video_core/command_classes/nvdec.cpp @@ -0,0 +1,48 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/command_classes/nvdec.h" +#include "video_core/gpu.h" + +namespace Tegra { + +Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), codec(std::make_unique<Codec>(gpu)) {} + +Nvdec::~Nvdec() = default; + +void Nvdec::ProcessMethod(Method method, const std::vector<u32>& arguments) { + if (method == Method::SetVideoCodec) { + codec->StateWrite(static_cast<u32>(method), arguments[0]); + } else { + codec->StateWrite(static_cast<u32>(method), static_cast<u64>(arguments[0]) << 8); + } + + switch (method) { + case Method::SetVideoCodec: + codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(arguments[0])); + break; + case Method::Execute: + Execute(); + break; + } +} + +AVFramePtr Nvdec::GetFrame() { + return codec->GetCurrentFrame(); +} + +void Nvdec::Execute() { + switch (codec->GetCurrentCodec()) { + case NvdecCommon::VideoCodec::H264: + case NvdecCommon::VideoCodec::Vp9: + codec->Decode(); + break; + default: + UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec())); + break; + } +} + +} // namespace Tegra diff --git a/src/video_core/command_classes/nvdec.h b/src/video_core/command_classes/nvdec.h new file mode 100644 index 000000000..e4877c533 --- /dev/null +++ b/src/video_core/command_classes/nvdec.h @@ -0,0 +1,38 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> +#include "common/common_types.h" +#include "video_core/command_classes/codecs/codec.h" + +namespace Tegra { +class GPU; + +class Nvdec { +public: + enum class Method : u32 { + SetVideoCodec = 0x80, + Execute = 0xc0, + }; + + explicit Nvdec(GPU& gpu); + ~Nvdec(); + + /// Writes the method into the state, Invoke Execute() if encountered + void ProcessMethod(Method method, const std::vector<u32>& arguments); + + /// Return most recently decoded frame + [[nodiscard]] AVFramePtr GetFrame(); + +private: + /// Invoke codec to decode a frame + void Execute(); + + GPU& gpu; + std::unique_ptr<Codec> codec; +}; +} // namespace Tegra diff --git a/src/video_core/command_classes/nvdec_common.h b/src/video_core/command_classes/nvdec_common.h new file mode 100644 index 000000000..01b5e086d --- /dev/null +++ b/src/video_core/command_classes/nvdec_common.h @@ -0,0 +1,48 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Tegra::NvdecCommon { + +struct NvdecRegisters { + INSERT_PADDING_WORDS(256); + u64 set_codec_id{}; + INSERT_PADDING_WORDS(254); + u64 set_platform_id{}; + u64 picture_info_offset{}; + u64 frame_bitstream_offset{}; + u64 frame_number{}; + u64 h264_slice_data_offsets{}; + u64 h264_mv_dump_offset{}; + INSERT_PADDING_WORDS(6); + u64 frame_stats_offset{}; + u64 h264_last_surface_luma_offset{}; + u64 h264_last_surface_chroma_offset{}; + std::array<u64, 17> surface_luma_offset{}; + std::array<u64, 17> surface_chroma_offset{}; + INSERT_PADDING_WORDS(132); + u64 vp9_entropy_probs_offset{}; + u64 vp9_backward_updates_offset{}; + u64 vp9_last_frame_segmap_offset{}; + u64 vp9_curr_frame_segmap_offset{}; + INSERT_PADDING_WORDS(2); + u64 vp9_last_frame_mvs_offset{}; + u64 vp9_curr_frame_mvs_offset{}; + INSERT_PADDING_WORDS(2); +}; +static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size"); + +enum class VideoCodec : u32 { + None = 0x0, + H264 = 0x3, + Vp8 = 0x5, + H265 = 0x7, + Vp9 = 0x9, +}; + +} // namespace Tegra::NvdecCommon diff --git a/src/video_core/command_classes/sync_manager.cpp b/src/video_core/command_classes/sync_manager.cpp new file mode 100644 index 000000000..19dc9e0ab --- /dev/null +++ b/src/video_core/command_classes/sync_manager.cpp @@ -0,0 +1,60 @@ +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#include <algorithm> +#include "sync_manager.h" +#include "video_core/gpu.h" + +namespace Tegra { +SyncptIncrManager::SyncptIncrManager(GPU& gpu_) : gpu(gpu_) {} +SyncptIncrManager::~SyncptIncrManager() = default; + +void SyncptIncrManager::Increment(u32 id) { + increments.emplace_back(0, 0, id, true); + IncrementAllDone(); +} + +u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) { + const u32 handle = current_id++; + increments.emplace_back(handle, class_id, id); + return handle; +} + +void SyncptIncrManager::SignalDone(u32 handle) { + const auto done_incr = + std::find_if(increments.begin(), increments.end(), + [handle](const SyncptIncr& incr) { return incr.id == handle; }); + if (done_incr != increments.cend()) { + done_incr->complete = true; + } + IncrementAllDone(); +} + +void SyncptIncrManager::IncrementAllDone() { + std::size_t done_count = 0; + for (; done_count < increments.size(); ++done_count) { + if (!increments[done_count].complete) { + break; + } + gpu.IncrementSyncPoint(increments[done_count].syncpt_id); + } + increments.erase(increments.begin(), increments.begin() + done_count); +} +} // namespace Tegra diff --git a/src/video_core/command_classes/sync_manager.h b/src/video_core/command_classes/sync_manager.h new file mode 100644 index 000000000..2c321ec58 --- /dev/null +++ b/src/video_core/command_classes/sync_manager.h @@ -0,0 +1,64 @@ +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#pragma once + +#include <mutex> +#include <vector> +#include "common/common_types.h" + +namespace Tegra { +class GPU; +struct SyncptIncr { + u32 id; + u32 class_id; + u32 syncpt_id; + bool complete; + + SyncptIncr(u32 id_, u32 class_id_, u32 syncpt_id_, bool done = false) + : id(id_), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {} +}; + +class SyncptIncrManager { +public: + explicit SyncptIncrManager(GPU& gpu); + ~SyncptIncrManager(); + + /// Add syncpoint id and increment all + void Increment(u32 id); + + /// Returns a handle to increment later + u32 IncrementWhenDone(u32 class_id, u32 id); + + /// IncrememntAllDone, including handle + void SignalDone(u32 handle); + + /// Increment all sequential pending increments that are already done. + void IncrementAllDone(); + +private: + std::vector<SyncptIncr> increments; + std::mutex increment_lock; + u32 current_id{}; + + GPU& gpu; +}; + +} // namespace Tegra diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp new file mode 100644 index 000000000..2b7569335 --- /dev/null +++ b/src/video_core/command_classes/vic.cpp @@ -0,0 +1,172 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include "common/assert.h" +#include "video_core/command_classes/nvdec.h" +#include "video_core/command_classes/vic.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" +#include "video_core/textures/decoders.h" + +extern "C" { +#include <libswscale/swscale.h> +} + +namespace Tegra { + +Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_) + : gpu(gpu_), nvdec_processor(std::move(nvdec_processor_)) {} +Vic::~Vic() = default; + +void Vic::VicStateWrite(u32 offset, u32 arguments) { + u8* const state_offset = reinterpret_cast<u8*>(&vic_state) + offset * sizeof(u32); + std::memcpy(state_offset, &arguments, sizeof(u32)); +} + +void Vic::ProcessMethod(Method method, const std::vector<u32>& arguments) { + LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", method); + VicStateWrite(static_cast<u32>(method), arguments[0]); + const u64 arg = static_cast<u64>(arguments[0]) << 8; + switch (method) { + case Method::Execute: + Execute(); + break; + case Method::SetConfigStructOffset: + config_struct_address = arg; + break; + case Method::SetOutputSurfaceLumaOffset: + output_surface_luma_address = arg; + break; + case Method::SetOutputSurfaceChromaUOffset: + output_surface_chroma_u_address = arg; + break; + case Method::SetOutputSurfaceChromaVOffset: + output_surface_chroma_v_address = arg; + break; + default: + break; + } +} + +void Vic::Execute() { + if (output_surface_luma_address == 0) { + LOG_ERROR(Service_NVDRV, "VIC Luma address not set. Received 0x{:X}", + vic_state.output_surface.luma_offset); + return; + } + const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)}; + const AVFramePtr frame_ptr = nvdec_processor->GetFrame(); + const auto* frame = frame_ptr.get(); + if (!frame || frame->width == 0 || frame->height == 0) { + return; + } + const VideoPixelFormat pixel_format = + static_cast<VideoPixelFormat>(config.pixel_format.Value()); + switch (pixel_format) { + case VideoPixelFormat::BGRA8: + case VideoPixelFormat::RGBA8: { + LOG_TRACE(Service_NVDRV, "Writing RGB Frame"); + + if (scaler_ctx == nullptr || frame->width != scaler_width || + frame->height != scaler_height) { + const AVPixelFormat target_format = + (pixel_format == VideoPixelFormat::RGBA8) ? AV_PIX_FMT_RGBA : AV_PIX_FMT_BGRA; + + sws_freeContext(scaler_ctx); + scaler_ctx = nullptr; + + // FFmpeg returns all frames in YUV420, convert it into expected format + scaler_ctx = + sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width, + frame->height, target_format, 0, nullptr, nullptr, nullptr); + + scaler_width = frame->width; + scaler_height = frame->height; + } + // Get Converted frame + const std::size_t linear_size = frame->width * frame->height * 4; + + using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>; + AVMallocPtr converted_frame_buffer{static_cast<u8*>(av_malloc(linear_size)), av_free}; + + const int converted_stride{frame->width * 4}; + u8* const converted_frame_buf_addr{converted_frame_buffer.get()}; + + sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, + &converted_frame_buf_addr, &converted_stride); + + const u32 blk_kind = static_cast<u32>(config.block_linear_kind); + if (blk_kind != 0) { + // swizzle pitch linear to block linear + const u32 block_height = static_cast<u32>(config.block_linear_height_log2); + const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1, + block_height, 0); + std::vector<u8> swizzled_data(size); + Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4, + frame->width, 4, swizzled_data.data(), + converted_frame_buffer.get(), block_height, 0, 0); + + gpu.MemoryManager().WriteBlock(output_surface_luma_address, swizzled_data.data(), size); + } else { + // send pitch linear frame + gpu.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr, + linear_size); + } + break; + } + case VideoPixelFormat::Yuv420: { + LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame"); + + const std::size_t surface_width = config.surface_width_minus1 + 1; + const std::size_t surface_height = config.surface_height_minus1 + 1; + const std::size_t half_width = surface_width / 2; + const std::size_t half_height = config.surface_height_minus1 / 2; + const std::size_t aligned_width = (surface_width + 0xff) & ~0xff; + + const auto* luma_ptr = frame->data[0]; + const auto* chroma_b_ptr = frame->data[1]; + const auto* chroma_r_ptr = frame->data[2]; + const auto stride = frame->linesize[0]; + const auto half_stride = frame->linesize[1]; + + std::vector<u8> luma_buffer(aligned_width * surface_height); + std::vector<u8> chroma_buffer(aligned_width * half_height); + + // Populate luma buffer + for (std::size_t y = 0; y < surface_height - 1; ++y) { + std::size_t src = y * stride; + std::size_t dst = y * aligned_width; + + std::size_t size = surface_width; + + for (std::size_t offset = 0; offset < size; ++offset) { + luma_buffer[dst + offset] = luma_ptr[src + offset]; + } + } + gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), + luma_buffer.size()); + + // Populate chroma buffer from both channels with interleaving. + for (std::size_t y = 0; y < half_height; ++y) { + std::size_t src = y * half_stride; + std::size_t dst = y * aligned_width; + + for (std::size_t x = 0; x < half_width; ++x) { + chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x]; + chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x]; + } + } + gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(), + chroma_buffer.size()); + break; + } + default: + UNIMPLEMENTED_MSG("Unknown video pixel format {}", config.pixel_format.Value()); + break; + } +} + +} // namespace Tegra diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h new file mode 100644 index 000000000..8c4e284a1 --- /dev/null +++ b/src/video_core/command_classes/vic.h @@ -0,0 +1,110 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> +#include "common/bit_field.h" +#include "common/common_types.h" + +struct SwsContext; + +namespace Tegra { +class GPU; +class Nvdec; + +struct PlaneOffsets { + u32 luma_offset{}; + u32 chroma_u_offset{}; + u32 chroma_v_offset{}; +}; + +struct VicRegisters { + INSERT_PADDING_WORDS(64); + u32 nop{}; + INSERT_PADDING_WORDS(15); + u32 pm_trigger{}; + INSERT_PADDING_WORDS(47); + u32 set_application_id{}; + u32 set_watchdog_timer{}; + INSERT_PADDING_WORDS(17); + u32 context_save_area{}; + u32 context_switch{}; + INSERT_PADDING_WORDS(43); + u32 execute{}; + INSERT_PADDING_WORDS(63); + std::array<std::array<PlaneOffsets, 8>, 8> surfacex_slots{}; + u32 picture_index{}; + u32 control_params{}; + u32 config_struct_offset{}; + u32 filter_struct_offset{}; + u32 palette_offset{}; + u32 hist_offset{}; + u32 context_id{}; + u32 fce_ucode_size{}; + PlaneOffsets output_surface{}; + u32 fce_ucode_offset{}; + INSERT_PADDING_WORDS(4); + std::array<u32, 8> slot_context_id{}; + INSERT_PADDING_WORDS(16); +}; +static_assert(sizeof(VicRegisters) == 0x7A0, "VicRegisters is an invalid size"); + +class Vic { +public: + enum class Method : u32 { + Execute = 0xc0, + SetControlParams = 0x1c1, + SetConfigStructOffset = 0x1c2, + SetOutputSurfaceLumaOffset = 0x1c8, + SetOutputSurfaceChromaUOffset = 0x1c9, + SetOutputSurfaceChromaVOffset = 0x1ca + }; + + explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor); + ~Vic(); + + /// Write to the device state. + void ProcessMethod(Method method, const std::vector<u32>& arguments); + +private: + void Execute(); + + void VicStateWrite(u32 offset, u32 arguments); + VicRegisters vic_state{}; + + enum class VideoPixelFormat : u64_le { + RGBA8 = 0x1f, + BGRA8 = 0x20, + Yuv420 = 0x44, + }; + + union VicConfig { + u64_le raw{}; + BitField<0, 7, u64_le> pixel_format; + BitField<7, 2, u64_le> chroma_loc_horiz; + BitField<9, 2, u64_le> chroma_loc_vert; + BitField<11, 4, u64_le> block_linear_kind; + BitField<15, 4, u64_le> block_linear_height_log2; + BitField<19, 3, u64_le> reserved0; + BitField<22, 10, u64_le> reserved1; + BitField<32, 14, u64_le> surface_width_minus1; + BitField<46, 14, u64_le> surface_height_minus1; + }; + + GPU& gpu; + std::shared_ptr<Tegra::Nvdec> nvdec_processor; + + GPUVAddr config_struct_address{}; + GPUVAddr output_surface_luma_address{}; + GPUVAddr output_surface_chroma_u_address{}; + GPUVAddr output_surface_chroma_v_address{}; + + SwsContext* scaler_ctx{}; + s32 scaler_width{}; + s32 scaler_height{}; +}; + +} // namespace Tegra diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp index b06c32c84..acf2668dc 100644 --- a/src/video_core/compatible_formats.cpp +++ b/src/video_core/compatible_formats.cpp @@ -3,33 +3,33 @@ // Refer to the license.txt file included. #include <array> -#include <bitset> #include <cstddef> +#include "common/common_types.h" #include "video_core/compatible_formats.h" #include "video_core/surface.h" namespace VideoCore::Surface { - namespace { +using Table = std::array<std::array<u64, 2>, MaxPixelFormat>; // Compatibility table taken from Table 3.X.2 in: // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt -constexpr std::array VIEW_CLASS_128_BITS = { +constexpr std::array VIEW_CLASS_128_BITS{ PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_UINT, PixelFormat::R32G32B32A32_SINT, }; -constexpr std::array VIEW_CLASS_96_BITS = { +constexpr std::array VIEW_CLASS_96_BITS{ PixelFormat::R32G32B32_FLOAT, }; // Missing formats: // PixelFormat::RGB32UI, // PixelFormat::RGB32I, -constexpr std::array VIEW_CLASS_64_BITS = { +constexpr std::array VIEW_CLASS_64_BITS{ PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_UINT, PixelFormat::R32G32_SINT, PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM, @@ -38,7 +38,7 @@ constexpr std::array VIEW_CLASS_64_BITS = { // TODO: How should we handle 48 bits? -constexpr std::array VIEW_CLASS_32_BITS = { +constexpr std::array VIEW_CLASS_32_BITS{ PixelFormat::R16G16_FLOAT, PixelFormat::B10G11R11_FLOAT, PixelFormat::R32_FLOAT, PixelFormat::A2B10G10R10_UNORM, PixelFormat::R16G16_UINT, PixelFormat::R32_UINT, PixelFormat::R16G16_SINT, PixelFormat::R32_SINT, PixelFormat::A8B8G8R8_UNORM, @@ -50,43 +50,105 @@ constexpr std::array VIEW_CLASS_32_BITS = { // TODO: How should we handle 24 bits? -constexpr std::array VIEW_CLASS_16_BITS = { +constexpr std::array VIEW_CLASS_16_BITS{ PixelFormat::R16_FLOAT, PixelFormat::R8G8_UINT, PixelFormat::R16_UINT, PixelFormat::R16_SINT, PixelFormat::R8G8_UNORM, PixelFormat::R16_UNORM, PixelFormat::R8G8_SNORM, PixelFormat::R16_SNORM, PixelFormat::R8G8_SINT, }; -constexpr std::array VIEW_CLASS_8_BITS = { +constexpr std::array VIEW_CLASS_8_BITS{ PixelFormat::R8_UINT, PixelFormat::R8_UNORM, PixelFormat::R8_SINT, PixelFormat::R8_SNORM, }; -constexpr std::array VIEW_CLASS_RGTC1_RED = { +constexpr std::array VIEW_CLASS_RGTC1_RED{ PixelFormat::BC4_UNORM, PixelFormat::BC4_SNORM, }; -constexpr std::array VIEW_CLASS_RGTC2_RG = { +constexpr std::array VIEW_CLASS_RGTC2_RG{ PixelFormat::BC5_UNORM, PixelFormat::BC5_SNORM, }; -constexpr std::array VIEW_CLASS_BPTC_UNORM = { +constexpr std::array VIEW_CLASS_BPTC_UNORM{ PixelFormat::BC7_UNORM, PixelFormat::BC7_SRGB, }; -constexpr std::array VIEW_CLASS_BPTC_FLOAT = { +constexpr std::array VIEW_CLASS_BPTC_FLOAT{ PixelFormat::BC6H_SFLOAT, PixelFormat::BC6H_UFLOAT, }; +constexpr std::array VIEW_CLASS_ASTC_4x4_RGBA{ + PixelFormat::ASTC_2D_4X4_UNORM, + PixelFormat::ASTC_2D_4X4_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_5x4_RGBA{ + PixelFormat::ASTC_2D_5X4_UNORM, + PixelFormat::ASTC_2D_5X4_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_5x5_RGBA{ + PixelFormat::ASTC_2D_5X5_UNORM, + PixelFormat::ASTC_2D_5X5_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_6x5_RGBA{ + PixelFormat::ASTC_2D_6X5_UNORM, + PixelFormat::ASTC_2D_6X5_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_6x6_RGBA{ + PixelFormat::ASTC_2D_6X6_UNORM, + PixelFormat::ASTC_2D_6X6_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_8x5_RGBA{ + PixelFormat::ASTC_2D_8X5_UNORM, + PixelFormat::ASTC_2D_8X5_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{ + PixelFormat::ASTC_2D_8X8_UNORM, + PixelFormat::ASTC_2D_8X8_SRGB, +}; + +// Missing formats: +// PixelFormat::ASTC_2D_10X5_UNORM +// PixelFormat::ASTC_2D_10X5_SRGB + +// Missing formats: +// PixelFormat::ASTC_2D_10X6_UNORM +// PixelFormat::ASTC_2D_10X6_SRGB + +constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{ + PixelFormat::ASTC_2D_10X8_UNORM, + PixelFormat::ASTC_2D_10X8_SRGB, +}; + +constexpr std::array VIEW_CLASS_ASTC_10x10_RGBA{ + PixelFormat::ASTC_2D_10X10_UNORM, + PixelFormat::ASTC_2D_10X10_SRGB, +}; + +// Missing formats +// ASTC_2D_12X10_UNORM, +// ASTC_2D_12X10_SRGB, + +constexpr std::array VIEW_CLASS_ASTC_12x12_RGBA{ + PixelFormat::ASTC_2D_12X12_UNORM, + PixelFormat::ASTC_2D_12X12_SRGB, +}; + // Compatibility table taken from Table 4.X.1 in: // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_copy_image.txt -constexpr std::array COPY_CLASS_128_BITS = { +constexpr std::array COPY_CLASS_128_BITS{ PixelFormat::R32G32B32A32_UINT, PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_SINT, PixelFormat::BC2_UNORM, PixelFormat::BC2_SRGB, PixelFormat::BC3_UNORM, PixelFormat::BC3_SRGB, PixelFormat::BC5_UNORM, PixelFormat::BC5_SNORM, @@ -97,7 +159,7 @@ constexpr std::array COPY_CLASS_128_BITS = { // PixelFormat::RGBA32I // COMPRESSED_RG_RGTC2 -constexpr std::array COPY_CLASS_64_BITS = { +constexpr std::array COPY_CLASS_64_BITS{ PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT, PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM, PixelFormat::R16G16B16A16_SINT, PixelFormat::R32G32_UINT, @@ -110,32 +172,36 @@ constexpr std::array COPY_CLASS_64_BITS = { // COMPRESSED_RGBA_S3TC_DXT1_EXT // COMPRESSED_SIGNED_RED_RGTC1 -void Enable(FormatCompatibility::Table& compatiblity, size_t format_a, size_t format_b) { - compatiblity[format_a][format_b] = true; - compatiblity[format_b][format_a] = true; +constexpr void Enable(Table& table, size_t format_a, size_t format_b) { + table[format_a][format_b / 64] |= u64(1) << (format_b % 64); + table[format_b][format_a / 64] |= u64(1) << (format_a % 64); } -void Enable(FormatCompatibility::Table& compatibility, PixelFormat format_a, PixelFormat format_b) { - Enable(compatibility, static_cast<size_t>(format_a), static_cast<size_t>(format_b)); +constexpr void Enable(Table& table, PixelFormat format_a, PixelFormat format_b) { + Enable(table, static_cast<size_t>(format_a), static_cast<size_t>(format_b)); } template <typename Range> -void EnableRange(FormatCompatibility::Table& compatibility, const Range& range) { +constexpr void EnableRange(Table& table, const Range& range) { for (auto it_a = range.begin(); it_a != range.end(); ++it_a) { for (auto it_b = it_a; it_b != range.end(); ++it_b) { - Enable(compatibility, *it_a, *it_b); + Enable(table, *it_a, *it_b); } } } -} // Anonymous namespace +constexpr bool IsSupported(const Table& table, PixelFormat format_a, PixelFormat format_b) { + const size_t a = static_cast<size_t>(format_a); + const size_t b = static_cast<size_t>(format_b); + return ((table[a][b / 64] >> (b % 64)) & 1) != 0; +} -FormatCompatibility::FormatCompatibility() { +constexpr Table MakeViewTable() { + Table view{}; for (size_t i = 0; i < MaxPixelFormat; ++i) { // Identity is allowed Enable(view, i, i); } - EnableRange(view, VIEW_CLASS_128_BITS); EnableRange(view, VIEW_CLASS_96_BITS); EnableRange(view, VIEW_CLASS_64_BITS); @@ -146,10 +212,39 @@ FormatCompatibility::FormatCompatibility() { EnableRange(view, VIEW_CLASS_RGTC2_RG); EnableRange(view, VIEW_CLASS_BPTC_UNORM); EnableRange(view, VIEW_CLASS_BPTC_FLOAT); + EnableRange(view, VIEW_CLASS_ASTC_4x4_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_5x4_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_5x5_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_6x5_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA); + EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA); + return view; +} - copy = view; +constexpr Table MakeCopyTable() { + Table copy = MakeViewTable(); EnableRange(copy, COPY_CLASS_128_BITS); EnableRange(copy, COPY_CLASS_64_BITS); + return copy; +} +} // Anonymous namespace + +bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views) { + if (broken_views) { + // If format views are broken, only accept formats that are identical. + return format_a == format_b; + } + static constexpr Table TABLE = MakeViewTable(); + return IsSupported(TABLE, format_a, format_b); +} + +bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b) { + static constexpr Table TABLE = MakeCopyTable(); + return IsSupported(TABLE, format_a, format_b); } } // namespace VideoCore::Surface diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h index 51766349b..9a0522988 100644 --- a/src/video_core/compatible_formats.h +++ b/src/video_core/compatible_formats.h @@ -4,31 +4,12 @@ #pragma once -#include <array> -#include <bitset> -#include <cstddef> - #include "video_core/surface.h" namespace VideoCore::Surface { -class FormatCompatibility { -public: - using Table = std::array<std::bitset<MaxPixelFormat>, MaxPixelFormat>; - - explicit FormatCompatibility(); - - bool TestView(PixelFormat format_a, PixelFormat format_b) const noexcept { - return view[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)]; - } - - bool TestCopy(PixelFormat format_a, PixelFormat format_b) const noexcept { - return copy[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)]; - } +bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views); -private: - Table view; - Table copy; -}; +bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b); } // namespace VideoCore::Surface diff --git a/src/video_core/delayed_destruction_ring.h b/src/video_core/delayed_destruction_ring.h new file mode 100644 index 000000000..4f1d29c04 --- /dev/null +++ b/src/video_core/delayed_destruction_ring.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <utility> +#include <vector> + +namespace VideoCommon { + +/// Container to push objects to be destroyed a few ticks in the future +template <typename T, size_t TICKS_TO_DESTROY> +class DelayedDestructionRing { +public: + void Tick() { + index = (index + 1) % TICKS_TO_DESTROY; + elements[index].clear(); + } + + void Push(T&& object) { + elements[index].push_back(std::move(object)); + } + +private: + size_t index = 0; + std::array<std::vector<T>, TICKS_TO_DESTROY> elements; +}; + +} // namespace VideoCommon diff --git a/src/video_core/dirty_flags.cpp b/src/video_core/dirty_flags.cpp index e16075993..7149af290 100644 --- a/src/video_core/dirty_flags.cpp +++ b/src/video_core/dirty_flags.cpp @@ -9,13 +9,33 @@ #include "video_core/dirty_flags.h" #define OFF(field_name) MAXWELL3D_REG_INDEX(field_name) -#define NUM(field_name) (sizeof(::Tegra::Engines::Maxwell3D::Regs::field_name) / sizeof(u32)) +#define NUM(field_name) (sizeof(::Tegra::Engines::Maxwell3D::Regs::field_name) / (sizeof(u32))) namespace VideoCommon::Dirty { - +namespace { using Tegra::Engines::Maxwell3D; -void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables) { +void SetupDirtyVertexBuffers(Maxwell3D::DirtyState::Tables& tables) { + static constexpr std::size_t num_array = 3; + for (std::size_t i = 0; i < Maxwell3D::Regs::NumVertexArrays; ++i) { + const std::size_t array_offset = OFF(vertex_array) + i * NUM(vertex_array[0]); + const std::size_t limit_offset = OFF(vertex_array_limit) + i * NUM(vertex_array_limit[0]); + + FillBlock(tables, array_offset, num_array, VertexBuffer0 + i, VertexBuffers); + FillBlock(tables, limit_offset, NUM(vertex_array_limit), VertexBuffer0 + i, VertexBuffers); + } +} + +void SetupIndexBuffer(Maxwell3D::DirtyState::Tables& tables) { + FillBlock(tables[0], OFF(index_array), NUM(index_array), IndexBuffer); +} + +void SetupDirtyDescriptors(Maxwell3D::DirtyState::Tables& tables) { + FillBlock(tables[0], OFF(tic), NUM(tic), Descriptors); + FillBlock(tables[0], OFF(tsc), NUM(tsc), Descriptors); +} + +void SetupDirtyRenderTargets(Maxwell3D::DirtyState::Tables& tables) { static constexpr std::size_t num_per_rt = NUM(rt[0]); static constexpr std::size_t begin = OFF(rt); static constexpr std::size_t num = num_per_rt * Maxwell3D::Regs::NumRenderTargets; @@ -23,6 +43,10 @@ void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tabl FillBlock(tables[0], begin + rt * num_per_rt, num_per_rt, ColorBuffer0 + rt); } FillBlock(tables[1], begin, num, RenderTargets); + FillBlock(tables[0], OFF(render_area), NUM(render_area), RenderTargets); + + tables[0][OFF(rt_control)] = RenderTargets; + tables[1][OFF(rt_control)] = RenderTargetControl; static constexpr std::array zeta_flags{ZetaBuffer, RenderTargets}; for (std::size_t i = 0; i < std::size(zeta_flags); ++i) { @@ -34,5 +58,13 @@ void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tabl FillBlock(table, OFF(zeta), NUM(zeta), flag); } } +} // Anonymous namespace + +void SetupDirtyFlags(Maxwell3D::DirtyState::Tables& tables) { + SetupDirtyVertexBuffers(tables); + SetupIndexBuffer(tables); + SetupDirtyDescriptors(tables); + SetupDirtyRenderTargets(tables); +} } // namespace VideoCommon::Dirty diff --git a/src/video_core/dirty_flags.h b/src/video_core/dirty_flags.h index 3f6c1d83a..702688ace 100644 --- a/src/video_core/dirty_flags.h +++ b/src/video_core/dirty_flags.h @@ -16,7 +16,10 @@ namespace VideoCommon::Dirty { enum : u8 { NullEntry = 0, + Descriptors, + RenderTargets, + RenderTargetControl, ColorBuffer0, ColorBuffer1, ColorBuffer2, @@ -27,6 +30,12 @@ enum : u8 { ColorBuffer7, ZetaBuffer, + VertexBuffers, + VertexBuffer0, + VertexBuffer31 = VertexBuffer0 + 31, + + IndexBuffer, + LastCommonEntry, }; @@ -44,6 +53,6 @@ void FillBlock(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables, std::size_ FillBlock(tables[1], begin, num, index_b); } -void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables); +void SetupDirtyFlags(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables); } // namespace VideoCommon::Dirty diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index f2f96ac33..8b33c04ab 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/cityhash.h" #include "common/microprofile.h" #include "core/core.h" #include "core/memory.h" @@ -12,7 +13,7 @@ namespace Tegra { -DmaPusher::DmaPusher(Core::System& system, GPU& gpu) : gpu{gpu}, system{system} {} +DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_) : gpu{gpu_}, system{system_} {} DmaPusher::~DmaPusher() = default; @@ -22,8 +23,6 @@ void DmaPusher::DispatchCalls() { MICROPROFILE_SCOPE(DispatchCalls); gpu.SyncGuestHost(); - // On entering GPU code, assume all memory may be touched by the ARM core. - gpu.Maxwell3D().OnMemoryWrite(); dma_pushbuffer_subindex = 0; @@ -45,32 +44,41 @@ bool DmaPusher::Step() { return false; } - const CommandList& command_list{dma_pushbuffer.front()}; - ASSERT_OR_EXECUTE(!command_list.empty(), { - // Somehow the command_list is empty, in order to avoid a crash - // We ignore it and assume its size is 0. - dma_pushbuffer.pop(); - dma_pushbuffer_subindex = 0; - return true; - }); - const CommandListHeader command_list_header{command_list[dma_pushbuffer_subindex++]}; - const GPUVAddr dma_get = command_list_header.addr; - - if (dma_pushbuffer_subindex >= command_list.size()) { - // We've gone through the current list, remove it from the queue - dma_pushbuffer.pop(); - dma_pushbuffer_subindex = 0; - } + CommandList& command_list{dma_pushbuffer.front()}; - if (command_list_header.size == 0) { - return true; - } + ASSERT_OR_EXECUTE( + command_list.command_lists.size() || command_list.prefetch_command_list.size(), { + // Somehow the command_list is empty, in order to avoid a crash + // We ignore it and assume its size is 0. + dma_pushbuffer.pop(); + dma_pushbuffer_subindex = 0; + return true; + }); + + if (command_list.prefetch_command_list.size()) { + // Prefetched command list from nvdrv, used for things like synchronization + command_headers = std::move(command_list.prefetch_command_list); + dma_pushbuffer.pop(); + } else { + const CommandListHeader command_list_header{ + command_list.command_lists[dma_pushbuffer_subindex++]}; + const GPUVAddr dma_get = command_list_header.addr; + + if (dma_pushbuffer_subindex >= command_list.command_lists.size()) { + // We've gone through the current list, remove it from the queue + dma_pushbuffer.pop(); + dma_pushbuffer_subindex = 0; + } - // Push buffer non-empty, read a word - command_headers.resize(command_list_header.size); - gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(), - command_list_header.size * sizeof(u32)); + if (command_list_header.size == 0) { + return true; + } + // Push buffer non-empty, read a word + command_headers.resize(command_list_header.size); + gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(), + command_list_header.size * sizeof(u32)); + } for (std::size_t index = 0; index < command_headers.size();) { const CommandHeader& command_header = command_headers[index]; @@ -142,7 +150,12 @@ void DmaPusher::SetState(const CommandHeader& command_header) { void DmaPusher::CallMethod(u32 argument) const { if (dma_state.method < non_puller_methods) { - gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count}); + gpu.CallMethod(GPU::MethodCall{ + dma_state.method, + argument, + dma_state.subchannel, + dma_state.method_count, + }); } else { subchannels[dma_state.subchannel]->CallMethod(dma_state.method, argument, dma_state.is_last_call); diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index efa90d170..19f286fa7 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -18,6 +18,8 @@ class System; namespace Tegra { +class GPU; + enum class SubmissionMode : u32 { IncreasingOld = 0, Increasing = 1, @@ -27,6 +29,31 @@ enum class SubmissionMode : u32 { IncreaseOnce = 5 }; +// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence +// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4. +// So the values you see in docs might be multiplied by 4. +enum class BufferMethods : u32 { + BindObject = 0x0, + Nop = 0x2, + SemaphoreAddressHigh = 0x4, + SemaphoreAddressLow = 0x5, + SemaphoreSequence = 0x6, + SemaphoreTrigger = 0x7, + NotifyIntr = 0x8, + WrcacheFlush = 0x9, + Unk28 = 0xA, + UnkCacheFlush = 0xB, + RefCnt = 0x14, + SemaphoreAcquire = 0x1A, + SemaphoreRelease = 0x1B, + FenceValue = 0x1C, + FenceAction = 0x1D, + WaitForInterrupt = 0x1E, + Unk7c = 0x1F, + Yield = 0x20, + NonPullerMethods = 0x40, +}; + struct CommandListHeader { union { u64 raw; @@ -49,9 +76,23 @@ union CommandHeader { static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout"); static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!"); -class GPU; +inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, SubmissionMode mode) { + CommandHeader result{}; + result.method.Assign(static_cast<u32>(method)); + result.arg_count.Assign(arg_count); + result.mode.Assign(mode); + return result; +} -using CommandList = std::vector<Tegra::CommandListHeader>; +struct CommandList final { + CommandList() = default; + explicit CommandList(std::size_t size) : command_lists(size) {} + explicit CommandList(std::vector<CommandHeader>&& prefetch_command_list_) + : prefetch_command_list{std::move(prefetch_command_list_)} {} + + std::vector<CommandListHeader> command_lists; + std::vector<CommandHeader> prefetch_command_list; +}; /** * The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the @@ -60,9 +101,9 @@ using CommandList = std::vector<Tegra::CommandListHeader>; * See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for * details on this implementation. */ -class DmaPusher { +class DmaPusher final { public: - explicit DmaPusher(Core::System& system, GPU& gpu); + explicit DmaPusher(Core::System& system_, GPU& gpu_); ~DmaPusher(); void Push(CommandList&& entries) { @@ -71,7 +112,7 @@ public: void DispatchCalls(); - void BindSubchannel(Tegra::Engines::EngineInterface* engine, u32 subchannel_id) { + void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { subchannels[subchannel_id] = engine; } @@ -104,7 +145,7 @@ private: bool ib_enable{true}; ///< IB mode enabled - std::array<Tegra::Engines::EngineInterface*, max_subchannels> subchannels{}; + std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; GPU& gpu; Core::System& system; diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp index d44ad0cd8..71d7e1473 100644 --- a/src/video_core/engines/engine_upload.cpp +++ b/src/video_core/engines/engine_upload.cpp @@ -11,16 +11,16 @@ namespace Tegra::Engines::Upload { -State::State(MemoryManager& memory_manager, Registers& regs) - : regs{regs}, memory_manager{memory_manager} {} +State::State(MemoryManager& memory_manager_, Registers& regs_) + : regs{regs_}, memory_manager{memory_manager_} {} State::~State() = default; -void State::ProcessExec(const bool is_linear) { +void State::ProcessExec(const bool is_linear_) { write_offset = 0; copy_size = regs.line_length_in * regs.line_count; inner_buffer.resize(copy_size); - this->is_linear = is_linear; + is_linear = is_linear_; } void State::ProcessData(const u32 data, const bool is_last_call) { diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index 462da419e..1c7f1effa 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h @@ -54,10 +54,10 @@ struct Registers { class State { public: - State(MemoryManager& memory_manager, Registers& regs); + explicit State(MemoryManager& memory_manager_, Registers& regs_); ~State(); - void ProcessExec(bool is_linear); + void ProcessExec(bool is_linear_); void ProcessData(u32 data, bool is_last_call); private: diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 6e50661a3..0f640fdae 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -10,90 +10,58 @@ namespace Tegra::Engines { -Fermi2D::Fermi2D() = default; +Fermi2D::Fermi2D() { + // Nvidia's OpenGL driver seems to assume these values + regs.src.depth = 1; + regs.dst.depth = 1; +} Fermi2D::~Fermi2D() = default; -void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { - rasterizer = &rasterizer_; +void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { + rasterizer = rasterizer_; } void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Fermi2D register, increase the size of the Regs structure"); - regs.reg_array[method] = method_argument; - switch (method) { - // Trigger the surface copy on the last register write. This is blit_src_y, but this is 64-bit, - // so trigger on the second 32-bit write. - case FERMI2D_REG_INDEX(blit_src_y) + 1: { - HandleSurfaceCopy(); - break; - } + if (method == FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1) { + Blit(); } } void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) { - for (std::size_t i = 0; i < amount; i++) { - CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1); + for (u32 i = 0; i < amount; ++i) { + CallMethod(method, base_start[i], methods_pending - i <= 1); } } -static std::pair<u32, u32> DelimitLine(u32 src_1, u32 src_2, u32 dst_1, u32 dst_2, u32 src_line) { - const u32 line_a = src_2 - src_1; - const u32 line_b = dst_2 - dst_1; - const u32 excess = std::max<s32>(0, line_a - src_line + src_1); - return {line_b - (excess * line_b) / line_a, excess}; -} +void Fermi2D::Blit() { + LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}", + regs.src.Address(), regs.dst.Address()); -void Fermi2D::HandleSurfaceCopy() { - LOG_DEBUG(HW_GPU, "Requested a surface copy with operation {}", - static_cast<u32>(regs.operation)); - - // TODO(Subv): Only raw copies are implemented. - ASSERT(regs.operation == Operation::SrcCopy); - - const u32 src_blit_x1{static_cast<u32>(regs.blit_src_x >> 32)}; - const u32 src_blit_y1{static_cast<u32>(regs.blit_src_y >> 32)}; - u32 src_blit_x2, src_blit_y2; - if (regs.blit_control.origin == Origin::Corner) { - src_blit_x2 = - static_cast<u32>((regs.blit_src_x + (regs.blit_du_dx * regs.blit_dst_width)) >> 32); - src_blit_y2 = - static_cast<u32>((regs.blit_src_y + (regs.blit_dv_dy * regs.blit_dst_height)) >> 32); - } else { - src_blit_x2 = static_cast<u32>((regs.blit_src_x >> 32) + regs.blit_dst_width); - src_blit_y2 = static_cast<u32>((regs.blit_src_y >> 32) + regs.blit_dst_height); - } - u32 dst_blit_x2 = regs.blit_dst_x + regs.blit_dst_width; - u32 dst_blit_y2 = regs.blit_dst_y + regs.blit_dst_height; - const auto [new_dst_w, src_excess_x] = - DelimitLine(src_blit_x1, src_blit_x2, regs.blit_dst_x, dst_blit_x2, regs.src.width); - const auto [new_dst_h, src_excess_y] = - DelimitLine(src_blit_y1, src_blit_y2, regs.blit_dst_y, dst_blit_y2, regs.src.height); - dst_blit_x2 = new_dst_w + regs.blit_dst_x; - src_blit_x2 = src_blit_x2 - src_excess_x; - dst_blit_y2 = new_dst_h + regs.blit_dst_y; - src_blit_y2 = src_blit_y2 - src_excess_y; - const auto [new_src_w, dst_excess_x] = - DelimitLine(regs.blit_dst_x, dst_blit_x2, src_blit_x1, src_blit_x2, regs.dst.width); - const auto [new_src_h, dst_excess_y] = - DelimitLine(regs.blit_dst_y, dst_blit_y2, src_blit_y1, src_blit_y2, regs.dst.height); - src_blit_x2 = new_src_w + src_blit_x1; - dst_blit_x2 = dst_blit_x2 - dst_excess_x; - src_blit_y2 = new_src_h + src_blit_y1; - dst_blit_y2 = dst_blit_y2 - dst_excess_y; - const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2}; - const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2, - dst_blit_y2}; - Config copy_config; - copy_config.operation = regs.operation; - copy_config.filter = regs.blit_control.filter; - copy_config.src_rect = src_rect; - copy_config.dst_rect = dst_rect; + UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy"); + UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero"); + UNIMPLEMENTED_IF_MSG(regs.dst.layer != 0, "Destination layer is not zero"); + UNIMPLEMENTED_IF_MSG(regs.src.depth != 1, "Source depth is not one"); + UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled"); - if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { + const auto& args = regs.pixels_from_memory; + const Config config{ + .operation = regs.operation, + .filter = args.sample_mode.filter, + .dst_x0 = args.dst_x0, + .dst_y0 = args.dst_y0, + .dst_x1 = args.dst_x0 + args.dst_width, + .dst_y1 = args.dst_y0 + args.dst_height, + .src_x0 = static_cast<s32>(args.src_x0 >> 32), + .src_y0 = static_cast<s32>(args.src_y0 >> 32), + .src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32), + .src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32), + }; + if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, config)) { UNIMPLEMENTED(); } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 213abfaae..c808a577d 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -38,7 +38,7 @@ public: ~Fermi2D(); /// Binds a rasterizer to this engine. - void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); /// Write the value to the register identified by method. void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; @@ -53,8 +53,8 @@ public: }; enum class Filter : u32 { - PointSample = 0, // Nearest - Linear = 1, + Point = 0, + Bilinear = 1, }; enum class Operation : u32 { @@ -67,88 +67,235 @@ public: BlendPremult = 6, }; - struct Regs { - static constexpr std::size_t NUM_REGS = 0x258; + enum class MemoryLayout : u32 { + BlockLinear = 0, + Pitch = 1, + }; - struct Surface { - RenderTargetFormat format; - BitField<0, 1, u32> linear; - union { - BitField<0, 4, u32> block_width; - BitField<4, 4, u32> block_height; - BitField<8, 4, u32> block_depth; - }; - u32 depth; - u32 layer; - u32 pitch; - u32 width; - u32 height; - u32 address_high; - u32 address_low; - - GPUVAddr Address() const { - return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | - address_low); - } - - u32 BlockWidth() const { - return block_width.Value(); - } - - u32 BlockHeight() const { - return block_height.Value(); - } - - u32 BlockDepth() const { - return block_depth.Value(); - } - }; - static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size"); + enum class CpuIndexWrap : u32 { + Wrap = 0, + NoWrap = 1, + }; + struct Surface { + RenderTargetFormat format; + MemoryLayout linear; union { - struct { - INSERT_UNION_PADDING_WORDS(0x80); + BitField<0, 4, u32> block_width; + BitField<4, 4, u32> block_height; + BitField<8, 4, u32> block_depth; + }; + u32 depth; + u32 layer; + u32 pitch; + u32 width; + u32 height; + u32 addr_upper; + u32 addr_lower; + + [[nodiscard]] constexpr GPUVAddr Address() const noexcept { + return (static_cast<GPUVAddr>(addr_upper) << 32) | static_cast<GPUVAddr>(addr_lower); + } + }; + static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size"); - Surface dst; + enum class SectorPromotion : u32 { + NoPromotion = 0, + PromoteTo2V = 1, + PromoteTo2H = 2, + PromoteTo4 = 3, + }; - INSERT_UNION_PADDING_WORDS(2); + enum class NumTpcs : u32 { + All = 0, + One = 1, + }; - Surface src; + enum class RenderEnableMode : u32 { + False = 0, + True = 1, + Conditional = 2, + RenderIfEqual = 3, + RenderIfNotEqual = 4, + }; - INSERT_UNION_PADDING_WORDS(0x15); + enum class ColorKeyFormat : u32 { + A16R56G6B5 = 0, + A1R5G55B5 = 1, + A8R8G8B8 = 2, + A2R10G10B10 = 3, + Y8 = 4, + Y16 = 5, + Y32 = 6, + }; - Operation operation; + union Beta4 { + BitField<0, 8, u32> b; + BitField<8, 8, u32> g; + BitField<16, 8, u32> r; + BitField<24, 8, u32> a; + }; - INSERT_UNION_PADDING_WORDS(0x177); + struct Point { + u32 x; + u32 y; + }; + enum class PatternSelect : u32 { + MonoChrome8x8 = 0, + MonoChrome64x1 = 1, + MonoChrome1x64 = 2, + Color = 3, + }; + + enum class NotifyType : u32 { + WriteOnly = 0, + WriteThenAwaken = 1, + }; + + enum class MonochromePatternColorFormat : u32 { + A8X8R8G6B5 = 0, + A1R5G5B5 = 1, + A8R8G8B8 = 2, + A8Y8 = 3, + A8X8Y16 = 4, + Y32 = 5, + }; + + enum class MonochromePatternFormat : u32 { + CGA6_M1 = 0, + LE_M1 = 1, + }; + + union Regs { + static constexpr std::size_t NUM_REGS = 0x258; + struct { + u32 object; + INSERT_PADDING_WORDS_NOINIT(0x3F); + u32 no_operation; + NotifyType notify; + INSERT_PADDING_WORDS_NOINIT(0x2); + u32 wait_for_idle; + INSERT_PADDING_WORDS_NOINIT(0xB); + u32 pm_trigger; + INSERT_PADDING_WORDS_NOINIT(0xF); + u32 context_dma_notify; + u32 dst_context_dma; + u32 src_context_dma; + u32 semaphore_context_dma; + INSERT_PADDING_WORDS_NOINIT(0x1C); + Surface dst; + CpuIndexWrap pixels_from_cpu_index_wrap; + u32 kind2d_check_enable; + Surface src; + SectorPromotion pixels_from_memory_sector_promotion; + INSERT_PADDING_WORDS_NOINIT(0x1); + NumTpcs num_tpcs; + u32 render_enable_addr_upper; + u32 render_enable_addr_lower; + RenderEnableMode render_enable_mode; + INSERT_PADDING_WORDS_NOINIT(0x4); + u32 clip_x0; + u32 clip_y0; + u32 clip_width; + u32 clip_height; + BitField<0, 1, u32> clip_enable; + BitField<0, 3, ColorKeyFormat> color_key_format; + u32 color_key; + BitField<0, 1, u32> color_key_enable; + BitField<0, 8, u32> rop; + u32 beta1; + Beta4 beta4; + Operation operation; + union { + BitField<0, 6, u32> x; + BitField<8, 6, u32> y; + } pattern_offset; + BitField<0, 2, PatternSelect> pattern_select; + INSERT_PADDING_WORDS_NOINIT(0xC); + struct { + BitField<0, 3, MonochromePatternColorFormat> color_format; + BitField<0, 1, MonochromePatternFormat> format; + u32 color0; + u32 color1; + u32 pattern0; + u32 pattern1; + } monochrome_pattern; + struct { + std::array<u32, 0x40> X8R8G8B8; + std::array<u32, 0x20> R5G6B5; + std::array<u32, 0x20> X1R5G5B5; + std::array<u32, 0x10> Y8; + } color_pattern; + INSERT_PADDING_WORDS_NOINIT(0x10); + struct { + u32 prim_mode; + u32 prim_color_format; + u32 prim_color; + u32 line_tie_break_bits; + INSERT_PADDING_WORDS_NOINIT(0x14); + u32 prim_point_xy; + INSERT_PADDING_WORDS_NOINIT(0x7); + std::array<Point, 0x40> prim_point; + } render_solid; + struct { + u32 data_type; + u32 color_format; + u32 index_format; + u32 mono_format; + u32 wrap; + u32 color0; + u32 color1; + u32 mono_opacity; + INSERT_PADDING_WORDS_NOINIT(0x6); + u32 src_width; + u32 src_height; + u32 dx_du_frac; + u32 dx_du_int; + u32 dx_dv_frac; + u32 dy_dv_int; + u32 dst_x0_frac; + u32 dst_x0_int; + u32 dst_y0_frac; + u32 dst_y0_int; + u32 data; + } pixels_from_cpu; + INSERT_PADDING_WORDS_NOINIT(0x3); + u32 big_endian_control; + INSERT_PADDING_WORDS_NOINIT(0x3); + struct { + BitField<0, 3, u32> block_shape; + BitField<0, 5, u32> corral_size; + BitField<0, 1, u32> safe_overlap; union { - u32 raw; BitField<0, 1, Origin> origin; BitField<4, 1, Filter> filter; - } blit_control; - - INSERT_UNION_PADDING_WORDS(0x8); - - u32 blit_dst_x; - u32 blit_dst_y; - u32 blit_dst_width; - u32 blit_dst_height; - u64 blit_du_dx; - u64 blit_dv_dy; - u64 blit_src_x; - u64 blit_src_y; - - INSERT_UNION_PADDING_WORDS(0x21); - }; - std::array<u32, NUM_REGS> reg_array; + } sample_mode; + INSERT_PADDING_WORDS_NOINIT(0x8); + s32 dst_x0; + s32 dst_y0; + s32 dst_width; + s32 dst_height; + s64 du_dx; + s64 dv_dy; + s64 src_x0; + s64 src_y0; + } pixels_from_memory; }; + std::array<u32, NUM_REGS> reg_array; } regs{}; struct Config { Operation operation; Filter filter; - Common::Rectangle<u32> src_rect; - Common::Rectangle<u32> dst_rect; + s32 dst_x0; + s32 dst_y0; + s32 dst_x1; + s32 dst_y1; + s32 src_x0; + s32 src_y0; + s32 src_x1; + s32 src_y1; }; private: @@ -156,25 +303,49 @@ private: /// Performs the copy from the source surface to the destination surface as configured in the /// registers. - void HandleSurfaceCopy(); + void Blit(); }; #define ASSERT_REG_POSITION(field_name, position) \ - static_assert(offsetof(Fermi2D::Regs, field_name) == position * 4, \ + static_assert(offsetof(Fermi2D::Regs, field_name) == position, \ "Field " #field_name " has invalid position") -ASSERT_REG_POSITION(dst, 0x80); -ASSERT_REG_POSITION(src, 0x8C); -ASSERT_REG_POSITION(operation, 0xAB); -ASSERT_REG_POSITION(blit_control, 0x223); -ASSERT_REG_POSITION(blit_dst_x, 0x22c); -ASSERT_REG_POSITION(blit_dst_y, 0x22d); -ASSERT_REG_POSITION(blit_dst_width, 0x22e); -ASSERT_REG_POSITION(blit_dst_height, 0x22f); -ASSERT_REG_POSITION(blit_du_dx, 0x230); -ASSERT_REG_POSITION(blit_dv_dy, 0x232); -ASSERT_REG_POSITION(blit_src_x, 0x234); -ASSERT_REG_POSITION(blit_src_y, 0x236); +ASSERT_REG_POSITION(object, 0x0); +ASSERT_REG_POSITION(no_operation, 0x100); +ASSERT_REG_POSITION(notify, 0x104); +ASSERT_REG_POSITION(wait_for_idle, 0x110); +ASSERT_REG_POSITION(pm_trigger, 0x140); +ASSERT_REG_POSITION(context_dma_notify, 0x180); +ASSERT_REG_POSITION(dst_context_dma, 0x184); +ASSERT_REG_POSITION(src_context_dma, 0x188); +ASSERT_REG_POSITION(semaphore_context_dma, 0x18C); +ASSERT_REG_POSITION(dst, 0x200); +ASSERT_REG_POSITION(pixels_from_cpu_index_wrap, 0x228); +ASSERT_REG_POSITION(kind2d_check_enable, 0x22C); +ASSERT_REG_POSITION(src, 0x230); +ASSERT_REG_POSITION(pixels_from_memory_sector_promotion, 0x258); +ASSERT_REG_POSITION(num_tpcs, 0x260); +ASSERT_REG_POSITION(render_enable_addr_upper, 0x264); +ASSERT_REG_POSITION(render_enable_addr_lower, 0x268); +ASSERT_REG_POSITION(clip_x0, 0x280); +ASSERT_REG_POSITION(clip_y0, 0x284); +ASSERT_REG_POSITION(clip_width, 0x288); +ASSERT_REG_POSITION(clip_height, 0x28c); +ASSERT_REG_POSITION(clip_enable, 0x290); +ASSERT_REG_POSITION(color_key_format, 0x294); +ASSERT_REG_POSITION(color_key, 0x298); +ASSERT_REG_POSITION(rop, 0x2A0); +ASSERT_REG_POSITION(beta1, 0x2A4); +ASSERT_REG_POSITION(beta4, 0x2A8); +ASSERT_REG_POSITION(operation, 0x2AC); +ASSERT_REG_POSITION(pattern_offset, 0x2B0); +ASSERT_REG_POSITION(pattern_select, 0x2B4); +ASSERT_REG_POSITION(monochrome_pattern, 0x2E8); +ASSERT_REG_POSITION(color_pattern, 0x300); +ASSERT_REG_POSITION(render_solid, 0x580); +ASSERT_REG_POSITION(pixels_from_cpu, 0x800); +ASSERT_REG_POSITION(big_endian_control, 0x870); +ASSERT_REG_POSITION(pixels_from_memory, 0x880); #undef ASSERT_REG_POSITION diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 898370739..a9b75091e 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -21,8 +21,8 @@ KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manage KeplerCompute::~KeplerCompute() = default; -void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { - rasterizer = &rasterizer_; +void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { + rasterizer = rasterizer_; } void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) { @@ -39,7 +39,6 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal case KEPLER_COMPUTE_REG_INDEX(data_upload): { upload_state.ProcessData(method_argument, is_last_call); if (is_last_call) { - system.GPU().Maxwell3D().OnMemoryWrite(); } break; } @@ -58,24 +57,6 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun } } -Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const { - const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value(); - ASSERT(cbuf_mask[regs.tex_cb_index]); - - const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index]; - ASSERT(texinfo.Address() != 0); - - const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle); - ASSERT(address < texinfo.Address() + texinfo.size); - - const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)}; - return GetTextureInfo(tex_handle); -} - -Texture::FullTextureInfo KeplerCompute::GetTextureInfo(Texture::TextureHandle tex_handle) const { - return Texture::FullTextureInfo{GetTICEntry(tex_handle.tic_id), GetTSCEntry(tex_handle.tsc_id)}; -} - u32 KeplerCompute::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const { ASSERT(stage == ShaderType::Compute); const auto& buffer = launch_description.const_buffer_config[const_buffer]; @@ -98,9 +79,11 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con SamplerDescriptor KeplerCompute::AccessSampler(u32 handle) const { const Texture::TextureHandle tex_handle{handle}; - const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle); - SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic); - result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value()); + const Texture::TICEntry tic = GetTICEntry(tex_handle.tic_id); + const Texture::TSCEntry tsc = GetTSCEntry(tex_handle.tsc_id); + + SamplerDescriptor result = SamplerDescriptor::FromTIC(tic); + result.is_shadow.Assign(tsc.depth_compare_enabled.Value()); return result; } diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 7f2500aab..7c40cba38 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -46,7 +46,7 @@ public: ~KeplerCompute(); /// Binds a rasterizer to this engine. - void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); static constexpr std::size_t NumConstBuffers = 8; @@ -55,7 +55,7 @@ public: union { struct { - INSERT_UNION_PADDING_WORDS(0x60); + INSERT_PADDING_WORDS_NOINIT(0x60); Upload::Registers upload; @@ -67,7 +67,7 @@ public: u32 data_upload; - INSERT_UNION_PADDING_WORDS(0x3F); + INSERT_PADDING_WORDS_NOINIT(0x3F); struct { u32 address; @@ -76,11 +76,11 @@ public: } } launch_desc_loc; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); u32 launch; - INSERT_UNION_PADDING_WORDS(0x4A7); + INSERT_PADDING_WORDS_NOINIT(0x4A7); struct { u32 address_high; @@ -92,7 +92,7 @@ public: } } tsc; - INSERT_UNION_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS_NOINIT(0x3); struct { u32 address_high; @@ -104,7 +104,7 @@ public: } } tic; - INSERT_UNION_PADDING_WORDS(0x22); + INSERT_PADDING_WORDS_NOINIT(0x22); struct { u32 address_high; @@ -115,11 +115,11 @@ public: } } code_loc; - INSERT_UNION_PADDING_WORDS(0x3FE); + INSERT_PADDING_WORDS_NOINIT(0x3FE); u32 tex_cb_index; - INSERT_UNION_PADDING_WORDS(0x374); + INSERT_PADDING_WORDS_NOINIT(0x374); }; std::array<u32, NUM_REGS> reg_array; }; @@ -209,11 +209,6 @@ public: void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; - Texture::FullTextureInfo GetTexture(std::size_t offset) const; - - /// Given a texture handle, returns the TSC and TIC entries. - Texture::FullTextureInfo GetTextureInfo(Texture::TextureHandle tex_handle) const; - u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override; SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override; diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index dc71b2eec..560551157 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -14,8 +14,8 @@ namespace Tegra::Engines { -KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager) - : system{system}, upload_state{memory_manager, regs.upload} {} +KeplerMemory::KeplerMemory(Core::System& system_, MemoryManager& memory_manager) + : system{system_}, upload_state{memory_manager, regs.upload} {} KeplerMemory::~KeplerMemory() = default; @@ -33,7 +33,6 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call case KEPLERMEMORY_REG_INDEX(data): { upload_state.ProcessData(method_argument, is_last_call); if (is_last_call) { - system.GPU().Maxwell3D().OnMemoryWrite(); } break; } diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index 5b7f71a00..19808a5c6 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -35,7 +35,7 @@ namespace Tegra::Engines { class KeplerMemory final : public EngineInterface { public: - KeplerMemory(Core::System& system, MemoryManager& memory_manager); + explicit KeplerMemory(Core::System& system_, MemoryManager& memory_manager); ~KeplerMemory(); /// Write the value to the register identified by method. @@ -50,7 +50,7 @@ public: union { struct { - INSERT_UNION_PADDING_WORDS(0x60); + INSERT_PADDING_WORDS_NOINIT(0x60); Upload::Registers upload; @@ -62,7 +62,7 @@ public: u32 data; - INSERT_UNION_PADDING_WORDS(0x11); + INSERT_PADDING_WORDS_NOINIT(0x11); }; std::array<u32, NUM_REGS> reg_array; }; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 33854445f..75517a4f7 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cinttypes> #include <cstring> #include <optional> #include "common/assert.h" @@ -31,8 +30,8 @@ Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) Maxwell3D::~Maxwell3D() = default; -void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { - rasterizer = &rasterizer_; +void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { + rasterizer = rasterizer_; } void Maxwell3D::InitializeRegisterDefaults() { @@ -124,6 +123,115 @@ void Maxwell3D::InitializeRegisterDefaults() { mme_inline[MAXWELL3D_REG_INDEX(index_array.count)] = true; } +void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) { + if (executing_macro == 0) { + // A macro call must begin by writing the macro method's register, not its argument. + ASSERT_MSG((method % 2) == 0, + "Can't start macro execution by writing to the ARGS register"); + executing_macro = method; + } + + macro_params.insert(macro_params.end(), base_start, base_start + amount); + + // Call the macro when there are no more parameters in the command buffer + if (is_last_call) { + CallMacroMethod(executing_macro, macro_params); + macro_params.clear(); + } +} + +u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { + // Keep track of the register value in shadow_state when requested. + const auto control = shadow_state.shadow_ram_control; + if (control == Regs::ShadowRamControl::Track || + control == Regs::ShadowRamControl::TrackWithFilter) { + shadow_state.reg_array[method] = argument; + return argument; + } + if (control == Regs::ShadowRamControl::Replay) { + return shadow_state.reg_array[method]; + } + return argument; +} + +void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) { + if (regs.reg_array[method] == argument) { + return; + } + regs.reg_array[method] = argument; + + for (const auto& table : dirty.tables) { + dirty.flags[table[method]] = true; + } +} + +void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, + bool is_last_call) { + switch (method) { + case MAXWELL3D_REG_INDEX(wait_for_idle): + return rasterizer->WaitForIdle(); + case MAXWELL3D_REG_INDEX(shadow_ram_control): + shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument); + return; + case MAXWELL3D_REG_INDEX(macros.data): + return macro_engine->AddCode(regs.macros.upload_address, argument); + case MAXWELL3D_REG_INDEX(macros.bind): + return ProcessMacroBind(argument); + case MAXWELL3D_REG_INDEX(firmware[4]): + return ProcessFirmwareCall4(); + case MAXWELL3D_REG_INDEX(const_buffer.cb_data): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 1: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 2: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 3: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 4: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 5: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 6: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 7: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 8: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 9: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 10: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 11: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 12: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15: + return StartCBData(method); + case MAXWELL3D_REG_INDEX(cb_bind[0]): + return ProcessCBBind(0); + case MAXWELL3D_REG_INDEX(cb_bind[1]): + return ProcessCBBind(1); + case MAXWELL3D_REG_INDEX(cb_bind[2]): + return ProcessCBBind(2); + case MAXWELL3D_REG_INDEX(cb_bind[3]): + return ProcessCBBind(3); + case MAXWELL3D_REG_INDEX(cb_bind[4]): + return ProcessCBBind(4); + case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): + return DrawArrays(); + case MAXWELL3D_REG_INDEX(clear_buffers): + return ProcessClearBuffers(); + case MAXWELL3D_REG_INDEX(query.query_get): + return ProcessQueryGet(); + case MAXWELL3D_REG_INDEX(condition.mode): + return ProcessQueryCondition(); + case MAXWELL3D_REG_INDEX(counter_reset): + return ProcessCounterReset(); + case MAXWELL3D_REG_INDEX(sync_info): + return ProcessSyncPoint(); + case MAXWELL3D_REG_INDEX(exec_upload): + return upload_state.ProcessExec(regs.exec_upload.linear != 0); + case MAXWELL3D_REG_INDEX(data_upload): + upload_state.ProcessData(argument, is_last_call); + if (is_last_call) { + } + return; + case MAXWELL3D_REG_INDEX(fragment_barrier): + return rasterizer->FragmentBarrier(); + case MAXWELL3D_REG_INDEX(tiled_cache_barrier): + return rasterizer->TiledCacheBarrier(); + } +} + void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) { // Reset the current macro. executing_macro = 0; @@ -157,142 +265,16 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { // Methods after 0xE00 are special, they're actually triggers for some microcode that was // uploaded to the GPU during initialization. if (method >= MacroRegistersStart) { - // We're trying to execute a macro - if (executing_macro == 0) { - // A macro call must begin by writing the macro method's register, not its argument. - ASSERT_MSG((method % 2) == 0, - "Can't start macro execution by writing to the ARGS register"); - executing_macro = method; - } - - macro_params.push_back(method_argument); - - // Call the macro when there are no more parameters in the command buffer - if (is_last_call) { - CallMacroMethod(executing_macro, macro_params); - macro_params.clear(); - } + ProcessMacro(method, &method_argument, 1, is_last_call); return; } ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); - u32 arg = method_argument; - // Keep track of the register value in shadow_state when requested. - if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Track || - shadow_state.shadow_ram_control == Regs::ShadowRamControl::TrackWithFilter) { - shadow_state.reg_array[method] = arg; - } else if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Replay) { - arg = shadow_state.reg_array[method]; - } - - if (regs.reg_array[method] != arg) { - regs.reg_array[method] = arg; - - for (const auto& table : dirty.tables) { - dirty.flags[table[method]] = true; - } - } - - switch (method) { - case MAXWELL3D_REG_INDEX(wait_for_idle): { - rasterizer->WaitForIdle(); - break; - } - case MAXWELL3D_REG_INDEX(shadow_ram_control): { - shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(method_argument); - break; - } - case MAXWELL3D_REG_INDEX(macros.data): { - macro_engine->AddCode(regs.macros.upload_address, arg); - break; - } - case MAXWELL3D_REG_INDEX(macros.bind): { - ProcessMacroBind(arg); - break; - } - case MAXWELL3D_REG_INDEX(firmware[4]): { - ProcessFirmwareCall4(); - break; - } - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { - StartCBData(method); - break; - } - case MAXWELL3D_REG_INDEX(cb_bind[0]): { - ProcessCBBind(0); - break; - } - case MAXWELL3D_REG_INDEX(cb_bind[1]): { - ProcessCBBind(1); - break; - } - case MAXWELL3D_REG_INDEX(cb_bind[2]): { - ProcessCBBind(2); - break; - } - case MAXWELL3D_REG_INDEX(cb_bind[3]): { - ProcessCBBind(3); - break; - } - case MAXWELL3D_REG_INDEX(cb_bind[4]): { - ProcessCBBind(4); - break; - } - case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): { - DrawArrays(); - break; - } - case MAXWELL3D_REG_INDEX(clear_buffers): { - ProcessClearBuffers(); - break; - } - case MAXWELL3D_REG_INDEX(query.query_get): { - ProcessQueryGet(); - break; - } - case MAXWELL3D_REG_INDEX(condition.mode): { - ProcessQueryCondition(); - break; - } - case MAXWELL3D_REG_INDEX(counter_reset): { - ProcessCounterReset(); - break; - } - case MAXWELL3D_REG_INDEX(sync_info): { - ProcessSyncPoint(); - break; - } - case MAXWELL3D_REG_INDEX(exec_upload): { - upload_state.ProcessExec(regs.exec_upload.linear != 0); - break; - } - case MAXWELL3D_REG_INDEX(data_upload): { - upload_state.ProcessData(arg, is_last_call); - if (is_last_call) { - OnMemoryWrite(); - } - break; - } - default: - break; - } + const u32 argument = ProcessShadowRam(method, method_argument); + ProcessDirtyRegisters(method, argument); + ProcessMethodCall(method, argument, method_argument, is_last_call); } void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, @@ -300,50 +282,33 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, // Methods after 0xE00 are special, they're actually triggers for some microcode that was // uploaded to the GPU during initialization. if (method >= MacroRegistersStart) { - // We're trying to execute a macro - if (executing_macro == 0) { - // A macro call must begin by writing the macro method's register, not its argument. - ASSERT_MSG((method % 2) == 0, - "Can't start macro execution by writing to the ARGS register"); - executing_macro = method; - } - - for (std::size_t i = 0; i < amount; i++) { - macro_params.push_back(base_start[i]); - } - - // Call the macro when there are no more parameters in the command buffer - if (amount == methods_pending) { - CallMacroMethod(executing_macro, macro_params); - macro_params.clear(); - } + ProcessMacro(method, base_start, amount, amount == methods_pending); return; } switch (method) { - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): - case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { + case MAXWELL3D_REG_INDEX(const_buffer.cb_data): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 1: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 2: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 3: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 4: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 5: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 6: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 7: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 8: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 9: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 10: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 11: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 12: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14: + case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15: ProcessCBMultiData(method, base_start, amount); break; - } - default: { + default: for (std::size_t i = 0; i < amount; i++) { CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1); } - } + break; } } @@ -396,7 +361,7 @@ void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) { } void Maxwell3D::FlushMMEInlineDraw() { - LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), + LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), regs.vertex_buffer.count); ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); ASSERT(mme_draw.instance_count == mme_draw.gl_end_count); @@ -541,8 +506,7 @@ void Maxwell3D::ProcessCounterReset() { rasterizer->ResetCounter(QueryType::SamplesPassed); break; default: - LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", - static_cast<int>(regs.counter_reset)); + LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.counter_reset); break; } } @@ -557,7 +521,7 @@ void Maxwell3D::ProcessSyncPoint() { } void Maxwell3D::DrawArrays() { - LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), + LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), regs.vertex_buffer.count); ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); @@ -595,27 +559,28 @@ std::optional<u64> Maxwell3D::GetQueryResult() { return 0; case Regs::QuerySelect::SamplesPassed: // Deferred. - rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed, + rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed, system.GPU().GetTicks()); - return {}; + return std::nullopt; default: LOG_DEBUG(HW_GPU, "Unimplemented query select type {}", - static_cast<u32>(regs.query.query_get.select.Value())); + regs.query.query_get.select.Value()); return 1; } } -void Maxwell3D::ProcessCBBind(std::size_t stage_index) { +void Maxwell3D::ProcessCBBind(size_t stage_index) { // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage. - auto& shader = state.shader_stages[stage_index]; - auto& bind_data = regs.cb_bind[stage_index]; - - ASSERT(bind_data.index < Regs::MaxConstBuffers); - auto& buffer = shader.const_buffers[bind_data.index]; - + const auto& bind_data = regs.cb_bind[stage_index]; + auto& buffer = state.shader_stages[stage_index].const_buffers[bind_data.index]; buffer.enabled = bind_data.valid.Value() != 0; buffer.address = regs.const_buffer.BufferAddress(); buffer.size = regs.const_buffer.cb_size; + + const bool is_enabled = bind_data.valid.Value() != 0; + const GPUVAddr gpu_addr = is_enabled ? regs.const_buffer.BufferAddress() : 0; + const u32 size = is_enabled ? regs.const_buffer.cb_size : 0; + rasterizer->BindGraphicsUniformBuffer(stage_index, bind_data.index, gpu_addr, size); } void Maxwell3D::ProcessCBData(u32 value) { @@ -627,7 +592,7 @@ void Maxwell3D::ProcessCBData(u32 value) { } void Maxwell3D::StartCBData(u32 method) { - constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]); + constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data); cb_data_state.start_pos = regs.const_buffer.cb_pos; cb_data_state.id = method - first_cb_data; cb_data_state.current = method; @@ -640,7 +605,7 @@ void Maxwell3D::ProcessCBMultiData(u32 method, const u32* start_base, u32 amount if (cb_data_state.current != null_cb_data) { FinishCBData(); } - constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]); + constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data); cb_data_state.start_pos = regs.const_buffer.cb_pos; cb_data_state.id = method - first_cb_data; cb_data_state.current = method; @@ -670,14 +635,13 @@ void Maxwell3D::FinishCBData() { const u32 id = cb_data_state.id; memory_manager.WriteBlock(address, cb_data_state.buffer[id].data(), size); - OnMemoryWrite(); cb_data_state.id = null_cb_data; cb_data_state.current = null_cb_data; } Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { - const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)}; + const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)}; Texture::TICEntry tic_entry; memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); @@ -686,43 +650,19 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { } Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { - const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)}; + const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)}; Texture::TSCEntry tsc_entry; memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); return tsc_entry; } -Texture::FullTextureInfo Maxwell3D::GetTextureInfo(Texture::TextureHandle tex_handle) const { - return Texture::FullTextureInfo{GetTICEntry(tex_handle.tic_id), GetTSCEntry(tex_handle.tsc_id)}; -} - -Texture::FullTextureInfo Maxwell3D::GetStageTexture(ShaderType stage, std::size_t offset) const { - const auto stage_index = static_cast<std::size_t>(stage); - const auto& shader = state.shader_stages[stage_index]; - const auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index]; - ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); - - const GPUVAddr tex_info_address = - tex_info_buffer.address + offset * sizeof(Texture::TextureHandle); - - ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size); - - const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)}; - - return GetTextureInfo(tex_handle); -} - u32 Maxwell3D::GetRegisterValue(u32 method) const { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register"); return regs.reg_array[method]; } void Maxwell3D::ProcessClearBuffers() { - ASSERT(regs.clear_buffers.R == regs.clear_buffers.G && - regs.clear_buffers.R == regs.clear_buffers.B && - regs.clear_buffers.R == regs.clear_buffers.A); - rasterizer->Clear(); } @@ -730,9 +670,7 @@ u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offse ASSERT(stage != ShaderType::Compute); const auto& shader_stage = state.shader_stages[static_cast<std::size_t>(stage)]; const auto& buffer = shader_stage.const_buffers[const_buffer]; - u32 result; - std::memcpy(&result, memory_manager.GetPointer(buffer.address + offset), sizeof(u32)); - return result; + return memory_manager.Read<u32>(buffer.address + offset); } SamplerDescriptor Maxwell3D::AccessBoundSampler(ShaderType stage, u64 offset) const { @@ -750,9 +688,11 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b SamplerDescriptor Maxwell3D::AccessSampler(u32 handle) const { const Texture::TextureHandle tex_handle{handle}; - const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle); - SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic); - result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value()); + const Texture::TICEntry tic = GetTICEntry(tex_handle.tic_id); + const Texture::TSCEntry tsc = GetTSCEntry(tex_handle.tsc_id); + + SamplerDescriptor result = SamplerDescriptor::FromTIC(tic); + result.is_shadow.Assign(tsc.depth_compare_enabled.Value()); return result; } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index bc289c55d..ffed42a29 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -55,7 +55,7 @@ public: ~Maxwell3D(); /// Binds a rasterizer to this engine. - void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); /// Register structure of the Maxwell3D engine. /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. @@ -438,16 +438,6 @@ public: DecrWrapOGL = 0x8508, }; - enum class MemoryLayout : u32 { - Linear = 0, - BlockLinear = 1, - }; - - enum class InvMemoryLayout : u32 { - BlockLinear = 0, - Linear = 1, - }; - enum class CounterReset : u32 { SampleCnt = 0x01, Unk02 = 0x02, @@ -546,7 +536,7 @@ public: Equation equation_a; Factor factor_source_a; Factor factor_dest_a; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); }; enum class TessellationPrimitive : u32 { @@ -589,26 +579,36 @@ public: NegativeW = 7, }; + enum class SamplerIndex : u32 { + Independently = 0, + ViaHeaderIndex = 1, + }; + + struct TileMode { + union { + BitField<0, 4, u32> block_width; + BitField<4, 4, u32> block_height; + BitField<8, 4, u32> block_depth; + BitField<12, 1, u32> is_pitch_linear; + BitField<16, 1, u32> is_3d; + }; + }; + static_assert(sizeof(TileMode) == 4); + struct RenderTargetConfig { u32 address_high; u32 address_low; u32 width; u32 height; Tegra::RenderTargetFormat format; + TileMode tile_mode; union { - BitField<0, 3, u32> block_width; - BitField<4, 3, u32> block_height; - BitField<8, 3, u32> block_depth; - BitField<12, 1, InvMemoryLayout> type; - BitField<16, 1, u32> is_3d; - } memory_layout; - union { - BitField<0, 16, u32> layers; + BitField<0, 16, u32> depth; BitField<16, 1, u32> volume; }; u32 layer_stride; u32 base_layer; - INSERT_UNION_PADDING_WORDS(7); + INSERT_PADDING_WORDS_NOINIT(7); GPUVAddr Address() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | @@ -640,7 +640,7 @@ public: BitField<8, 3, ViewportSwizzle> z; BitField<12, 3, ViewportSwizzle> w; } swizzle; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); Common::Rectangle<f32> GetRect() const { return { @@ -700,7 +700,7 @@ public: u32 address_low; s32 buffer_size; s32 buffer_offset; - INSERT_UNION_PADDING_WORDS(3); + INSERT_PADDING_WORDS_NOINIT(3); GPUVAddr Address() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | @@ -713,7 +713,7 @@ public: u32 stream; u32 varying_count; u32 stride; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); }; static_assert(sizeof(TransformFeedbackLayout) == 16); @@ -731,7 +731,7 @@ public: union { struct { - INSERT_UNION_PADDING_WORDS(0x44); + INSERT_PADDING_WORDS_NOINIT(0x44); u32 wait_for_idle; @@ -744,7 +744,7 @@ public: ShadowRamControl shadow_ram_control; - INSERT_UNION_PADDING_WORDS(0x16); + INSERT_PADDING_WORDS_NOINIT(0x16); Upload::Registers upload; struct { @@ -755,7 +755,11 @@ public: u32 data_upload; - INSERT_UNION_PADDING_WORDS(0x44); + INSERT_PADDING_WORDS_NOINIT(0x16); + + u32 force_early_fragment_tests; + + INSERT_PADDING_WORDS_NOINIT(0x2D); struct { union { @@ -765,7 +769,7 @@ public: }; } sync_info; - INSERT_UNION_PADDING_WORDS(0x15); + INSERT_PADDING_WORDS_NOINIT(0x15); union { BitField<0, 2, TessellationPrimitive> prim; @@ -777,21 +781,21 @@ public: std::array<f32, 4> tess_level_outer; std::array<f32, 2> tess_level_inner; - INSERT_UNION_PADDING_WORDS(0x10); + INSERT_PADDING_WORDS_NOINIT(0x10); u32 rasterize_enable; std::array<TransformFeedbackBinding, NumTransformFeedbackBuffers> tfb_bindings; - INSERT_UNION_PADDING_WORDS(0xC0); + INSERT_PADDING_WORDS_NOINIT(0xC0); std::array<TransformFeedbackLayout, NumTransformFeedbackBuffers> tfb_layouts; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); u32 tfb_enabled; - INSERT_UNION_PADDING_WORDS(0x2E); + INSERT_PADDING_WORDS_NOINIT(0x2E); std::array<RenderTargetConfig, NumRenderTargets> rt; @@ -799,7 +803,7 @@ public: std::array<ViewPort, NumViewports> viewports; - INSERT_UNION_PADDING_WORDS(0x1D); + INSERT_PADDING_WORDS_NOINIT(0x1D); struct { u32 first; @@ -811,16 +815,16 @@ public: float clear_color[4]; float clear_depth; - INSERT_UNION_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS_NOINIT(0x3); s32 clear_stencil; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); PolygonMode polygon_mode_front; PolygonMode polygon_mode_back; - INSERT_UNION_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS_NOINIT(0x3); u32 polygon_offset_point_enable; u32 polygon_offset_line_enable; @@ -828,46 +832,53 @@ public: u32 patch_vertices; - INSERT_UNION_PADDING_WORDS(0xC); + INSERT_PADDING_WORDS_NOINIT(0x4); + + u32 fragment_barrier; + + INSERT_PADDING_WORDS_NOINIT(0x7); std::array<ScissorTest, NumViewports> scissor_test; - INSERT_UNION_PADDING_WORDS(0x15); + INSERT_PADDING_WORDS_NOINIT(0x15); s32 stencil_back_func_ref; u32 stencil_back_mask; u32 stencil_back_func_mask; - INSERT_UNION_PADDING_WORDS(0xC); + INSERT_PADDING_WORDS_NOINIT(0x5); + + u32 invalidate_texture_data_cache; + + INSERT_PADDING_WORDS_NOINIT(0x1); + + u32 tiled_cache_barrier; + + INSERT_PADDING_WORDS_NOINIT(0x4); u32 color_mask_common; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); f32 depth_bounds[2]; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); u32 rt_separate_frag_data; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); u32 multisample_raster_enable; u32 multisample_raster_samples; std::array<u32, 4> multisample_sample_mask; - INSERT_UNION_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS_NOINIT(0x5); struct { u32 address_high; u32 address_low; Tegra::DepthFormat format; - union { - BitField<0, 4, u32> block_width; - BitField<4, 4, u32> block_height; - BitField<8, 4, u32> block_depth; - BitField<20, 1, InvMemoryLayout> type; - } memory_layout; + TileMode tile_mode; u32 layer_stride; GPUVAddr Address() const { @@ -876,7 +887,18 @@ public: } } zeta; - INSERT_UNION_PADDING_WORDS(0x41); + struct { + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> width; + }; + union { + BitField<0, 16, u32> y; + BitField<16, 16, u32> height; + }; + } render_area; + + INSERT_PADDING_WORDS_NOINIT(0x3F); union { BitField<0, 4, u32> stencil; @@ -885,24 +907,24 @@ public: BitField<12, 4, u32> viewport; } clear_flags; - INSERT_UNION_PADDING_WORDS(0x10); + INSERT_PADDING_WORDS_NOINIT(0x10); u32 fill_rectangle; - INSERT_UNION_PADDING_WORDS(0x8); + INSERT_PADDING_WORDS_NOINIT(0x8); std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; std::array<MsaaSampleLocation, 4> multisample_sample_locations; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); union { BitField<0, 1, u32> enable; BitField<4, 3, u32> target; } multisample_coverage_to_color; - INSERT_UNION_PADDING_WORDS(0x8); + INSERT_PADDING_WORDS_NOINIT(0x8); struct { union { @@ -917,7 +939,7 @@ public: BitField<25, 3, u32> map_7; }; - u32 GetMap(std::size_t index) const { + u32 Map(std::size_t index) const { const std::array<u32, NumRenderTargets> maps{map_0, map_1, map_2, map_3, map_4, map_5, map_6, map_7}; ASSERT(index < maps.size()); @@ -925,20 +947,22 @@ public: } } rt_control; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); u32 zeta_width; u32 zeta_height; union { - BitField<0, 16, u32> zeta_layers; + BitField<0, 16, u32> zeta_depth; BitField<16, 1, u32> zeta_volume; }; - INSERT_UNION_PADDING_WORDS(0x26); + SamplerIndex sampler_index; + + INSERT_PADDING_WORDS_NOINIT(0x25); u32 depth_test_enable; - INSERT_UNION_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS_NOINIT(0x5); u32 independent_blend_enable; @@ -946,7 +970,7 @@ public: u32 alpha_test_enabled; - INSERT_UNION_PADDING_WORDS(0x6); + INSERT_PADDING_WORDS_NOINIT(0x6); u32 d3d_cull_mode; @@ -960,7 +984,8 @@ public: float b; float a; } blend_color; - INSERT_UNION_PADDING_WORDS(0x4); + + INSERT_PADDING_WORDS_NOINIT(0x4); struct { u32 separate_alpha; @@ -969,7 +994,7 @@ public: Blend::Factor factor_dest_rgb; Blend::Equation equation_a; Blend::Factor factor_source_a; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); Blend::Factor factor_dest_a; u32 enable_common; @@ -985,7 +1010,7 @@ public: u32 stencil_front_func_mask; u32 stencil_front_mask; - INSERT_UNION_PADDING_WORDS(0x2); + INSERT_PADDING_WORDS_NOINIT(0x2); u32 frag_color_clamp; @@ -997,12 +1022,17 @@ public: float line_width_smooth; float line_width_aliased; - INSERT_UNION_PADDING_WORDS(0x1F); + INSERT_PADDING_WORDS_NOINIT(0x1B); + + u32 invalidate_sampler_cache_no_wfi; + u32 invalidate_texture_header_cache_no_wfi; + + INSERT_PADDING_WORDS_NOINIT(0x2); u32 vb_element_base; u32 vb_base_instance; - INSERT_UNION_PADDING_WORDS(0x35); + INSERT_PADDING_WORDS_NOINIT(0x35); u32 clip_distance_enabled; @@ -1010,11 +1040,11 @@ public: float point_size; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); u32 point_sprite_enable; - INSERT_UNION_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS_NOINIT(0x3); CounterReset counter_reset; @@ -1027,7 +1057,7 @@ public: BitField<4, 1, u32> alpha_to_one; } multisample_control; - INSERT_UNION_PADDING_WORDS(0x4); + INSERT_PADDING_WORDS_NOINIT(0x4); struct { u32 address_high; @@ -1041,34 +1071,34 @@ public: } condition; struct { - u32 tsc_address_high; - u32 tsc_address_low; - u32 tsc_limit; + u32 address_high; + u32 address_low; + u32 limit; - GPUVAddr TSCAddress() const { - return static_cast<GPUVAddr>( - (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low); + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); } } tsc; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); float polygon_offset_factor; u32 line_smooth_enable; struct { - u32 tic_address_high; - u32 tic_address_low; - u32 tic_limit; + u32 address_high; + u32 address_low; + u32 limit; - GPUVAddr TICAddress() const { - return static_cast<GPUVAddr>( - (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low); + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); } } tic; - INSERT_UNION_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS_NOINIT(0x5); u32 stencil_two_side_enable; StencilOp stencil_back_op_fail; @@ -1076,17 +1106,17 @@ public: StencilOp stencil_back_op_zpass; ComparisonOp stencil_back_func_func; - INSERT_UNION_PADDING_WORDS(0x4); + INSERT_PADDING_WORDS_NOINIT(0x4); u32 framebuffer_srgb; float polygon_offset_units; - INSERT_UNION_PADDING_WORDS(0x4); + INSERT_PADDING_WORDS_NOINIT(0x4); Tegra::Texture::MsaaMode multisample_mode; - INSERT_UNION_PADDING_WORDS(0xC); + INSERT_PADDING_WORDS_NOINIT(0xC); union { BitField<2, 1, u32> coord_origin; @@ -1102,7 +1132,7 @@ public: (static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low); } } code_address; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); struct { u32 vertex_end_gl; @@ -1114,14 +1144,14 @@ public: }; } draw; - INSERT_UNION_PADDING_WORDS(0xA); + INSERT_PADDING_WORDS_NOINIT(0xA); struct { u32 enabled; u32 index; } primitive_restart; - INSERT_UNION_PADDING_WORDS(0x5F); + INSERT_PADDING_WORDS_NOINIT(0x5F); struct { u32 start_addr_high; @@ -1162,9 +1192,9 @@ public: } } index_array; - INSERT_UNION_PADDING_WORDS(0x7); + INSERT_PADDING_WORDS_NOINIT(0x7); - INSERT_UNION_PADDING_WORDS(0x1F); + INSERT_PADDING_WORDS_NOINIT(0x1F); float polygon_offset_clamp; @@ -1178,14 +1208,14 @@ public: } } instanced_arrays; - INSERT_UNION_PADDING_WORDS(0x4); + INSERT_PADDING_WORDS_NOINIT(0x4); union { BitField<0, 1, u32> enable; BitField<4, 8, u32> unk4; } vp_point_size; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); u32 cull_test_enabled; FrontFace front_face; @@ -1193,11 +1223,11 @@ public: u32 pixel_center_integer; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); u32 viewport_transform_enabled; - INSERT_UNION_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS_NOINIT(0x3); union { BitField<0, 1, u32> depth_range_0_1; @@ -1206,18 +1236,18 @@ public: BitField<11, 1, u32> depth_clamp_disabled; } view_volume_clip_control; - INSERT_UNION_PADDING_WORDS(0x1F); + INSERT_PADDING_WORDS_NOINIT(0x1F); u32 depth_bounds_enable; - INSERT_UNION_PADDING_WORDS(1); + INSERT_PADDING_WORDS_NOINIT(1); struct { u32 enable; LogicOperation operation; } logic_op; - INSERT_UNION_PADDING_WORDS(0x1); + INSERT_PADDING_WORDS_NOINIT(0x1); union { u32 raw; @@ -1230,9 +1260,9 @@ public: BitField<6, 4, u32> RT; BitField<10, 11, u32> layer; } clear_buffers; - INSERT_UNION_PADDING_WORDS(0xB); + INSERT_PADDING_WORDS_NOINIT(0xB); std::array<ColorMask, NumRenderTargets> color_mask; - INSERT_UNION_PADDING_WORDS(0x38); + INSERT_PADDING_WORDS_NOINIT(0x38); struct { u32 query_address_high; @@ -1254,7 +1284,7 @@ public: } } query; - INSERT_UNION_PADDING_WORDS(0x3C); + INSERT_PADDING_WORDS_NOINIT(0x3C); struct { union { @@ -1284,8 +1314,7 @@ public: GPUVAddr LimitAddress() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) | - limit_low) + - 1; + limit_low); } } vertex_array_limit[NumVertexArrays]; @@ -1295,10 +1324,10 @@ public: BitField<4, 4, ShaderProgram> program; }; u32 offset; - INSERT_UNION_PADDING_WORDS(14); + INSERT_PADDING_WORDS_NOINIT(14); } shader_config[MaxShaderProgram]; - INSERT_UNION_PADDING_WORDS(0x60); + INSERT_PADDING_WORDS_NOINIT(0x60); u32 firmware[0x20]; @@ -1307,7 +1336,7 @@ public: u32 cb_address_high; u32 cb_address_low; u32 cb_pos; - u32 cb_data[NumCBData]; + std::array<u32, NumCBData> cb_data; GPUVAddr BufferAddress() const { return static_cast<GPUVAddr>( @@ -1315,7 +1344,7 @@ public: } } const_buffer; - INSERT_UNION_PADDING_WORDS(0x10); + INSERT_PADDING_WORDS_NOINIT(0x10); struct { union { @@ -1323,18 +1352,18 @@ public: BitField<0, 1, u32> valid; BitField<4, 5, u32> index; }; - INSERT_UNION_PADDING_WORDS(7); + INSERT_PADDING_WORDS_NOINIT(7); } cb_bind[MaxShaderStage]; - INSERT_UNION_PADDING_WORDS(0x56); + INSERT_PADDING_WORDS_NOINIT(0x56); u32 tex_cb_index; - INSERT_UNION_PADDING_WORDS(0x7D); + INSERT_PADDING_WORDS_NOINIT(0x7D); std::array<std::array<u8, 128>, NumTransformFeedbackBuffers> tfb_varying_locs; - INSERT_UNION_PADDING_WORDS(0x298); + INSERT_PADDING_WORDS_NOINIT(0x298); struct { /// Compressed address of a buffer that holds information about bound SSBOs. @@ -1346,14 +1375,14 @@ public: } } ssbo_info; - INSERT_UNION_PADDING_WORDS(0x11); + INSERT_PADDING_WORDS_NOINIT(0x11); struct { u32 address[MaxShaderStage]; u32 size[MaxShaderStage]; } tex_info_buffers; - INSERT_UNION_PADDING_WORDS(0xCC); + INSERT_PADDING_WORDS_NOINIT(0xCC); }; std::array<u32, NUM_REGS> reg_array; }; @@ -1373,6 +1402,7 @@ public: }; std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages; + u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering. }; @@ -1393,12 +1423,6 @@ public: void FlushMMEInlineDraw(); - /// Given a texture handle, returns the TSC and TIC entries. - Texture::FullTextureInfo GetTextureInfo(Texture::TextureHandle tex_handle) const; - - /// Returns the texture information for a specific texture in a specific shader stage. - Texture::FullTextureInfo GetStageTexture(ShaderType stage, std::size_t offset) const; - u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override; SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override; @@ -1428,11 +1452,6 @@ public: return *rasterizer; } - /// Notify a memory write has happened. - void OnMemoryWrite() { - dirty.flags |= dirty.on_write_stores; - } - enum class MMEDrawMode : u32 { Undefined, Array, @@ -1454,45 +1473,19 @@ public: using Tables = std::array<Table, 2>; Flags flags; - Flags on_write_stores; Tables tables{}; } dirty; private: void InitializeRegisterDefaults(); - Core::System& system; - MemoryManager& memory_manager; + void ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call); - VideoCore::RasterizerInterface* rasterizer = nullptr; + u32 ProcessShadowRam(u32 method, u32 argument); - /// Start offsets of each macro in macro_memory - std::array<u32, 0x80> macro_positions = {}; - - std::array<bool, Regs::NUM_REGS> mme_inline{}; - - /// Macro method that is currently being executed / being fed parameters. - u32 executing_macro = 0; - /// Parameters that have been submitted to the macro call so far. - std::vector<u32> macro_params; - - /// Interpreter for the macro codes uploaded to the GPU. - std::unique_ptr<MacroEngine> macro_engine; - - static constexpr u32 null_cb_data = 0xFFFFFFFF; - struct { - std::array<std::array<u32, 0x4000>, 16> buffer; - u32 current{null_cb_data}; - u32 id{null_cb_data}; - u32 start_pos{}; - u32 counter{}; - } cb_data_state; + void ProcessDirtyRegisters(u32 method, u32 argument); - Upload::State upload_state; - - bool execute_on{true}; - - std::array<u8, Regs::NUM_REGS> dirty_pointers{}; + void ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call); /// Retrieves information about a specific TIC entry from the TIC buffer. Texture::TICEntry GetTICEntry(u32 tic_index) const; @@ -1502,8 +1495,8 @@ private: /** * Call a macro on this engine. + * * @param method Method to call - * @param num_parameters Number of arguments * @param parameters Arguments to the method call */ void CallMacroMethod(u32 method, const std::vector<u32>& parameters); @@ -1542,7 +1535,7 @@ private: void FinishCBData(); /// Handles a write to the CB_BIND register. - void ProcessCBBind(std::size_t stage_index); + void ProcessCBBind(size_t stage_index); /// Handles a write to the VERTEX_END_GL register, triggering a draw. void DrawArrays(); @@ -1552,6 +1545,38 @@ private: /// Returns a query's value or an empty object if the value will be deferred through a cache. std::optional<u64> GetQueryResult(); + + Core::System& system; + MemoryManager& memory_manager; + + VideoCore::RasterizerInterface* rasterizer = nullptr; + + /// Start offsets of each macro in macro_memory + std::array<u32, 0x80> macro_positions{}; + + std::array<bool, Regs::NUM_REGS> mme_inline{}; + + /// Macro method that is currently being executed / being fed parameters. + u32 executing_macro = 0; + /// Parameters that have been submitted to the macro call so far. + std::vector<u32> macro_params; + + /// Interpreter for the macro codes uploaded to the GPU. + std::unique_ptr<MacroEngine> macro_engine; + + static constexpr u32 null_cb_data = 0xFFFFFFFF; + struct CBDataState { + std::array<std::array<u32, 0x4000>, 16> buffer; + u32 current{null_cb_data}; + u32 id{null_cb_data}; + u32 start_pos{}; + u32 counter{}; + }; + CBDataState cb_data_state; + + Upload::State upload_state; + + bool execute_on{true}; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -1564,6 +1589,7 @@ ASSERT_REG_POSITION(shadow_ram_control, 0x49); ASSERT_REG_POSITION(upload, 0x60); ASSERT_REG_POSITION(exec_upload, 0x6C); ASSERT_REG_POSITION(data_upload, 0x6D); +ASSERT_REG_POSITION(force_early_fragment_tests, 0x84); ASSERT_REG_POSITION(sync_info, 0xB2); ASSERT_REG_POSITION(tess_mode, 0xC8); ASSERT_REG_POSITION(tess_level_outer, 0xC9); @@ -1586,10 +1612,13 @@ ASSERT_REG_POSITION(polygon_offset_point_enable, 0x370); ASSERT_REG_POSITION(polygon_offset_line_enable, 0x371); ASSERT_REG_POSITION(polygon_offset_fill_enable, 0x372); ASSERT_REG_POSITION(patch_vertices, 0x373); +ASSERT_REG_POSITION(fragment_barrier, 0x378); ASSERT_REG_POSITION(scissor_test, 0x380); ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); ASSERT_REG_POSITION(stencil_back_mask, 0x3D6); ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); +ASSERT_REG_POSITION(invalidate_texture_data_cache, 0x3DD); +ASSERT_REG_POSITION(tiled_cache_barrier, 0x3DF); ASSERT_REG_POSITION(color_mask_common, 0x3E4); ASSERT_REG_POSITION(depth_bounds, 0x3E7); ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB); @@ -1597,6 +1626,7 @@ ASSERT_REG_POSITION(multisample_raster_enable, 0x3ED); ASSERT_REG_POSITION(multisample_raster_samples, 0x3EE); ASSERT_REG_POSITION(multisample_sample_mask, 0x3EF); ASSERT_REG_POSITION(zeta, 0x3F8); +ASSERT_REG_POSITION(render_area, 0x3FD); ASSERT_REG_POSITION(clear_flags, 0x43E); ASSERT_REG_POSITION(fill_rectangle, 0x44F); ASSERT_REG_POSITION(vertex_attrib_format, 0x458); @@ -1605,7 +1635,8 @@ ASSERT_REG_POSITION(multisample_coverage_to_color, 0x47E); ASSERT_REG_POSITION(rt_control, 0x487); ASSERT_REG_POSITION(zeta_width, 0x48a); ASSERT_REG_POSITION(zeta_height, 0x48b); -ASSERT_REG_POSITION(zeta_layers, 0x48c); +ASSERT_REG_POSITION(zeta_depth, 0x48c); +ASSERT_REG_POSITION(sampler_index, 0x48D); ASSERT_REG_POSITION(depth_test_enable, 0x4B3); ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); @@ -1629,6 +1660,8 @@ ASSERT_REG_POSITION(frag_color_clamp, 0x4EA); ASSERT_REG_POSITION(screen_y_control, 0x4EB); ASSERT_REG_POSITION(line_width_smooth, 0x4EC); ASSERT_REG_POSITION(line_width_aliased, 0x4ED); +ASSERT_REG_POSITION(invalidate_sampler_cache_no_wfi, 0x509); +ASSERT_REG_POSITION(invalidate_texture_header_cache_no_wfi, 0x50A); ASSERT_REG_POSITION(vb_element_base, 0x50D); ASSERT_REG_POSITION(vb_base_instance, 0x50E); ASSERT_REG_POSITION(clip_distance_enabled, 0x544); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index e88290754..a2f19559f 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -16,8 +16,10 @@ namespace Tegra::Engines { using namespace Texture; -MaxwellDMA::MaxwellDMA(Core::System& system, MemoryManager& memory_manager) - : system{system}, memory_manager{memory_manager} {} +MaxwellDMA::MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_) + : system{system_}, memory_manager{memory_manager_} {} + +MaxwellDMA::~MaxwellDMA() = default; void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) { ASSERT_MSG(method < NUM_REGS, "Invalid MaxwellDMA register"); @@ -58,9 +60,6 @@ void MaxwellDMA::Launch() { return; } - // All copies here update the main memory, so mark all rasterizer states as invalid. - system.GPU().Maxwell3D().OnMemoryWrite(); - if (is_src_pitch && is_dst_pitch) { CopyPitchToPitch(); } else { @@ -94,6 +93,7 @@ void MaxwellDMA::CopyPitchToPitch() { } void MaxwellDMA::CopyBlockLinearToPitch() { + UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0); UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0); UNIMPLEMENTED_IF(regs.src_params.layer != 0); @@ -114,8 +114,6 @@ void MaxwellDMA::CopyBlockLinearToPitch() { const u32 block_depth = src_params.block_size.depth; const size_t src_size = CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth); - const size_t src_layer_size = - CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth); if (read_buffer.size() < src_size) { read_buffer.resize(src_size); @@ -135,6 +133,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() { } void MaxwellDMA::CopyPitchToBlockLinear() { + UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one"); + const auto& dst_params = regs.dst_params; const u32 bytes_per_pixel = regs.pitch_in / regs.line_length_in; const u32 width = dst_params.width; diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 50f445efc..3c59eeb13 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -72,11 +72,13 @@ public: struct RenderEnable { enum class Mode : u32 { - FALSE = 0, - TRUE = 1, - CONDITIONAL = 2, - RENDER_IF_EQUAL = 3, - RENDER_IF_NOT_EQUAL = 4, + // Note: This uses Pascal case in order to avoid the identifiers + // FALSE and TRUE, which are reserved on Darwin. + False = 0, + True = 1, + Conditional = 2, + RenderIfEqual = 3, + RenderIfNotEqual = 4, }; PackedGPUVAddr address; @@ -185,8 +187,8 @@ public: }; static_assert(sizeof(RemapConst) == 12); - explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager); - ~MaxwellDMA() = default; + explicit MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_); + ~MaxwellDMA(); /// Write the value to the register identified by method. void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index d374b73cf..8b45f1b62 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -32,31 +32,31 @@ struct Register { constexpr Register() = default; - constexpr Register(u64 value) : value(value) {} + constexpr Register(u64 value_) : value(value_) {} - constexpr operator u64() const { + [[nodiscard]] constexpr operator u64() const { return value; } template <typename T> - constexpr u64 operator-(const T& oth) const { + [[nodiscard]] constexpr u64 operator-(const T& oth) const { return value - oth; } template <typename T> - constexpr u64 operator&(const T& oth) const { + [[nodiscard]] constexpr u64 operator&(const T& oth) const { return value & oth; } - constexpr u64 operator&(const Register& oth) const { + [[nodiscard]] constexpr u64 operator&(const Register& oth) const { return value & oth.value; } - constexpr u64 operator~() const { + [[nodiscard]] constexpr u64 operator~() const { return ~value; } - u64 GetSwizzledIndex(u64 elem) const { + [[nodiscard]] u64 GetSwizzledIndex(u64 elem) const { elem = (value + elem) & 3; return (value & ~3) + elem; } @@ -75,7 +75,7 @@ enum class AttributeSize : u64 { union Attribute { Attribute() = default; - constexpr explicit Attribute(u64 value) : value(value) {} + constexpr explicit Attribute(u64 value_) : value(value_) {} enum class Index : u64 { LayerViewportPointSize = 6, @@ -107,7 +107,7 @@ union Attribute { BitField<31, 1, u64> patch; BitField<47, 3, AttributeSize> size; - bool IsPhysical() const { + [[nodiscard]] bool IsPhysical() const { return patch == 0 && element == 0 && static_cast<u64>(index.Value()) == 0; } } fmt20; @@ -124,7 +124,7 @@ union Attribute { union Sampler { Sampler() = default; - constexpr explicit Sampler(u64 value) : value(value) {} + constexpr explicit Sampler(u64 value_) : value(value_) {} enum class Index : u64 { Sampler_0 = 8, @@ -137,7 +137,7 @@ union Sampler { union Image { Image() = default; - constexpr explicit Image(u64 value) : value{value} {} + constexpr explicit Image(u64 value_) : value{value_} {} BitField<36, 13, u64> index; u64 value; @@ -505,14 +505,14 @@ struct IpaMode { IpaInterpMode interpolation_mode; IpaSampleMode sampling_mode; - bool operator==(const IpaMode& a) const { + [[nodiscard]] bool operator==(const IpaMode& a) const { return std::tie(interpolation_mode, sampling_mode) == std::tie(a.interpolation_mode, a.sampling_mode); } - bool operator!=(const IpaMode& a) const { + [[nodiscard]] bool operator!=(const IpaMode& a) const { return !operator==(a); } - bool operator<(const IpaMode& a) const { + [[nodiscard]] bool operator<(const IpaMode& a) const { return std::tie(interpolation_mode, sampling_mode) < std::tie(a.interpolation_mode, a.sampling_mode); } @@ -658,10 +658,10 @@ union Instruction { return *this; } - constexpr Instruction(u64 value) : value{value} {} + constexpr Instruction(u64 value_) : value{value_} {} constexpr Instruction(const Instruction& instr) : value(instr.value) {} - constexpr bool Bit(u64 offset) const { + [[nodiscard]] constexpr bool Bit(u64 offset) const { return ((value >> offset) & 1) != 0; } @@ -746,34 +746,34 @@ union Instruction { BitField<28, 8, u64> imm_lut28; BitField<48, 8, u64> imm_lut48; - u32 GetImmLut28() const { + [[nodiscard]] u32 GetImmLut28() const { return static_cast<u32>(imm_lut28); } - u32 GetImmLut48() const { + [[nodiscard]] u32 GetImmLut48() const { return static_cast<u32>(imm_lut48); } } lop3; - u16 GetImm20_16() const { + [[nodiscard]] u16 GetImm20_16() const { return static_cast<u16>(imm20_16); } - u32 GetImm20_19() const { + [[nodiscard]] u32 GetImm20_19() const { u32 imm{static_cast<u32>(imm20_19)}; imm <<= 12; imm |= negate_imm ? 0x80000000 : 0; return imm; } - u32 GetImm20_32() const { + [[nodiscard]] u32 GetImm20_32() const { return static_cast<u32>(imm20_32); } - s32 GetSignedImm20_20() const { - u32 immediate = static_cast<u32>(imm20_19 | (negate_imm << 19)); + [[nodiscard]] s32 GetSignedImm20_20() const { + const auto immediate = static_cast<u32>(imm20_19 | (negate_imm << 19)); // Sign extend the 20-bit value. - u32 mask = 1U << (20 - 1); + const auto mask = 1U << (20 - 1); return static_cast<s32>((immediate ^ mask) - mask); } } alu; @@ -857,7 +857,7 @@ union Instruction { BitField<56, 1, u64> second_negate; BitField<30, 9, u64> second; - u32 PackImmediates() const { + [[nodiscard]] u32 PackImmediates() const { // Immediates are half floats shifted. constexpr u32 imm_shift = 6; return static_cast<u32>((first << imm_shift) | (second << (16 + imm_shift))); @@ -1033,7 +1033,7 @@ union Instruction { BitField<28, 2, AtomicType> type; BitField<30, 22, s64> offset; - s32 GetImmediateOffset() const { + [[nodiscard]] s32 GetImmediateOffset() const { return static_cast<s32>(offset << 2); } } atoms; @@ -1215,7 +1215,7 @@ union Instruction { BitField<39, 4, u64> rounding; // H0, H1 extract for F16 missing BitField<41, 1, u64> selector; // Guessed as some games set it, TODO: reverse this value - F2fRoundingOp GetRoundingMode() const { + [[nodiscard]] F2fRoundingOp GetRoundingMode() const { constexpr u64 rounding_mask = 0x0B; return static_cast<F2fRoundingOp>(rounding.Value() & rounding_mask); } @@ -1239,15 +1239,15 @@ union Instruction { BitField<54, 1, u64> aoffi_flag; BitField<55, 3, TextureProcessMode> process_mode; - bool IsComponentEnabled(std::size_t component) const { - return ((1ull << component) & component_mask) != 0; + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { + return ((1ULL << component) & component_mask) != 0; } - TextureProcessMode GetTextureProcessMode() const { + [[nodiscard]] TextureProcessMode GetTextureProcessMode() const { return process_mode; } - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::DC: return dc_flag != 0; @@ -1271,15 +1271,15 @@ union Instruction { BitField<36, 1, u64> aoffi_flag; BitField<37, 3, TextureProcessMode> process_mode; - bool IsComponentEnabled(std::size_t component) const { + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { return ((1ULL << component) & component_mask) != 0; } - TextureProcessMode GetTextureProcessMode() const { + [[nodiscard]] TextureProcessMode GetTextureProcessMode() const { return process_mode; } - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::DC: return dc_flag != 0; @@ -1299,7 +1299,7 @@ union Instruction { BitField<31, 4, u64> component_mask; BitField<49, 1, u64> nodep_flag; - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::NODEP: return nodep_flag != 0; @@ -1309,7 +1309,7 @@ union Instruction { return false; } - bool IsComponentEnabled(std::size_t component) const { + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { return ((1ULL << component) & component_mask) != 0; } } txq; @@ -1321,11 +1321,11 @@ union Instruction { BitField<35, 1, u64> ndv_flag; BitField<49, 1, u64> nodep_flag; - bool IsComponentEnabled(std::size_t component) const { - return ((1ull << component) & component_mask) != 0; + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { + return ((1ULL << component) & component_mask) != 0; } - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::NDV: return (ndv_flag != 0); @@ -1347,7 +1347,7 @@ union Instruction { BitField<54, 2, u64> offset_mode; BitField<56, 2, u64> component; - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::NDV: return ndv_flag != 0; @@ -1373,7 +1373,7 @@ union Instruction { BitField<33, 2, u64> offset_mode; BitField<37, 2, u64> component; - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::NDV: return ndv_flag != 0; @@ -1399,7 +1399,7 @@ union Instruction { BitField<52, 2, u64> component; BitField<55, 1, u64> fp16_flag; - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::DC: return dc_flag != 0; @@ -1422,24 +1422,27 @@ union Instruction { BitField<53, 4, u64> texture_info; BitField<59, 1, u64> fp32_flag; - TextureType GetTextureType() const { + [[nodiscard]] TextureType GetTextureType() const { // The TEXS instruction has a weird encoding for the texture type. - if (texture_info == 0) + if (texture_info == 0) { return TextureType::Texture1D; - if (texture_info >= 1 && texture_info <= 9) + } + if (texture_info >= 1 && texture_info <= 9) { return TextureType::Texture2D; - if (texture_info >= 10 && texture_info <= 11) + } + if (texture_info >= 10 && texture_info <= 11) { return TextureType::Texture3D; - if (texture_info >= 12 && texture_info <= 13) + } + if (texture_info >= 12 && texture_info <= 13) { return TextureType::TextureCube; + } - LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", - static_cast<u32>(texture_info.Value())); + LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", texture_info.Value()); UNREACHABLE(); return TextureType::Texture1D; } - TextureProcessMode GetTextureProcessMode() const { + [[nodiscard]] TextureProcessMode GetTextureProcessMode() const { switch (texture_info) { case 0: case 2: @@ -1458,7 +1461,7 @@ union Instruction { return TextureProcessMode::None; } - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::DC: return (texture_info >= 4 && texture_info <= 6) || texture_info == 9; @@ -1470,16 +1473,16 @@ union Instruction { return false; } - bool IsArrayTexture() const { + [[nodiscard]] bool IsArrayTexture() const { // TEXS only supports Texture2D arrays. return texture_info >= 7 && texture_info <= 9; } - bool HasTwoDestinations() const { + [[nodiscard]] bool HasTwoDestinations() const { return gpr28.Value() != Register::ZeroIndex; } - bool IsComponentEnabled(std::size_t component) const { + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{ {}, {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, @@ -1506,7 +1509,7 @@ union Instruction { BitField<54, 1, u64> cl; BitField<55, 1, u64> process_mode; - TextureProcessMode GetTextureProcessMode() const { + [[nodiscard]] TextureProcessMode GetTextureProcessMode() const { return process_mode == 0 ? TextureProcessMode::LZ : TextureProcessMode::LL; } } tld; @@ -1516,7 +1519,7 @@ union Instruction { BitField<53, 4, u64> texture_info; BitField<59, 1, u64> fp32_flag; - TextureType GetTextureType() const { + [[nodiscard]] TextureType GetTextureType() const { // The TLDS instruction has a weird encoding for the texture type. if (texture_info <= 1) { return TextureType::Texture1D; @@ -1529,19 +1532,19 @@ union Instruction { return TextureType::Texture3D; } - LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", - static_cast<u32>(texture_info.Value())); + LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", texture_info.Value()); UNREACHABLE(); return TextureType::Texture1D; } - TextureProcessMode GetTextureProcessMode() const { - if (texture_info == 1 || texture_info == 5 || texture_info == 12) + [[nodiscard]] TextureProcessMode GetTextureProcessMode() const { + if (texture_info == 1 || texture_info == 5 || texture_info == 12) { return TextureProcessMode::LL; + } return TextureProcessMode::LZ; } - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::AOFFI: return texture_info == 12 || texture_info == 4; @@ -1555,7 +1558,7 @@ union Instruction { return false; } - bool IsArrayTexture() const { + [[nodiscard]] bool IsArrayTexture() const { // TEXS only supports Texture2D arrays. return texture_info == 8; } @@ -1567,7 +1570,7 @@ union Instruction { BitField<35, 1, u64> aoffi_flag; BitField<49, 1, u64> nodep_flag; - bool UsesMiscMode(TextureMiscMode mode) const { + [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const { switch (mode) { case TextureMiscMode::AOFFI: return aoffi_flag != 0; @@ -1591,7 +1594,7 @@ union Instruction { BitField<20, 3, StoreType> store_data_layout; BitField<20, 4, u64> component_mask_selector; - bool IsComponentEnabled(std::size_t component) const { + [[nodiscard]] bool IsComponentEnabled(std::size_t component) const { ASSERT(mode == SurfaceDataMode::P); constexpr u8 R = 0b0001; constexpr u8 G = 0b0010; @@ -1604,7 +1607,7 @@ union Instruction { return std::bitset<4>{mask.at(component_mask_selector)}.test(component); } - StoreType GetStoreDataLayout() const { + [[nodiscard]] StoreType GetStoreDataLayout() const { ASSERT(mode == SurfaceDataMode::D_BA); return store_data_layout; } @@ -1622,14 +1625,15 @@ union Instruction { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; - s32 GetBranchTarget() const { + [[nodiscard]] s32 GetBranchTarget() const { // Sign extend the branch target offset - u32 mask = 1U << (24 - 1); - u32 value = static_cast<u32>(target); + const auto mask = 1U << (24 - 1); + const auto target_value = static_cast<u32>(target); + constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction)); + // The branch offset is relative to the next instruction and is stored in bytes, so // divide it by the size of an instruction and add 1 to it. - return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) + - 1; + return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1; } } bra; @@ -1637,14 +1641,15 @@ union Instruction { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; - s32 GetBranchExtend() const { + [[nodiscard]] s32 GetBranchExtend() const { // Sign extend the branch target offset - u32 mask = 1U << (24 - 1); - u32 value = static_cast<u32>(target); + const auto mask = 1U << (24 - 1); + const auto target_value = static_cast<u32>(target); + constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction)); + // The branch offset is relative to the next instruction and is stored in bytes, so // divide it by the size of an instruction and add 1 to it. - return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) + - 1; + return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1; } } brx; @@ -1697,7 +1702,7 @@ union Instruction { BitField<50, 1, u64> is_op_b_register; BitField<51, 3, VmnmxOperation> operation; - VmnmxType SourceFormatA() const { + [[nodiscard]] VmnmxType SourceFormatA() const { switch (src_format_a) { case 0b11: return VmnmxType::Bits32; @@ -1708,7 +1713,7 @@ union Instruction { } } - VmnmxType SourceFormatB() const { + [[nodiscard]] VmnmxType SourceFormatB() const { switch (src_format_b) { case 0b11: return VmnmxType::Bits32; @@ -1739,7 +1744,7 @@ union Instruction { BitField<20, 14, u64> shifted_offset; BitField<34, 5, u64> index; - u64 GetOffset() const { + [[nodiscard]] u64 GetOffset() const { return shifted_offset * 4; } } cbuf34; @@ -1748,7 +1753,7 @@ union Instruction { BitField<20, 16, s64> offset; BitField<36, 5, u64> index; - s64 GetOffset() const { + [[nodiscard]] s64 GetOffset() const { return offset; } } cbuf36; @@ -1893,6 +1898,7 @@ public: ICMP_IMM, FCMP_RR, FCMP_RC, + FCMP_IMMR, MUFU, // Multi-Function Operator RRO_C, // Range Reduction Operator RRO_R, @@ -1996,29 +2002,29 @@ public: /// Returns whether an opcode has an execution predicate field or not (ie, whether it can be /// conditionally executed). - static bool IsPredicatedInstruction(Id opcode) { + [[nodiscard]] static bool IsPredicatedInstruction(Id opcode) { // TODO(Subv): Add the rest of unpredicated instructions. return opcode != Id::SSY && opcode != Id::PBK; } class Matcher { public: - constexpr Matcher(const char* const name, u16 mask, u16 expected, Id id, Type type) - : name{name}, mask{mask}, expected{expected}, id{id}, type{type} {} + constexpr Matcher(const char* const name_, u16 mask_, u16 expected_, Id id_, Type type_) + : name{name_}, mask{mask_}, expected{expected_}, id{id_}, type{type_} {} - constexpr const char* GetName() const { + [[nodiscard]] constexpr const char* GetName() const { return name; } - constexpr u16 GetMask() const { + [[nodiscard]] constexpr u16 GetMask() const { return mask; } - constexpr Id GetId() const { + [[nodiscard]] constexpr Id GetId() const { return id; } - constexpr Type GetType() const { + [[nodiscard]] constexpr Type GetType() const { return type; } @@ -2027,7 +2033,7 @@ public: * @param instruction The instruction to test * @returns true if the given instruction matches. */ - constexpr bool Matches(u16 instruction) const { + [[nodiscard]] constexpr bool Matches(u16 instruction) const { return (instruction & mask) == expected; } @@ -2039,7 +2045,8 @@ public: Type type; }; - static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) { + using DecodeResult = std::optional<std::reference_wrapper<const Matcher>>; + [[nodiscard]] static DecodeResult Decode(Instruction instr) { static const auto table{GetDecodeTable()}; const auto matches_instruction = [instr](const auto& matcher) { @@ -2061,7 +2068,7 @@ private: * A '0' in a bitstring indicates that a zero must be present at that bit position. * A '1' in a bitstring indicates that a one must be present at that bit position. */ - static constexpr auto GetMaskAndExpect(const char* const bitstring) { + [[nodiscard]] static constexpr auto GetMaskAndExpect(const char* const bitstring) { u16 mask = 0, expect = 0; for (std::size_t i = 0; i < opcode_bitsize; i++) { const std::size_t bit_position = opcode_bitsize - i - 1; @@ -2083,14 +2090,14 @@ private: public: /// Creates a matcher that can match and parse instructions based on bitstring. - static constexpr auto GetMatcher(const char* const bitstring, Id op, Type type, - const char* const name) { + [[nodiscard]] static constexpr auto GetMatcher(const char* const bitstring, Id op, + Type type, const char* const name) { const auto [mask, expected] = GetMaskAndExpect(bitstring); return Matcher(name, mask, expected, op, type); } }; - static std::vector<Matcher> GetDecodeTable() { + [[nodiscard]] static std::vector<Matcher> GetDecodeTable() { std::vector<Matcher> table = { #define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name) INST("111000110011----", Id::KIL, Type::Flow, "KIL"), @@ -2205,6 +2212,7 @@ private: INST("0111110-0-------", Id::HSET2_IMM, Type::HalfSet, "HSET2_IMM"), INST("010110111010----", Id::FCMP_RR, Type::Arithmetic, "FCMP_RR"), INST("010010111010----", Id::FCMP_RC, Type::Arithmetic, "FCMP_RC"), + INST("0011011-1010----", Id::FCMP_IMMR, Type::Arithmetic, "FCMP_IMMR"), INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h index 72e2a33d5..e0d7b89c5 100644 --- a/src/video_core/engines/shader_header.h +++ b/src/video_core/engines/shader_header.h @@ -41,37 +41,37 @@ struct Header { BitField<26, 1, u32> does_load_or_store; BitField<27, 1, u32> does_fp64; BitField<28, 4, u32> stream_out_mask; - } common0{}; + } common0; union { BitField<0, 24, u32> shader_local_memory_low_size; BitField<24, 8, u32> per_patch_attribute_count; - } common1{}; + } common1; union { BitField<0, 24, u32> shader_local_memory_high_size; BitField<24, 8, u32> threads_per_input_primitive; - } common2{}; + } common2; union { BitField<0, 24, u32> shader_local_memory_crs_size; BitField<24, 4, OutputTopology> output_topology; BitField<28, 4, u32> reserved; - } common3{}; + } common3; union { BitField<0, 12, u32> max_output_vertices; BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders. BitField<20, 4, u32> reserved; BitField<24, 8, u32> store_req_end; // NOTE: not used by geometry shaders. - } common4{}; + } common4; union { struct { - INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA - INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB - INSERT_UNION_PADDING_BYTES(16); // ImapGenericVector[32] - INSERT_UNION_PADDING_BYTES(2); // ImapColor + INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA + INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB + INSERT_PADDING_BYTES_NOINIT(16); // ImapGenericVector[32] + INSERT_PADDING_BYTES_NOINIT(2); // ImapColor union { BitField<0, 8, u16> clip_distances; BitField<8, 1, u16> point_sprite_s; @@ -82,20 +82,20 @@ struct Header { BitField<14, 1, u16> instance_id; BitField<15, 1, u16> vertex_id; }; - INSERT_UNION_PADDING_BYTES(5); // ImapFixedFncTexture[10] - INSERT_UNION_PADDING_BYTES(1); // ImapReserved - INSERT_UNION_PADDING_BYTES(3); // OmapSystemValuesA - INSERT_UNION_PADDING_BYTES(1); // OmapSystemValuesB - INSERT_UNION_PADDING_BYTES(16); // OmapGenericVector[32] - INSERT_UNION_PADDING_BYTES(2); // OmapColor - INSERT_UNION_PADDING_BYTES(2); // OmapSystemValuesC - INSERT_UNION_PADDING_BYTES(5); // OmapFixedFncTexture[10] - INSERT_UNION_PADDING_BYTES(1); // OmapReserved + INSERT_PADDING_BYTES_NOINIT(5); // ImapFixedFncTexture[10] + INSERT_PADDING_BYTES_NOINIT(1); // ImapReserved + INSERT_PADDING_BYTES_NOINIT(3); // OmapSystemValuesA + INSERT_PADDING_BYTES_NOINIT(1); // OmapSystemValuesB + INSERT_PADDING_BYTES_NOINIT(16); // OmapGenericVector[32] + INSERT_PADDING_BYTES_NOINIT(2); // OmapColor + INSERT_PADDING_BYTES_NOINIT(2); // OmapSystemValuesC + INSERT_PADDING_BYTES_NOINIT(5); // OmapFixedFncTexture[10] + INSERT_PADDING_BYTES_NOINIT(1); // OmapReserved } vtg; struct { - INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA - INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB + INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA + INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB union { BitField<0, 2, PixelImap> x; @@ -105,10 +105,10 @@ struct Header { u8 raw; } imap_generic_vector[32]; - INSERT_UNION_PADDING_BYTES(2); // ImapColor - INSERT_UNION_PADDING_BYTES(2); // ImapSystemValuesC - INSERT_UNION_PADDING_BYTES(10); // ImapFixedFncTexture[10] - INSERT_UNION_PADDING_BYTES(2); // ImapReserved + INSERT_PADDING_BYTES_NOINIT(2); // ImapColor + INSERT_PADDING_BYTES_NOINIT(2); // ImapSystemValuesC + INSERT_PADDING_BYTES_NOINIT(10); // ImapFixedFncTexture[10] + INSERT_PADDING_BYTES_NOINIT(2); // ImapReserved struct { u32 target; @@ -145,7 +145,7 @@ struct Header { } } ps; - std::array<u32, 0xF> raw{}; + std::array<u32, 0xF> raw; }; u64 GetLocalMemorySize() const { @@ -153,7 +153,6 @@ struct Header { (common2.shader_local_memory_high_size << 24)); } }; - static_assert(sizeof(Header) == 0x50, "Incorrect structure size"); } // namespace Tegra::Shader diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index 06cc12d5a..f055b61e9 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "core/core.h" +#include "video_core/delayed_destruction_ring.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" @@ -17,11 +18,11 @@ namespace VideoCommon { class FenceBase { public: - FenceBase(u32 payload, bool is_stubbed) - : address{}, payload{payload}, is_semaphore{false}, is_stubbed{is_stubbed} {} + explicit FenceBase(u32 payload_, bool is_stubbed_) + : address{}, payload{payload_}, is_semaphore{false}, is_stubbed{is_stubbed_} {} - FenceBase(GPUVAddr address, u32 payload, bool is_stubbed) - : address{address}, payload{payload}, is_semaphore{true}, is_stubbed{is_stubbed} {} + explicit FenceBase(GPUVAddr address_, u32 payload_, bool is_stubbed_) + : address{address_}, payload{payload_}, is_semaphore{true}, is_stubbed{is_stubbed_} {} GPUVAddr GetAddress() const { return address; @@ -47,6 +48,11 @@ protected: template <typename TFence, typename TTextureCache, typename TTBufferCache, typename TQueryCache> class FenceManager { public: + /// Notify the fence manager about a new frame + void TickFrame() { + delayed_destruction_ring.Tick(); + } + void SignalSemaphore(GPUVAddr addr, u32 value) { TryReleasePendingFences(); const bool should_flush = ShouldFlush(); @@ -74,8 +80,6 @@ public: } void WaitPendingFences() { - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; while (!fences.empty()) { TFence& current_fence = fences.front(); if (ShouldWait()) { @@ -83,23 +87,23 @@ public: } PopAsyncFlushes(); if (current_fence->IsSemaphore()) { - memory_manager.template Write<u32>(current_fence->GetAddress(), - current_fence->GetPayload()); + gpu_memory.template Write<u32>(current_fence->GetAddress(), + current_fence->GetPayload()); } else { gpu.IncrementSyncPoint(current_fence->GetPayload()); } - fences.pop(); + PopFence(); } } protected: - FenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - TTextureCache& texture_cache, TTBufferCache& buffer_cache, - TQueryCache& query_cache) - : system{system}, rasterizer{rasterizer}, texture_cache{texture_cache}, - buffer_cache{buffer_cache}, query_cache{query_cache} {} + explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_, + TTextureCache& texture_cache_, TTBufferCache& buffer_cache_, + TQueryCache& query_cache_) + : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()}, + texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {} - virtual ~FenceManager() {} + virtual ~FenceManager() = default; /// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is /// true @@ -113,16 +117,15 @@ protected: /// Waits until a fence has been signalled by the host GPU. virtual void WaitFence(TFence& fence) = 0; - Core::System& system; VideoCore::RasterizerInterface& rasterizer; + Tegra::GPU& gpu; + Tegra::MemoryManager& gpu_memory; TTextureCache& texture_cache; TTBufferCache& buffer_cache; TQueryCache& query_cache; private: void TryReleasePendingFences() { - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; while (!fences.empty()) { TFence& current_fence = fences.front(); if (ShouldWait() && !IsFenceSignaled(current_fence)) { @@ -130,38 +133,49 @@ private: } PopAsyncFlushes(); if (current_fence->IsSemaphore()) { - memory_manager.template Write<u32>(current_fence->GetAddress(), - current_fence->GetPayload()); + gpu_memory.template Write<u32>(current_fence->GetAddress(), + current_fence->GetPayload()); } else { gpu.IncrementSyncPoint(current_fence->GetPayload()); } - fences.pop(); + PopFence(); } } bool ShouldWait() const { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; return texture_cache.ShouldWaitAsyncFlushes() || buffer_cache.ShouldWaitAsyncFlushes() || query_cache.ShouldWaitAsyncFlushes(); } bool ShouldFlush() const { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; return texture_cache.HasUncommittedFlushes() || buffer_cache.HasUncommittedFlushes() || query_cache.HasUncommittedFlushes(); } void PopAsyncFlushes() { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; texture_cache.PopAsyncFlushes(); buffer_cache.PopAsyncFlushes(); query_cache.PopAsyncFlushes(); } void CommitAsyncFlushes() { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; texture_cache.CommitAsyncFlushes(); buffer_cache.CommitAsyncFlushes(); query_cache.CommitAsyncFlushes(); } + void PopFence() { + delayed_destruction_ring.Push(std::move(fences.front())); + fences.pop(); + } + std::queue<TFence> fences; + + DelayedDestructionRing<TFence, 6> delayed_destruction_ring; }; } // namespace VideoCommon diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h new file mode 100644 index 000000000..b86c3a757 --- /dev/null +++ b/src/video_core/framebuffer_config.h @@ -0,0 +1,31 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Tegra { + +/** + * Struct describing framebuffer configuration + */ +struct FramebufferConfig { + enum class PixelFormat : u32 { + A8B8G8R8_UNORM = 1, + RGB565_UNORM = 4, + B8G8R8A8_UNORM = 5, + }; + + VAddr address{}; + u32 offset{}; + u32 width{}; + u32 height{}; + u32 stride{}; + PixelFormat pixel_format{}; + + using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags; + TransformFlags transform_flags{}; + Common::Rectangle<int> crop_rect; +}; + +} // namespace Tegra diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index acb6e6d46..2a9bd4121 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -10,6 +10,7 @@ #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/frontend/emu_window.h" +#include "core/hardware_interrupt_manager.h" #include "core/memory.h" #include "core/settings.h" #include "video_core/engines/fermi_2d.h" @@ -27,22 +28,24 @@ namespace Tegra { MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192)); -GPU::GPU(Core::System& system_, bool is_async_) - : system{system_}, dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)}, - memory_manager{std::make_unique<Tegra::MemoryManager>(system)}, +GPU::GPU(Core::System& system_, bool is_async_, bool use_nvdec_) + : system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)}, + dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)}, + cdma_pusher{std::make_unique<Tegra::CDmaPusher>(*this)}, use_nvdec{use_nvdec_}, maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)}, fermi_2d{std::make_unique<Engines::Fermi2D>()}, kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)}, maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)}, kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)}, - shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {} + shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_}, + gpu_thread{system_, is_async_} {} GPU::~GPU() = default; void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) { renderer = std::move(renderer_); + rasterizer = renderer->ReadRasterizer(); - VideoCore::RasterizerInterface& rasterizer = renderer->Rasterizer(); memory_manager->BindRasterizer(rasterizer); maxwell_3d->BindRasterizer(rasterizer); fermi_2d->BindRasterizer(rasterizer); @@ -77,31 +80,46 @@ DmaPusher& GPU::DmaPusher() { return *dma_pusher; } +Tegra::CDmaPusher& GPU::CDmaPusher() { + return *cdma_pusher; +} + const DmaPusher& GPU::DmaPusher() const { return *dma_pusher; } +const Tegra::CDmaPusher& GPU::CDmaPusher() const { + return *cdma_pusher; +} + void GPU::WaitFence(u32 syncpoint_id, u32 value) { // Synced GPU, is always in sync if (!is_async) { return; } + if (syncpoint_id == UINT32_MAX) { + // TODO: Research what this does. + LOG_ERROR(HW_GPU, "Waiting for syncpoint -1 not implemented"); + return; + } MICROPROFILE_SCOPE(GPU_wait); std::unique_lock lock{sync_mutex}; - sync_cv.wait(lock, [=, this] { return syncpoints[syncpoint_id].load() >= value; }); + sync_cv.wait(lock, [=, this] { return syncpoints.at(syncpoint_id).load() >= value; }); } void GPU::IncrementSyncPoint(const u32 syncpoint_id) { - syncpoints[syncpoint_id]++; + auto& syncpoint = syncpoints.at(syncpoint_id); + syncpoint++; std::lock_guard lock{sync_mutex}; sync_cv.notify_all(); - if (!syncpt_interrupts[syncpoint_id].empty()) { - u32 value = syncpoints[syncpoint_id].load(); - auto it = syncpt_interrupts[syncpoint_id].begin(); - while (it != syncpt_interrupts[syncpoint_id].end()) { + auto& interrupt = syncpt_interrupts.at(syncpoint_id); + if (!interrupt.empty()) { + u32 value = syncpoint.load(); + auto it = interrupt.begin(); + while (it != interrupt.end()) { if (value >= *it) { TriggerCpuInterrupt(syncpoint_id, *it); - it = syncpt_interrupts[syncpoint_id].erase(it); + it = interrupt.erase(it); continue; } it++; @@ -110,22 +128,22 @@ void GPU::IncrementSyncPoint(const u32 syncpoint_id) { } u32 GPU::GetSyncpointValue(const u32 syncpoint_id) const { - return syncpoints[syncpoint_id].load(); + return syncpoints.at(syncpoint_id).load(); } void GPU::RegisterSyncptInterrupt(const u32 syncpoint_id, const u32 value) { - auto& interrupt = syncpt_interrupts[syncpoint_id]; + auto& interrupt = syncpt_interrupts.at(syncpoint_id); bool contains = std::any_of(interrupt.begin(), interrupt.end(), [value](u32 in_value) { return in_value == value; }); if (contains) { return; } - syncpt_interrupts[syncpoint_id].emplace_back(value); + interrupt.emplace_back(value); } bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) { std::lock_guard lock{sync_mutex}; - auto& interrupt = syncpt_interrupts[syncpoint_id]; + auto& interrupt = syncpt_interrupts.at(syncpoint_id); const auto iter = std::find_if(interrupt.begin(), interrupt.end(), [value](u32 interrupt_value) { return value == interrupt_value; }); @@ -153,7 +171,7 @@ void GPU::TickWork() { const std::size_t size = request.size; flush_requests.pop_front(); flush_request_mutex.unlock(); - renderer->Rasterizer().FlushRegion(addr, size); + rasterizer->FlushRegion(addr, size); current_flush_fence.store(fence); flush_request_mutex.lock(); } @@ -175,41 +193,13 @@ u64 GPU::GetTicks() const { } void GPU::FlushCommands() { - renderer->Rasterizer().FlushCommands(); + rasterizer->FlushCommands(); } void GPU::SyncGuestHost() { - renderer->Rasterizer().SyncGuestHost(); + rasterizer->SyncGuestHost(); } -void GPU::OnCommandListEnd() { - renderer->Rasterizer().ReleaseFences(); -} -// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence -// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4. -// So the values you see in docs might be multiplied by 4. -enum class BufferMethods { - BindObject = 0x0, - Nop = 0x2, - SemaphoreAddressHigh = 0x4, - SemaphoreAddressLow = 0x5, - SemaphoreSequence = 0x6, - SemaphoreTrigger = 0x7, - NotifyIntr = 0x8, - WrcacheFlush = 0x9, - Unk28 = 0xA, - UnkCacheFlush = 0xB, - RefCnt = 0x14, - SemaphoreAcquire = 0x1A, - SemaphoreRelease = 0x1B, - FenceValue = 0x1C, - FenceAction = 0x1D, - Unk78 = 0x1E, - Unk7c = 0x1F, - Yield = 0x20, - NonPullerMethods = 0x40, -}; - enum class GpuSemaphoreOperation { AcquireEqual = 0x1, WriteLong = 0x2, @@ -240,8 +230,12 @@ void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending); } else { for (std::size_t i = 0; i < amount; i++) { - CallPullerMethod( - {method, base_start[i], subchannel, methods_pending - static_cast<u32>(i)}); + CallPullerMethod(MethodCall{ + method, + base_start[i], + subchannel, + methods_pending - static_cast<u32>(i), + }); } } } @@ -268,7 +262,12 @@ void GPU::CallPullerMethod(const MethodCall& method_call) { case BufferMethods::UnkCacheFlush: case BufferMethods::WrcacheFlush: case BufferMethods::FenceValue: + break; case BufferMethods::FenceAction: + ProcessFenceActionMethod(); + break; + case BufferMethods::WaitForInterrupt: + ProcessWaitForInterruptMethod(); break; case BufferMethods::SemaphoreTrigger: { ProcessSemaphoreTriggerMethod(); @@ -298,8 +297,7 @@ void GPU::CallPullerMethod(const MethodCall& method_call) { break; } default: - LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", - static_cast<u32>(method)); + LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method); break; } } @@ -378,10 +376,28 @@ void GPU::ProcessBindMethod(const MethodCall& method_call) { dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel); break; default: - UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", static_cast<u32>(engine_id)); + UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); + } +} + +void GPU::ProcessFenceActionMethod() { + switch (regs.fence_action.op) { + case FenceOperation::Acquire: + WaitFence(regs.fence_action.syncpoint_id, regs.fence_value); + break; + case FenceOperation::Increment: + IncrementSyncPoint(regs.fence_action.syncpoint_id); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value()); } } +void GPU::ProcessWaitForInterruptMethod() { + // TODO(bunnei) ImplementMe + LOG_WARNING(HW_GPU, "(STUBBED) called"); +} + void GPU::ProcessSemaphoreTriggerMethod() { const auto semaphoreOperationMask = 0xF; const auto op = @@ -443,4 +459,75 @@ void GPU::ProcessSemaphoreAcquire() { } } +void GPU::Start() { + gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher, *cdma_pusher); + cpu_context = renderer->GetRenderWindow().CreateSharedContext(); + cpu_context->MakeCurrent(); +} + +void GPU::ObtainContext() { + cpu_context->MakeCurrent(); +} + +void GPU::ReleaseContext() { + cpu_context->DoneCurrent(); +} + +void GPU::PushGPUEntries(Tegra::CommandList&& entries) { + gpu_thread.SubmitList(std::move(entries)); +} + +void GPU::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) { + if (!use_nvdec) { + return; + } + // This condition fires when a video stream ends, clear all intermediary data + if (entries[0].raw == 0xDEADB33F) { + cdma_pusher.reset(); + return; + } + if (!cdma_pusher) { + cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this); + } + + // SubmitCommandBuffer would make the nvdec operations async, this is not currently working + // TODO(ameerj): RE proper async nvdec operation + // gpu_thread.SubmitCommandBuffer(std::move(entries)); + + cdma_pusher->Push(std::move(entries)); + cdma_pusher->DispatchCalls(); +} + +void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + gpu_thread.SwapBuffers(framebuffer); +} + +void GPU::FlushRegion(VAddr addr, u64 size) { + gpu_thread.FlushRegion(addr, size); +} + +void GPU::InvalidateRegion(VAddr addr, u64 size) { + gpu_thread.InvalidateRegion(addr, size); +} + +void GPU::FlushAndInvalidateRegion(VAddr addr, u64 size) { + gpu_thread.FlushAndInvalidateRegion(addr, size); +} + +void GPU::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const { + auto& interrupt_manager = system.InterruptManager(); + interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value); +} + +void GPU::WaitIdle() const { + gpu_thread.WaitIdle(); +} + +void GPU::OnCommandListEnd() { + if (is_async) { + // This command only applies to asynchronous GPU mode + gpu_thread.OnCommandListEnd(); + } +} + } // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index c7d11deb2..b2ee45496 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -13,14 +13,17 @@ #include "common/common_types.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvflinger/buffer_queue.h" +#include "video_core/cdma_pusher.h" #include "video_core/dma_pusher.h" +#include "video_core/framebuffer_config.h" +#include "video_core/gpu_thread.h" using CacheAddr = std::uintptr_t; -inline CacheAddr ToCacheAddr(const void* host_ptr) { +[[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) { return reinterpret_cast<CacheAddr>(host_ptr); } -inline u8* FromCacheAddr(CacheAddr cache_addr) { +[[nodiscard]] inline u8* FromCacheAddr(CacheAddr cache_addr) { return reinterpret_cast<u8*>(cache_addr); } @@ -100,28 +103,6 @@ enum class DepthFormat : u32 { struct CommandListHeader; class DebugContext; -/** - * Struct describing framebuffer configuration - */ -struct FramebufferConfig { - enum class PixelFormat : u32 { - A8B8G8R8_UNORM = 1, - RGB565_UNORM = 4, - B8G8R8A8_UNORM = 5, - }; - - VAddr address; - u32 offset; - u32 width; - u32 height; - u32 stride; - PixelFormat pixel_format; - - using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags; - TransformFlags transform_flags; - Common::Rectangle<int> crop_rect; -}; - namespace Engines { class Fermi2D; class Maxwell3D; @@ -140,7 +121,7 @@ enum class EngineID { class MemoryManager; -class GPU { +class GPU final { public: struct MethodCall { u32 method{}; @@ -148,17 +129,17 @@ public: u32 subchannel{}; u32 method_count{}; - bool IsLastCall() const { + explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0) + : method(method_), argument(argument_), subchannel(subchannel_), + method_count(method_count_) {} + + [[nodiscard]] bool IsLastCall() const { return method_count <= 1; } - - MethodCall(u32 method, u32 argument, u32 subchannel = 0, u32 method_count = 0) - : method(method), argument(argument), subchannel(subchannel), - method_count(method_count) {} }; - explicit GPU(Core::System& system, bool is_async); - virtual ~GPU(); + explicit GPU(Core::System& system_, bool is_async_, bool use_nvdec_); + ~GPU(); /// Binds a renderer to the GPU. void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer); @@ -175,13 +156,13 @@ public: /// Synchronizes CPU writes with Host GPU memory. void SyncGuestHost(); /// Signal the ending of command list. - virtual void OnCommandListEnd(); + void OnCommandListEnd(); /// Request a host GPU memory flush from the CPU. - u64 RequestFlush(VAddr addr, std::size_t size); + [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size); /// Obtains current flush request fence id. - u64 CurrentFlushRequestFence() const { + [[nodiscard]] u64 CurrentFlushRequestFence() const { return current_flush_fence.load(std::memory_order_relaxed); } @@ -189,80 +170,112 @@ public: void TickWork(); /// Returns a reference to the Maxwell3D GPU engine. - Engines::Maxwell3D& Maxwell3D(); + [[nodiscard]] Engines::Maxwell3D& Maxwell3D(); /// Returns a const reference to the Maxwell3D GPU engine. - const Engines::Maxwell3D& Maxwell3D() const; + [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const; /// Returns a reference to the KeplerCompute GPU engine. - Engines::KeplerCompute& KeplerCompute(); + [[nodiscard]] Engines::KeplerCompute& KeplerCompute(); /// Returns a reference to the KeplerCompute GPU engine. - const Engines::KeplerCompute& KeplerCompute() const; + [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const; /// Returns a reference to the GPU memory manager. - Tegra::MemoryManager& MemoryManager(); + [[nodiscard]] Tegra::MemoryManager& MemoryManager(); /// Returns a const reference to the GPU memory manager. - const Tegra::MemoryManager& MemoryManager() const; + [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const; /// Returns a reference to the GPU DMA pusher. - Tegra::DmaPusher& DmaPusher(); + [[nodiscard]] Tegra::DmaPusher& DmaPusher(); + + /// Returns a const reference to the GPU DMA pusher. + [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const; - VideoCore::RendererBase& Renderer() { + /// Returns a reference to the GPU CDMA pusher. + [[nodiscard]] Tegra::CDmaPusher& CDmaPusher(); + + /// Returns a const reference to the GPU CDMA pusher. + [[nodiscard]] const Tegra::CDmaPusher& CDmaPusher() const; + + /// Returns a reference to the underlying renderer. + [[nodiscard]] VideoCore::RendererBase& Renderer() { return *renderer; } - const VideoCore::RendererBase& Renderer() const { + /// Returns a const reference to the underlying renderer. + [[nodiscard]] const VideoCore::RendererBase& Renderer() const { return *renderer; } - VideoCore::ShaderNotify& ShaderNotify() { + /// Returns a reference to the shader notifier. + [[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() { return *shader_notify; } - const VideoCore::ShaderNotify& ShaderNotify() const { + /// Returns a const reference to the shader notifier. + [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const { return *shader_notify; } // Waits for the GPU to finish working - virtual void WaitIdle() const = 0; + void WaitIdle() const; /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame. void WaitFence(u32 syncpoint_id, u32 value); void IncrementSyncPoint(u32 syncpoint_id); - u32 GetSyncpointValue(u32 syncpoint_id) const; + [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const; void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value); - bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); + [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); - u64 GetTicks() const; + [[nodiscard]] u64 GetTicks() const; - std::unique_lock<std::mutex> LockSync() { + [[nodiscard]] std::unique_lock<std::mutex> LockSync() { return std::unique_lock{sync_mutex}; } - bool IsAsync() const { + [[nodiscard]] bool IsAsync() const { return is_async; } - /// Returns a const reference to the GPU DMA pusher. - const Tegra::DmaPusher& DmaPusher() const; + [[nodiscard]] bool UseNvdec() const { + return use_nvdec; + } + + enum class FenceOperation : u32 { + Acquire = 0, + Increment = 1, + }; + + union FenceAction { + u32 raw; + BitField<0, 1, FenceOperation> op; + BitField<8, 24, u32> syncpoint_id; + + [[nodiscard]] static CommandHeader Build(FenceOperation op, u32 syncpoint_id) { + FenceAction result{}; + result.op.Assign(op); + result.syncpoint_id.Assign(syncpoint_id); + return {result.raw}; + } + }; struct Regs { static constexpr size_t NUM_REGS = 0x40; union { struct { - INSERT_UNION_PADDING_WORDS(0x4); + INSERT_PADDING_WORDS_NOINIT(0x4); struct { u32 address_high; u32 address_low; - GPUVAddr SemaphoreAddress() const { + [[nodiscard]] GPUVAddr SemaphoreAddress() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); } @@ -270,21 +283,18 @@ public: u32 semaphore_sequence; u32 semaphore_trigger; - INSERT_UNION_PADDING_WORDS(0xC); + INSERT_PADDING_WORDS_NOINIT(0xC); // The pusher and the puller share the reference counter, the pusher only has read // access u32 reference_count; - INSERT_UNION_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS_NOINIT(0x5); u32 semaphore_acquire; u32 semaphore_release; u32 fence_value; - union { - BitField<4, 4, u32> operation; - BitField<8, 8, u32> id; - } fence_action; - INSERT_UNION_PADDING_WORDS(0xE2); + FenceAction fence_action; + INSERT_PADDING_WORDS_NOINIT(0xE2); // Puller state u32 acquire_mode; @@ -300,34 +310,39 @@ public: /// Performs any additional setup necessary in order to begin GPU emulation. /// This can be used to launch any necessary threads and register any necessary /// core timing events. - virtual void Start() = 0; + void Start(); /// Obtain the CPU Context - virtual void ObtainContext() = 0; + void ObtainContext(); /// Release the CPU Context - virtual void ReleaseContext() = 0; + void ReleaseContext(); /// Push GPU command entries to be processed - virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; + void PushGPUEntries(Tegra::CommandList&& entries); + + /// Push GPU command buffer entries to be processed + void PushCommandBuffer(Tegra::ChCommandHeaderList& entries); /// Swap buffers (render frame) - virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer); /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory - virtual void FlushRegion(VAddr addr, u64 size) = 0; + void FlushRegion(VAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be invalidated - virtual void InvalidateRegion(VAddr addr, u64 size) = 0; + void InvalidateRegion(VAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be flushed and invalidated - virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; + void FlushAndInvalidateRegion(VAddr addr, u64 size); protected: - virtual void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const = 0; + void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const; private: void ProcessBindMethod(const MethodCall& method_call); + void ProcessFenceActionMethod(); + void ProcessWaitForInterruptMethod(); void ProcessSemaphoreTriggerMethod(); void ProcessSemaphoreRelease(); void ProcessSemaphoreAcquire(); @@ -343,16 +358,18 @@ private: u32 methods_pending); /// Determines where the method should be executed. - bool ExecuteMethodOnEngine(u32 method); + [[nodiscard]] bool ExecuteMethodOnEngine(u32 method); protected: Core::System& system; + std::unique_ptr<Tegra::MemoryManager> memory_manager; std::unique_ptr<Tegra::DmaPusher> dma_pusher; + std::unique_ptr<Tegra::CDmaPusher> cdma_pusher; std::unique_ptr<VideoCore::RendererBase> renderer; + VideoCore::RasterizerInterface* rasterizer = nullptr; + const bool use_nvdec; private: - std::unique_ptr<Tegra::MemoryManager> memory_manager; - /// Mapping of command subchannels to their bound engine ids std::array<EngineID, 8> bound_engines = {}; /// 3D engine @@ -373,12 +390,13 @@ private: std::array<std::list<u32>, Service::Nvidia::MaxSyncPoints> syncpt_interrupts; std::mutex sync_mutex; + std::mutex device_mutex; std::condition_variable sync_cv; struct FlushRequest { - FlushRequest(u64 fence, VAddr addr, std::size_t size) - : fence{fence}, addr{addr}, size{size} {} + explicit FlushRequest(u64 fence_, VAddr addr_, std::size_t size_) + : fence{fence_}, addr{addr_}, size{size_} {} u64 fence; VAddr addr; std::size_t size; @@ -390,6 +408,9 @@ private: std::mutex flush_request_mutex; const bool is_async; + + VideoCommon::GPUThread::ThreadManager gpu_thread; + std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context; }; #define ASSERT_REG_POSITION(field_name, position) \ diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp deleted file mode 100644 index 70a3d5738..000000000 --- a/src/video_core/gpu_asynch.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/core.h" -#include "core/hardware_interrupt_manager.h" -#include "video_core/gpu_asynch.h" -#include "video_core/gpu_thread.h" -#include "video_core/renderer_base.h" - -namespace VideoCommon { - -GPUAsynch::GPUAsynch(Core::System& system) : GPU{system, true}, gpu_thread{system} {} - -GPUAsynch::~GPUAsynch() = default; - -void GPUAsynch::Start() { - gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher); - cpu_context = renderer->GetRenderWindow().CreateSharedContext(); - cpu_context->MakeCurrent(); -} - -void GPUAsynch::ObtainContext() { - cpu_context->MakeCurrent(); -} - -void GPUAsynch::ReleaseContext() { - cpu_context->DoneCurrent(); -} - -void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) { - gpu_thread.SubmitList(std::move(entries)); -} - -void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { - gpu_thread.SwapBuffers(framebuffer); -} - -void GPUAsynch::FlushRegion(VAddr addr, u64 size) { - gpu_thread.FlushRegion(addr, size); -} - -void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) { - gpu_thread.InvalidateRegion(addr, size); -} - -void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) { - gpu_thread.FlushAndInvalidateRegion(addr, size); -} - -void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const { - auto& interrupt_manager = system.InterruptManager(); - interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value); -} - -void GPUAsynch::WaitIdle() const { - gpu_thread.WaitIdle(); -} - -void GPUAsynch::OnCommandListEnd() { - gpu_thread.OnCommandListEnd(); -} - -} // namespace VideoCommon diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h deleted file mode 100644 index f89c855a5..000000000 --- a/src/video_core/gpu_asynch.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "video_core/gpu.h" -#include "video_core/gpu_thread.h" - -namespace Core::Frontend { -class GraphicsContext; -} - -namespace VideoCore { -class RendererBase; -} // namespace VideoCore - -namespace VideoCommon { - -/// Implementation of GPU interface that runs the GPU asynchronously -class GPUAsynch final : public Tegra::GPU { -public: - explicit GPUAsynch(Core::System& system); - ~GPUAsynch() override; - - void Start() override; - void ObtainContext() override; - void ReleaseContext() override; - void PushGPUEntries(Tegra::CommandList&& entries) override; - void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - void FlushRegion(VAddr addr, u64 size) override; - void InvalidateRegion(VAddr addr, u64 size) override; - void FlushAndInvalidateRegion(VAddr addr, u64 size) override; - void WaitIdle() const override; - - void OnCommandListEnd() override; - -protected: - void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override; - -private: - GPUThread::ThreadManager gpu_thread; - std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context; -}; - -} // namespace VideoCommon diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp deleted file mode 100644 index 1ca47ddef..000000000 --- a/src/video_core/gpu_synch.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "video_core/gpu_synch.h" -#include "video_core/renderer_base.h" - -namespace VideoCommon { - -GPUSynch::GPUSynch(Core::System& system) : GPU{system, false} {} - -GPUSynch::~GPUSynch() = default; - -void GPUSynch::Start() {} - -void GPUSynch::ObtainContext() { - renderer->Context().MakeCurrent(); -} - -void GPUSynch::ReleaseContext() { - renderer->Context().DoneCurrent(); -} - -void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) { - dma_pusher->Push(std::move(entries)); - dma_pusher->DispatchCalls(); -} - -void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { - renderer->SwapBuffers(framebuffer); -} - -void GPUSynch::FlushRegion(VAddr addr, u64 size) { - renderer->Rasterizer().FlushRegion(addr, size); -} - -void GPUSynch::InvalidateRegion(VAddr addr, u64 size) { - renderer->Rasterizer().InvalidateRegion(addr, size); -} - -void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) { - renderer->Rasterizer().FlushAndInvalidateRegion(addr, size); -} - -} // namespace VideoCommon diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h deleted file mode 100644 index 297258cb1..000000000 --- a/src/video_core/gpu_synch.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "video_core/gpu.h" - -namespace Core::Frontend { -class GraphicsContext; -} - -namespace VideoCore { -class RendererBase; -} // namespace VideoCore - -namespace VideoCommon { - -/// Implementation of GPU interface that runs the GPU synchronously -class GPUSynch final : public Tegra::GPU { -public: - explicit GPUSynch(Core::System& system); - ~GPUSynch() override; - - void Start() override; - void ObtainContext() override; - void ReleaseContext() override; - void PushGPUEntries(Tegra::CommandList&& entries) override; - void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - void FlushRegion(VAddr addr, u64 size) override; - void InvalidateRegion(VAddr addr, u64 size) override; - void FlushAndInvalidateRegion(VAddr addr, u64 size) override; - void WaitIdle() const override {} - -protected: - void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id, - [[maybe_unused]] u32 value) const override {} -}; - -} // namespace VideoCommon diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index bf761abf2..50319f1d5 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -4,6 +4,7 @@ #include "common/assert.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "common/thread.h" #include "core/core.h" #include "core/frontend/emu_window.h" @@ -18,9 +19,11 @@ namespace VideoCommon::GPUThread { /// Runs the GPU thread static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher, - SynchState& state) { + SynchState& state, Tegra::CDmaPusher& cdma_pusher) { std::string name = "yuzu:GPU"; MicroProfileOnThreadCreate(name.c_str()); + SCOPE_EXIT({ MicroProfileOnThreadExit(); }); + Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); system.RegisterHostThread(); @@ -35,23 +38,28 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, } auto current_context = context.Acquire(); + VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer(); CommandDataContainer next; while (state.is_running) { next = state.queue.PopWait(); - if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) { + if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) { dma_pusher.Push(std::move(submit_list->entries)); dma_pusher.DispatchCalls(); - } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { + } else if (auto* command_list = std::get_if<SubmitChCommandEntries>(&next.data)) { + // NVDEC + cdma_pusher.Push(std::move(command_list->entries)); + cdma_pusher.DispatchCalls(); + } else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) { renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr); } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) { - renderer.Rasterizer().ReleaseFences(); + rasterizer->ReleaseFences(); } else if (std::holds_alternative<GPUTickCommand>(next.data)) { system.GPU().TickWork(); - } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { - renderer.Rasterizer().FlushRegion(data->addr, data->size); - } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { - renderer.Rasterizer().OnCPUWrite(data->addr, data->size); + } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) { + rasterizer->FlushRegion(flush->addr, flush->size); + } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { + rasterizer->OnCPUWrite(invalidate->addr, invalidate->size); } else if (std::holds_alternative<EndProcessingCommand>(next.data)) { return; } else { @@ -61,7 +69,8 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, } } -ThreadManager::ThreadManager(Core::System& system) : system{system} {} +ThreadManager::ThreadManager(Core::System& system_, bool is_async_) + : system{system_}, is_async{is_async_} {} ThreadManager::~ThreadManager() { if (!thread.joinable()) { @@ -75,47 +84,64 @@ ThreadManager::~ThreadManager() { void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, - Tegra::DmaPusher& dma_pusher) { - thread = std::thread{RunThread, std::ref(system), std::ref(renderer), - std::ref(context), std::ref(dma_pusher), std::ref(state)}; + Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher) { + rasterizer = renderer.ReadRasterizer(); + thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), + std::ref(dma_pusher), std::ref(state), std::ref(cdma_pusher)); } void ThreadManager::SubmitList(Tegra::CommandList&& entries) { PushCommand(SubmitListCommand(std::move(entries))); } +void ThreadManager::SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries) { + PushCommand(SubmitChCommandEntries(std::move(entries))); +} + void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { PushCommand(SwapBuffersCommand(framebuffer ? std::make_optional(*framebuffer) : std::nullopt)); } void ThreadManager::FlushRegion(VAddr addr, u64 size) { - if (!Settings::IsGPULevelHigh()) { + if (!is_async) { + // Always flush with synchronous GPU mode PushCommand(FlushRegionCommand(addr, size)); return; } - if (!Settings::IsGPULevelExtreme()) { - return; - } - if (system.Renderer().Rasterizer().MustFlushRegion(addr, size)) { + + // Asynchronous GPU mode + switch (Settings::values.gpu_accuracy.GetValue()) { + case Settings::GPUAccuracy::Normal: + PushCommand(FlushRegionCommand(addr, size)); + break; + case Settings::GPUAccuracy::High: + // TODO(bunnei): Is this right? Preserving existing behavior for now + break; + case Settings::GPUAccuracy::Extreme: { auto& gpu = system.GPU(); u64 fence = gpu.RequestFlush(addr, size); PushCommand(GPUTickCommand()); while (fence > gpu.CurrentFlushRequestFence()) { } + break; + } + default: + UNIMPLEMENTED_MSG("Unsupported gpu_accuracy {}", Settings::values.gpu_accuracy.GetValue()); } } void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { - system.Renderer().Rasterizer().OnCPUWrite(addr, size); + rasterizer->OnCPUWrite(addr, size); } void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important - system.Renderer().Rasterizer().OnCPUWrite(addr, size); + rasterizer->OnCPUWrite(addr, size); } void ThreadManager::WaitIdle() const { - while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed)) { + while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed) && + system.IsPoweredOn()) { } } @@ -126,6 +152,12 @@ void ThreadManager::OnCommandListEnd() { u64 ThreadManager::PushCommand(CommandData&& command_data) { const u64 fence{++state.last_fence}; state.queue.Push(CommandDataContainer(std::move(command_data), fence)); + + if (!is_async) { + // In synchronous GPU mode, block the caller until the command has executed + WaitIdle(); + } + return fence; } diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 5a28335d6..4cd951169 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -10,8 +10,9 @@ #include <optional> #include <thread> #include <variant> + #include "common/threadsafe_queue.h" -#include "video_core/gpu.h" +#include "video_core/framebuffer_config.h" namespace Tegra { struct FramebufferConfig; @@ -25,6 +26,11 @@ class GraphicsContext; class System; } // namespace Core +namespace VideoCore { +class RasterizerInterface; +class RendererBase; +} // namespace VideoCore + namespace VideoCommon::GPUThread { /// Command to signal to the GPU thread that processing has ended @@ -32,22 +38,30 @@ struct EndProcessingCommand final {}; /// Command to signal to the GPU thread that a command list is ready for processing struct SubmitListCommand final { - explicit SubmitListCommand(Tegra::CommandList&& entries) : entries{std::move(entries)} {} + explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {} Tegra::CommandList entries; }; +/// Command to signal to the GPU thread that a cdma command list is ready for processing +struct SubmitChCommandEntries final { + explicit SubmitChCommandEntries(Tegra::ChCommandHeaderList&& entries_) + : entries{std::move(entries_)} {} + + Tegra::ChCommandHeaderList entries; +}; + /// Command to signal to the GPU thread that a swap buffers is pending struct SwapBuffersCommand final { - explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer) - : framebuffer{std::move(framebuffer)} {} + explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer_) + : framebuffer{std::move(framebuffer_)} {} std::optional<Tegra::FramebufferConfig> framebuffer; }; /// Command to signal to the GPU thread to flush a region struct FlushRegionCommand final { - explicit constexpr FlushRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {} + explicit constexpr FlushRegionCommand(VAddr addr_, u64 size_) : addr{addr_}, size{size_} {} VAddr addr; u64 size; @@ -55,7 +69,7 @@ struct FlushRegionCommand final { /// Command to signal to the GPU thread to invalidate a region struct InvalidateRegionCommand final { - explicit constexpr InvalidateRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {} + explicit constexpr InvalidateRegionCommand(VAddr addr_, u64 size_) : addr{addr_}, size{size_} {} VAddr addr; u64 size; @@ -63,8 +77,8 @@ struct InvalidateRegionCommand final { /// Command to signal to the GPU thread to flush and invalidate a region struct FlushAndInvalidateRegionCommand final { - explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size) - : addr{addr}, size{size} {} + explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr_, u64 size_) + : addr{addr_}, size{size_} {} VAddr addr; u64 size; @@ -77,15 +91,15 @@ struct OnCommandListEndCommand final {}; struct GPUTickCommand final {}; using CommandData = - std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand, - InvalidateRegionCommand, FlushAndInvalidateRegionCommand, OnCommandListEndCommand, - GPUTickCommand>; + std::variant<EndProcessingCommand, SubmitListCommand, SubmitChCommandEntries, + SwapBuffersCommand, FlushRegionCommand, InvalidateRegionCommand, + FlushAndInvalidateRegionCommand, OnCommandListEndCommand, GPUTickCommand>; struct CommandDataContainer { CommandDataContainer() = default; - CommandDataContainer(CommandData&& data, u64 next_fence) - : data{std::move(data)}, fence{next_fence} {} + explicit CommandDataContainer(CommandData&& data_, u64 next_fence_) + : data{std::move(data_)}, fence{next_fence_} {} CommandData data; u64 fence{}; @@ -104,16 +118,19 @@ struct SynchState final { /// Class used to manage the GPU thread class ThreadManager final { public: - explicit ThreadManager(Core::System& system); + explicit ThreadManager(Core::System& system_, bool is_async_); ~ThreadManager(); /// Creates and starts the GPU thread. void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, - Tegra::DmaPusher& dma_pusher); + Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher); /// Push GPU command entries to be processed void SubmitList(Tegra::CommandList&& entries); + /// Push GPU CDMA command buffer entries to be processed + void SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries); + /// Swap buffers (render frame) void SwapBuffers(const Tegra::FramebufferConfig* framebuffer); @@ -135,11 +152,12 @@ private: /// Pushes a command to be executed by the GPU thread u64 PushCommand(CommandData&& command_data); -private: - SynchState state; Core::System& system; + const bool is_async; + VideoCore::RasterizerInterface* rasterizer = nullptr; + + SynchState state; std::thread thread; - std::thread::id thread_id; }; } // namespace VideoCommon::GPUThread diff --git a/src/video_core/guest_driver.h b/src/video_core/guest_driver.h index 99450777e..21e569ba1 100644 --- a/src/video_core/guest_driver.h +++ b/src/video_core/guest_driver.h @@ -19,8 +19,8 @@ namespace VideoCore { class GuestDriverProfile { public: explicit GuestDriverProfile() = default; - explicit GuestDriverProfile(std::optional<u32> texture_handler_size) - : texture_handler_size{texture_handler_size} {} + explicit GuestDriverProfile(std::optional<u32> texture_handler_size_) + : texture_handler_size{texture_handler_size_} {} void DeduceTextureHandlerSize(std::vector<u32> bound_offsets); diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index aa62363a7..970120acc 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -1,38 +1,83 @@ set(SHADER_FILES + block_linear_unswizzle_2d.comp + block_linear_unswizzle_3d.comp + convert_depth_to_float.frag + convert_float_to_depth.frag + full_screen_triangle.vert + opengl_copy_bc4.comp opengl_present.frag opengl_present.vert + pitch_unswizzle.comp + vulkan_blit_color_float.frag + vulkan_blit_depth_stencil.frag + vulkan_present.frag + vulkan_present.vert + vulkan_quad_indexed.comp + vulkan_uint8.comp ) +find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED) + +set(GLSL_FLAGS "") +set(QUIET_FLAG "--quiet") + set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) +set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders) set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE) -set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders) -add_custom_command( - OUTPUT - ${SHADER_DIR} +set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in) +set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake) + +# Check if `--quiet` is available on host's glslangValidator version +# glslangValidator prints to STDERR iff an unrecognized flag is passed to it +execute_process( COMMAND - ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} + ${GLSLANGVALIDATOR} ${QUIET_FLAG} + ERROR_VARIABLE + GLSLANG_ERROR + # STDOUT variable defined to silence unnecessary output during CMake configuration + OUTPUT_VARIABLE + GLSLANG_OUTPUT ) -set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in) -set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake) +if (NOT GLSLANG_ERROR STREQUAL "") + message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANGVALIDATOR}`") + set(QUIET_FLAG "") +endif() foreach(FILENAME IN ITEMS ${SHADER_FILES}) string(REPLACE "." "_" SHADER_NAME ${FILENAME}) set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}) - set(HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h) - add_custom_command( - OUTPUT - ${HEADER_FILE} - COMMAND - ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${HEADER_FILE} ${INPUT_FILE} - MAIN_DEPENDENCY - ${SOURCE_FILE} - DEPENDS - ${HEADER_GENERATOR} - ${INPUT_FILE} - ) - set(SHADER_HEADERS ${SHADER_HEADERS} ${HEADER_FILE}) + # Skip generating source headers on Vulkan exclusive files + if (NOT ${FILENAME} MATCHES "vulkan.*") + set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h) + add_custom_command( + OUTPUT + ${SOURCE_HEADER_FILE} + COMMAND + ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE} + MAIN_DEPENDENCY + ${SOURCE_FILE} + DEPENDS + ${INPUT_FILE} + # HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified + ) + set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE}) + endif() + # Skip compiling to SPIR-V OpenGL exclusive files + if (NOT ${FILENAME} MATCHES "opengl.*") + string(TOUPPER ${SHADER_NAME}_SPV SPIRV_VARIABLE_NAME) + set(SPIRV_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}_spv.h) + add_custom_command( + OUTPUT + ${SPIRV_HEADER_FILE} + COMMAND + ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} + MAIN_DEPENDENCY + ${SOURCE_FILE} + ) + set(SHADER_HEADERS ${SHADER_HEADERS} ${SPIRV_HEADER_FILE}) + endif() endforeach() add_custom_target(host_shaders diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake index 368bce0ed..c0fc49768 100644 --- a/src/video_core/host_shaders/StringShaderHeader.cmake +++ b/src/video_core/host_shaders/StringShaderHeader.cmake @@ -8,4 +8,6 @@ string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME) file(READ ${SOURCE_FILE} CONTENTS) +get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY) +make_directory(${OUTPUT_DIR}) configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY) diff --git a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp new file mode 100644 index 000000000..a131be79e --- /dev/null +++ b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp @@ -0,0 +1,122 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 430 + +#ifdef VULKAN + +#extension GL_EXT_shader_16bit_storage : require +#extension GL_EXT_shader_8bit_storage : require +#define HAS_EXTENDED_TYPES 1 +#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { +#define END_PUSH_CONSTANTS }; +#define UNIFORM(n) +#define BINDING_SWIZZLE_BUFFER 0 +#define BINDING_INPUT_BUFFER 1 +#define BINDING_OUTPUT_IMAGE 2 + +#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv + +#extension GL_NV_gpu_shader5 : enable +#ifdef GL_NV_gpu_shader5 +#define HAS_EXTENDED_TYPES 1 +#else +#define HAS_EXTENDED_TYPES 0 +#endif +#define BEGIN_PUSH_CONSTANTS +#define END_PUSH_CONSTANTS +#define UNIFORM(n) layout (location = n) uniform +#define BINDING_SWIZZLE_BUFFER 0 +#define BINDING_INPUT_BUFFER 1 +#define BINDING_OUTPUT_IMAGE 0 + +#endif + +BEGIN_PUSH_CONSTANTS +UNIFORM(0) uvec3 origin; +UNIFORM(1) ivec3 destination; +UNIFORM(2) uint bytes_per_block_log2; +UNIFORM(3) uint layer_stride; +UNIFORM(4) uint block_size; +UNIFORM(5) uint x_shift; +UNIFORM(6) uint block_height; +UNIFORM(7) uint block_height_mask; +END_PUSH_CONSTANTS + +layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { + uint swizzle_table[]; +}; + +#if HAS_EXTENDED_TYPES +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; +#endif +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; }; + +layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly uimage2DArray output_image; + +layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +const uint GOB_SIZE_X = 64; +const uint GOB_SIZE_Y = 8; +const uint GOB_SIZE_Z = 1; +const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z; + +const uint GOB_SIZE_X_SHIFT = 6; +const uint GOB_SIZE_Y_SHIFT = 3; +const uint GOB_SIZE_Z_SHIFT = 0; +const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; + +const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); + +uint SwizzleOffset(uvec2 pos) { + pos = pos & SWIZZLE_MASK; + return swizzle_table[pos.y * 64 + pos.x]; +} + +uvec4 ReadTexel(uint offset) { + switch (bytes_per_block_log2) { +#if HAS_EXTENDED_TYPES + case 0: + return uvec4(u8data[offset], 0, 0, 0); + case 1: + return uvec4(u16data[offset / 2], 0, 0, 0); +#else + case 0: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0); + case 1: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0); +#endif + case 2: + return uvec4(u32data[offset / 4], 0, 0, 0); + case 3: + return uvec4(u64data[offset / 8], 0, 0); + case 4: + return u128data[offset / 16]; + } + return uvec4(0); +} + +void main() { + uvec3 pos = gl_GlobalInvocationID + origin; + pos.x <<= bytes_per_block_log2; + + // Read as soon as possible due to its latency + const uint swizzle = SwizzleOffset(pos.xy); + + const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; + + uint offset = 0; + offset += pos.z * layer_stride; + offset += (block_y >> block_height) * block_size; + offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT; + offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift; + offset += swizzle; + + const uvec4 texel = ReadTexel(offset); + const ivec3 coord = ivec3(gl_GlobalInvocationID) + destination; + imageStore(output_image, coord, texel); +} diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp new file mode 100644 index 000000000..bb6872e6b --- /dev/null +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp @@ -0,0 +1,125 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 430 + +#ifdef VULKAN + +#extension GL_EXT_shader_16bit_storage : require +#extension GL_EXT_shader_8bit_storage : require +#define HAS_EXTENDED_TYPES 1 +#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { +#define END_PUSH_CONSTANTS }; +#define UNIFORM(n) +#define BINDING_SWIZZLE_BUFFER 0 +#define BINDING_INPUT_BUFFER 1 +#define BINDING_OUTPUT_IMAGE 2 + +#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv + +#extension GL_NV_gpu_shader5 : enable +#ifdef GL_NV_gpu_shader5 +#define HAS_EXTENDED_TYPES 1 +#else +#define HAS_EXTENDED_TYPES 0 +#endif +#define BEGIN_PUSH_CONSTANTS +#define END_PUSH_CONSTANTS +#define UNIFORM(n) layout (location = n) uniform +#define BINDING_SWIZZLE_BUFFER 0 +#define BINDING_INPUT_BUFFER 1 +#define BINDING_OUTPUT_IMAGE 0 + +#endif + +BEGIN_PUSH_CONSTANTS +UNIFORM(0) uvec3 origin; +UNIFORM(1) ivec3 destination; +UNIFORM(2) uint bytes_per_block_log2; +UNIFORM(3) uint slice_size; +UNIFORM(4) uint block_size; +UNIFORM(5) uint x_shift; +UNIFORM(6) uint block_height; +UNIFORM(7) uint block_height_mask; +UNIFORM(8) uint block_depth; +UNIFORM(9) uint block_depth_mask; +END_PUSH_CONSTANTS + +layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { + uint swizzle_table[]; +}; + +#if HAS_EXTENDED_TYPES +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; +#endif +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; }; + +layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly uimage3D output_image; + +layout(local_size_x = 16, local_size_y = 8, local_size_z = 8) in; + +const uint GOB_SIZE_X = 64; +const uint GOB_SIZE_Y = 8; +const uint GOB_SIZE_Z = 1; +const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z; + +const uint GOB_SIZE_X_SHIFT = 6; +const uint GOB_SIZE_Y_SHIFT = 3; +const uint GOB_SIZE_Z_SHIFT = 0; +const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; + +const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); + +uint SwizzleOffset(uvec2 pos) { + pos = pos & SWIZZLE_MASK; + return swizzle_table[pos.y * 64 + pos.x]; +} + +uvec4 ReadTexel(uint offset) { + switch (bytes_per_block_log2) { +#if HAS_EXTENDED_TYPES + case 0: + return uvec4(u8data[offset], 0, 0, 0); + case 1: + return uvec4(u16data[offset / 2], 0, 0, 0); +#else + case 0: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0); + case 1: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0); +#endif + case 2: + return uvec4(u32data[offset / 4], 0, 0, 0); + case 3: + return uvec4(u64data[offset / 8], 0, 0); + case 4: + return u128data[offset / 16]; + } + return uvec4(0); +} + +void main() { + uvec3 pos = gl_GlobalInvocationID + origin; + pos.x <<= bytes_per_block_log2; + + // Read as soon as possible due to its latency + const uint swizzle = SwizzleOffset(pos.xy); + + const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; + + uint offset = 0; + offset += (pos.z >> block_depth) * slice_size; + offset += (pos.z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height); + offset += (block_y >> block_height) * block_size; + offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT; + offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift; + offset += swizzle; + + const uvec4 texel = ReadTexel(offset); + const ivec3 coord = ivec3(gl_GlobalInvocationID) + destination; + imageStore(output_image, coord, texel); +} diff --git a/src/video_core/host_shaders/convert_depth_to_float.frag b/src/video_core/host_shaders/convert_depth_to_float.frag new file mode 100644 index 000000000..624c58509 --- /dev/null +++ b/src/video_core/host_shaders/convert_depth_to_float.frag @@ -0,0 +1,13 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 450 + +layout(binding = 0) uniform sampler2D depth_texture; +layout(location = 0) out float output_color; + +void main() { + ivec2 coord = ivec2(gl_FragCoord.xy); + output_color = texelFetch(depth_texture, coord, 0).r; +} diff --git a/src/video_core/host_shaders/convert_float_to_depth.frag b/src/video_core/host_shaders/convert_float_to_depth.frag new file mode 100644 index 000000000..d86c795f4 --- /dev/null +++ b/src/video_core/host_shaders/convert_float_to_depth.frag @@ -0,0 +1,13 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 450 + +layout(binding = 0) uniform sampler2D color_texture; + +void main() { + ivec2 coord = ivec2(gl_FragCoord.xy); + float color = texelFetch(color_texture, coord, 0).r; + gl_FragDepth = color; +} diff --git a/src/video_core/host_shaders/full_screen_triangle.vert b/src/video_core/host_shaders/full_screen_triangle.vert new file mode 100644 index 000000000..452ad6502 --- /dev/null +++ b/src/video_core/host_shaders/full_screen_triangle.vert @@ -0,0 +1,29 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 450 + +#ifdef VULKAN +#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { +#define END_PUSH_CONSTANTS }; +#define UNIFORM(n) +#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv +#define BEGIN_PUSH_CONSTANTS +#define END_PUSH_CONSTANTS +#define UNIFORM(n) layout (location = n) uniform +#endif + +BEGIN_PUSH_CONSTANTS +UNIFORM(0) vec2 tex_scale; +UNIFORM(1) vec2 tex_offset; +END_PUSH_CONSTANTS + +layout(location = 0) out vec2 texcoord; + +void main() { + float x = float((gl_VertexIndex & 1) << 2); + float y = float((gl_VertexIndex & 2) << 1); + gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0); + texcoord = fma(vec2(x, y) / 2.0, tex_scale, tex_offset); +} diff --git a/src/video_core/host_shaders/opengl_copy_bc4.comp b/src/video_core/host_shaders/opengl_copy_bc4.comp new file mode 100644 index 000000000..7b8e20fbe --- /dev/null +++ b/src/video_core/host_shaders/opengl_copy_bc4.comp @@ -0,0 +1,70 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 430 core +#extension GL_ARB_gpu_shader_int64 : require + +layout (local_size_x = 4, local_size_y = 4) in; + +layout(binding = 0, rg32ui) readonly uniform uimage3D bc4_input; +layout(binding = 1, rgba8ui) writeonly uniform uimage3D bc4_output; + +layout(location = 0) uniform uvec3 src_offset; +layout(location = 1) uniform uvec3 dst_offset; + +// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_rgtc.txt +uint DecompressBlock(uint64_t bits, uvec2 coord) { + const uint code_offset = 16 + 3 * (4 * coord.y + coord.x); + const uint code = uint(bits >> code_offset) & 7; + const uint red0 = uint(bits >> 0) & 0xff; + const uint red1 = uint(bits >> 8) & 0xff; + if (red0 > red1) { + switch (code) { + case 0: + return red0; + case 1: + return red1; + case 2: + return (6 * red0 + 1 * red1) / 7; + case 3: + return (5 * red0 + 2 * red1) / 7; + case 4: + return (4 * red0 + 3 * red1) / 7; + case 5: + return (3 * red0 + 4 * red1) / 7; + case 6: + return (2 * red0 + 5 * red1) / 7; + case 7: + return (1 * red0 + 6 * red1) / 7; + } + } else { + switch (code) { + case 0: + return red0; + case 1: + return red1; + case 2: + return (4 * red0 + 1 * red1) / 5; + case 3: + return (3 * red0 + 2 * red1) / 5; + case 4: + return (2 * red0 + 3 * red1) / 5; + case 5: + return (1 * red0 + 4 * red1) / 5; + case 6: + return 0; + case 7: + return 0xff; + } + } + return 0; +} + +void main() { + uvec2 packed_bits = imageLoad(bc4_input, ivec3(gl_WorkGroupID + src_offset)).rg; + uint64_t bits = packUint2x32(packed_bits); + uint red = DecompressBlock(bits, gl_LocalInvocationID.xy); + uvec4 color = uvec4(red & 0xff, 0, 0, 0xff); + imageStore(bc4_output, ivec3(gl_GlobalInvocationID + dst_offset), color); +} diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag index 8a4cb024b..84b818227 100644 --- a/src/video_core/host_shaders/opengl_present.frag +++ b/src/video_core/host_shaders/opengl_present.frag @@ -1,3 +1,7 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #version 430 core layout (location = 0) in vec2 frag_tex_coord; diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert index 2235d31a4..c3b5adbba 100644 --- a/src/video_core/host_shaders/opengl_present.vert +++ b/src/video_core/host_shaders/opengl_present.vert @@ -1,3 +1,7 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #version 430 core out gl_PerVertex { diff --git a/src/video_core/host_shaders/pitch_unswizzle.comp b/src/video_core/host_shaders/pitch_unswizzle.comp new file mode 100644 index 000000000..cb48ec170 --- /dev/null +++ b/src/video_core/host_shaders/pitch_unswizzle.comp @@ -0,0 +1,86 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 430 + +#ifdef VULKAN + +#extension GL_EXT_shader_16bit_storage : require +#extension GL_EXT_shader_8bit_storage : require +#define HAS_EXTENDED_TYPES 1 +#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { +#define END_PUSH_CONSTANTS }; +#define UNIFORM(n) +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 1 + +#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv + +#extension GL_NV_gpu_shader5 : enable +#ifdef GL_NV_gpu_shader5 +#define HAS_EXTENDED_TYPES 1 +#else +#define HAS_EXTENDED_TYPES 0 +#endif +#define BEGIN_PUSH_CONSTANTS +#define END_PUSH_CONSTANTS +#define UNIFORM(n) layout (location = n) uniform +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 0 + +#endif + +BEGIN_PUSH_CONSTANTS +UNIFORM(0) uvec2 origin; +UNIFORM(1) ivec2 destination; +UNIFORM(2) uint bytes_per_block; +UNIFORM(3) uint pitch; +END_PUSH_CONSTANTS + +#if HAS_EXTENDED_TYPES +layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU8 { uint8_t u8data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU16 { uint16_t u16data[]; }; +#endif +layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 { uint u32data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU64 { uvec2 u64data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU128 { uvec4 u128data[]; }; + +layout(binding = BINDING_OUTPUT_IMAGE) writeonly uniform uimage2D output_image; + +layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +uvec4 ReadTexel(uint offset) { + switch (bytes_per_block) { +#if HAS_EXTENDED_TYPES + case 1: + return uvec4(u8data[offset], 0, 0, 0); + case 2: + return uvec4(u16data[offset / 2], 0, 0, 0); +#else + case 1: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0); + case 2: + return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0); +#endif + case 4: + return uvec4(u32data[offset / 4], 0, 0, 0); + case 8: + return uvec4(u64data[offset / 8], 0, 0); + case 16: + return u128data[offset / 16]; + } + return uvec4(0); +} + +void main() { + uvec2 pos = gl_GlobalInvocationID.xy + origin; + + uint offset = 0; + offset += pos.x * bytes_per_block; + offset += pos.y * pitch; + + const uvec4 texel = ReadTexel(offset); + const ivec2 coord = ivec2(gl_GlobalInvocationID.xy) + destination; + imageStore(output_image, coord, texel); +} diff --git a/src/video_core/host_shaders/vulkan_blit_color_float.frag b/src/video_core/host_shaders/vulkan_blit_color_float.frag new file mode 100644 index 000000000..4a6aae410 --- /dev/null +++ b/src/video_core/host_shaders/vulkan_blit_color_float.frag @@ -0,0 +1,14 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 450 + +layout(binding = 0) uniform sampler2D tex; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out vec4 color; + +void main() { + color = textureLod(tex, texcoord, 0); +} diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag new file mode 100644 index 000000000..19bb23a5a --- /dev/null +++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag @@ -0,0 +1,16 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#version 450 +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2D depth_tex; +layout(binding = 1) uniform isampler2D stencil_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = textureLod(depth_tex, texcoord, 0).r; + gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r; +} diff --git a/src/video_core/renderer_vulkan/shaders/blit.frag b/src/video_core/host_shaders/vulkan_present.frag index a06ecd24a..0979ff3e6 100644 --- a/src/video_core/renderer_vulkan/shaders/blit.frag +++ b/src/video_core/host_shaders/vulkan_present.frag @@ -2,15 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -/* - * Build instructions: - * $ glslangValidator -V $THIS_FILE -o output.spv - * $ spirv-opt -O --strip-debug output.spv -o optimized.spv - * $ xxd -i optimized.spv - * - * Then copy that bytecode to the C++ file - */ - #version 460 core layout (location = 0) in vec2 frag_tex_coord; diff --git a/src/video_core/renderer_vulkan/shaders/blit.vert b/src/video_core/host_shaders/vulkan_present.vert index c64d9235a..00b868958 100644 --- a/src/video_core/renderer_vulkan/shaders/blit.vert +++ b/src/video_core/host_shaders/vulkan_present.vert @@ -2,15 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -/* - * Build instructions: - * $ glslangValidator -V $THIS_FILE -o output.spv - * $ spirv-opt -O --strip-debug output.spv -o optimized.spv - * $ xxd -i optimized.spv - * - * Then copy that bytecode to the C++ file - */ - #version 460 core layout (location = 0) in vec2 vert_position; diff --git a/src/video_core/renderer_vulkan/shaders/quad_indexed.comp b/src/video_core/host_shaders/vulkan_quad_indexed.comp index 5a472ba9b..8655591d0 100644 --- a/src/video_core/renderer_vulkan/shaders/quad_indexed.comp +++ b/src/video_core/host_shaders/vulkan_quad_indexed.comp @@ -2,15 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -/* - * Build instructions: - * $ glslangValidator -V quad_indexed.comp -o output.spv - * $ spirv-opt -O --strip-debug output.spv -o optimized.spv - * $ xxd -i optimized.spv - * - * Then copy that bytecode to the C++ file - */ - #version 460 core layout (local_size_x = 1024) in; diff --git a/src/video_core/renderer_vulkan/shaders/uint8.comp b/src/video_core/host_shaders/vulkan_uint8.comp index a320f3ae0..872291670 100644 --- a/src/video_core/renderer_vulkan/shaders/uint8.comp +++ b/src/video_core/host_shaders/vulkan_uint8.comp @@ -2,15 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -/* - * Build instructions: - * $ glslangValidator -V $THIS_FILE -o output.spv - * $ spirv-opt -O --strip-debug output.spv -o optimized.spv - * $ xxd -i optimized.spv - * - * Then copy that bytecode to the C++ file - */ - #version 460 core #extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_8bit_storage : require @@ -25,9 +16,16 @@ layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer { uint16_t output_indexes[]; }; +uint AssembleIndex(uint id) { + // Most primitive restart indices are 0xFF + // Hardcode this to 0xFF for now + uint index = uint(input_indexes[id]); + return index == 0xFF ? 0xFFFF : index; +} + void main() { uint id = gl_GlobalInvocationID.x; if (id < input_indexes.length()) { - output_indexes[id] = uint16_t(input_indexes[id]); + output_indexes[id] = uint16_t(AssembleIndex(id)); } } diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp index a50e7b4e0..cd21a2112 100644 --- a/src/video_core/macro/macro.cpp +++ b/src/video_core/macro/macro.cpp @@ -36,7 +36,7 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, } } else { // Macro not compiled, check if it's uploaded and if so, compile it - std::optional<u32> mid_method = std::nullopt; + std::optional<u32> mid_method; const auto macro_code = uploaded_macro_code.find(method); if (macro_code == uploaded_macro_code.end()) { for (const auto& [method_base, code] : uploaded_macro_code) { diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp index df00b57df..70ac7c620 100644 --- a/src/video_core/macro/macro_hle.cpp +++ b/src/video_core/macro/macro_hle.cpp @@ -85,7 +85,7 @@ constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{ {0x0217920100488FF7, &HLE_0217920100488FF7}, }}; -HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} +HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} HLEMacro::~HLEMacro() = default; std::optional<std::unique_ptr<CachedMacro>> HLEMacro::GetHLEProgram(u64 hash) const { @@ -99,8 +99,8 @@ std::optional<std::unique_ptr<CachedMacro>> HLEMacro::GetHLEProgram(u64 hash) co HLEMacroImpl::~HLEMacroImpl() = default; -HLEMacroImpl::HLEMacroImpl(Engines::Maxwell3D& maxwell3d, HLEFunction func) - : maxwell3d(maxwell3d), func(func) {} +HLEMacroImpl::HLEMacroImpl(Engines::Maxwell3D& maxwell3d_, HLEFunction func_) + : maxwell3d{maxwell3d_}, func{func_} {} void HLEMacroImpl::Execute(const std::vector<u32>& parameters, u32 method) { func(maxwell3d, parameters); diff --git a/src/video_core/macro/macro_hle.h b/src/video_core/macro/macro_hle.h index 37af875a0..cb3bd1600 100644 --- a/src/video_core/macro/macro_hle.h +++ b/src/video_core/macro/macro_hle.h @@ -20,7 +20,7 @@ using HLEFunction = void (*)(Engines::Maxwell3D& maxwell3d, const std::vector<u3 class HLEMacro { public: - explicit HLEMacro(Engines::Maxwell3D& maxwell3d); + explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); ~HLEMacro(); std::optional<std::unique_ptr<CachedMacro>> GetHLEProgram(u64 hash) const; diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp index bd01fd1f2..8da26fd59 100644 --- a/src/video_core/macro/macro_interpreter.cpp +++ b/src/video_core/macro/macro_interpreter.cpp @@ -11,29 +11,29 @@ MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192)); namespace Tegra { -MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) - : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {} +MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d_) + : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {} std::unique_ptr<CachedMacro> MacroInterpreter::Compile(const std::vector<u32>& code) { return std::make_unique<MacroInterpreterImpl>(maxwell3d, code); } -MacroInterpreterImpl::MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d, - const std::vector<u32>& code) - : maxwell3d(maxwell3d), code(code) {} +MacroInterpreterImpl::MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, + const std::vector<u32>& code_) + : maxwell3d{maxwell3d_}, code{code_} {} -void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 method) { +void MacroInterpreterImpl::Execute(const std::vector<u32>& params, u32 method) { MICROPROFILE_SCOPE(MacroInterp); Reset(); - registers[1] = parameters[0]; - num_parameters = parameters.size(); + registers[1] = params[0]; + num_parameters = params.size(); if (num_parameters > parameters_capacity) { parameters_capacity = num_parameters; - this->parameters = std::make_unique<u32[]>(num_parameters); + parameters = std::make_unique<u32[]>(num_parameters); } - std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32)); + std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32)); // Execute the code until we hit an exit condition. bool keep_executing = true; @@ -133,8 +133,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { break; } default: - UNIMPLEMENTED_MSG("Unimplemented macro operation {}", - static_cast<u32>(opcode.operation.Value())); + UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value()); } // An instruction with the Exit flag will not actually @@ -182,7 +181,7 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, return ~(src_a & src_b); default: - UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation)); + UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", operation); return 0; } } @@ -230,7 +229,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r Send((result >> 12) & 0b111111); break; default: - UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation)); + UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); } } diff --git a/src/video_core/macro/macro_interpreter.h b/src/video_core/macro/macro_interpreter.h index 90217fc89..d50c619ce 100644 --- a/src/video_core/macro/macro_interpreter.h +++ b/src/video_core/macro/macro_interpreter.h @@ -17,7 +17,7 @@ class Maxwell3D; class MacroInterpreter final : public MacroEngine { public: - explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d); + explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d_); protected: std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override; @@ -28,8 +28,8 @@ private: class MacroInterpreterImpl : public CachedMacro { public: - MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code); - void Execute(const std::vector<u32>& parameters, u32 method) override; + explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_); + void Execute(const std::vector<u32>& params, u32 method) override; private: /// Resets the execution engine state, zeroing registers, etc. @@ -38,9 +38,9 @@ private: /** * Executes a single macro instruction located at the current program counter. Returns whether * the interpreter should keep running. - * @param offset Offset to start execution at. + * * @param is_delay_slot Whether the current step is being executed due to a delay slot in a - * previous instruction. + * previous instruction. */ bool Step(bool is_delay_slot); diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index c1b9e4ad9..c6b2b2109 100644 --- a/src/video_core/macro/macro_jit_x64.cpp +++ b/src/video_core/macro/macro_jit_x64.cpp @@ -14,11 +14,11 @@ MICROPROFILE_DEFINE(MacroJitCompile, "GPU", "Compile macro JIT", MP_RGB(173, 255 MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255, 0)); namespace Tegra { -static const Xbyak::Reg64 STATE = Xbyak::util::rbx; -static const Xbyak::Reg32 RESULT = Xbyak::util::ebp; -static const Xbyak::Reg64 PARAMETERS = Xbyak::util::r12; -static const Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d; -static const Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; +constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx; +constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp; +constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12; +constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d; +constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({ STATE, @@ -28,15 +28,15 @@ static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({ BRANCH_HOLDER, }); -MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d) - : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {} +MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d_) + : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {} std::unique_ptr<CachedMacro> MacroJITx64::Compile(const std::vector<u32>& code) { return std::make_unique<MacroJITx64Impl>(maxwell3d, code); } -MacroJITx64Impl::MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code) - : Xbyak::CodeGenerator(MAX_CODE_SIZE), code(code), maxwell3d(maxwell3d) { +MacroJITx64Impl::MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_) + : CodeGenerator{MAX_CODE_SIZE}, code{code_}, maxwell3d{maxwell3d_} { Compile(); } @@ -165,8 +165,7 @@ void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { } break; default: - UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", - static_cast<std::size_t>(opcode.alu_operation.Value())); + UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value()); break; } Compile_ProcessResult(opcode.result_operation, opcode.dst); @@ -553,15 +552,15 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) { } void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) { - const auto SetRegister = [this](u32 reg, const Xbyak::Reg32& result) { + const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) { // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero // register. - if (reg == 0) { + if (reg_index == 0) { return; } - mov(dword[STATE + offsetof(JITState, registers) + reg * sizeof(u32)], result); + mov(dword[STATE + offsetof(JITState, registers) + reg_index * sizeof(u32)], result); }; - const auto SetMethodAddress = [this](const Xbyak::Reg32& reg) { mov(METHOD_ADDRESS, reg); }; + const auto SetMethodAddress = [this](const Xbyak::Reg32& reg32) { mov(METHOD_ADDRESS, reg32); }; switch (operation) { case Macro::ResultOperation::IgnoreAndFetch: @@ -604,7 +603,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3 Compile_Send(RESULT); break; default: - UNIMPLEMENTED_MSG("Unimplemented macro operation {}", static_cast<std::size_t>(operation)); + UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation); } } diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h index a180e7428..7f50ac2f8 100644 --- a/src/video_core/macro/macro_jit_x64.h +++ b/src/video_core/macro/macro_jit_x64.h @@ -23,7 +23,7 @@ constexpr size_t MAX_CODE_SIZE = 0x10000; class MacroJITx64 final : public MacroEngine { public: - explicit MacroJITx64(Engines::Maxwell3D& maxwell3d); + explicit MacroJITx64(Engines::Maxwell3D& maxwell3d_); protected: std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override; @@ -34,7 +34,7 @@ private: class MacroJITx64Impl : public Xbyak::CodeGenerator, public CachedMacro { public: - MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code); + explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_); ~MacroJITx64Impl(); void Execute(const std::vector<u32>& parameters, u32 method) override; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 16b2aaa27..44240a9c4 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -4,6 +4,7 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/logging/log.h" #include "core/core.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/process.h" @@ -11,6 +12,7 @@ #include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" namespace Tegra { @@ -19,8 +21,8 @@ MemoryManager::MemoryManager(Core::System& system_) MemoryManager::~MemoryManager() = default; -void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { - rasterizer = &rasterizer_; +void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { + rasterizer = rasterizer_; } GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) { @@ -37,6 +39,12 @@ GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std } GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) { + const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first); + if (it != map_ranges.end() && it->first == gpu_addr) { + it->second = size; + } else { + map_ranges.insert(it, MapRange{gpu_addr, size}); + } return UpdateRange(gpu_addr, cpu_addr, size); } @@ -44,13 +52,28 @@ GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_ return Map(cpu_addr, *FindFreeRange(size, align), size); } +GPUVAddr MemoryManager::MapAllocate32(VAddr cpu_addr, std::size_t size) { + const std::optional<GPUVAddr> gpu_addr = FindFreeRange(size, 1, true); + ASSERT(gpu_addr); + return Map(cpu_addr, *gpu_addr, size); +} + void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { - if (!size) { + if (size == 0) { return; } - + const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first); + if (it != map_ranges.end()) { + ASSERT(it->first == gpu_addr); + map_ranges.erase(it); + } else { + UNREACHABLE_MSG("Unmapping non-existent GPU address=0x{:x}", gpu_addr); + } // Flush and invalidate through the GPU interface, to be asynchronous if possible. - system.GPU().FlushAndInvalidateRegion(*GpuToCpuAddress(gpu_addr), size); + const std::optional<VAddr> cpu_addr = GpuToCpuAddress(gpu_addr); + ASSERT(cpu_addr); + + rasterizer->UnmapMemory(*cpu_addr, size); UpdateRange(gpu_addr, PageEntry::State::Unmapped, size); } @@ -58,7 +81,7 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) { for (u64 offset{}; offset < size; offset += page_size) { if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) { - return {}; + return std::nullopt; } } @@ -108,7 +131,8 @@ void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::s page_table[PageEntryIndex(gpu_addr)] = page_entry; } -std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align) const { +std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align, + bool start_32bit_address) const { if (!align) { align = page_size; } else { @@ -116,7 +140,7 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size } u64 available_size{}; - GPUVAddr gpu_addr{address_space_start}; + GPUVAddr gpu_addr{start_32bit_address ? address_space_start_low : address_space_start}; while (gpu_addr + available_size < address_space_size) { if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) { available_size += page_size; @@ -135,13 +159,13 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size } } - return {}; + return std::nullopt; } std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { const auto page_entry{GetPageEntry(gpu_addr)}; if (!page_entry.IsValid()) { - return {}; + return std::nullopt; } return page_entry.ToAddress() + (gpu_addr & page_mask); @@ -207,6 +231,12 @@ const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const { return system.Memory().GetPointer(*address); } +size_t MemoryManager::BytesToMapEnd(GPUVAddr gpu_addr) const noexcept { + auto it = std::ranges::upper_bound(map_ranges, gpu_addr, {}, &MapRange::first); + --it; + return it->second - (gpu_addr - it->first); +} + void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const { std::size_t remaining_size{size}; std::size_t page_index{gpu_src_addr >> page_bits}; @@ -303,17 +333,29 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buf } } +void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size) const { + size_t remaining_size{size}; + size_t page_index{gpu_addr >> page_bits}; + size_t page_offset{gpu_addr & page_mask}; + while (remaining_size > 0) { + const size_t num_bytes{std::min(page_size - page_offset, remaining_size)}; + if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) { + rasterizer->FlushRegion(*page_addr + page_offset, num_bytes); + } + ++page_index; + page_offset = 0; + remaining_size -= num_bytes; + } +} + void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size) { std::vector<u8> tmp_buffer(size); ReadBlock(gpu_src_addr, tmp_buffer.data(), size); - WriteBlock(gpu_dest_addr, tmp_buffer.data(), size); -} -void MemoryManager::CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, - std::size_t size) { - std::vector<u8> tmp_buffer(size); - ReadBlockUnsafe(gpu_src_addr, tmp_buffer.data(), size); - WriteBlockUnsafe(gpu_dest_addr, tmp_buffer.data(), size); + // The output block must be flushed in case it has data modified from the GPU. + // Fixes NPC geometry in Zombie Panic in Wonderland DX + FlushRegion(gpu_dest_addr, size); + WriteBlock(gpu_dest_addr, tmp_buffer.data(), size); } bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const { diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 53c8d122a..b3538d503 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -28,7 +28,7 @@ public: }; constexpr PageEntry() = default; - constexpr PageEntry(State state) : state{state} {} + constexpr PageEntry(State state_) : state{state_} {} constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {} [[nodiscard]] constexpr bool IsUnmapped() const { @@ -68,11 +68,11 @@ static_assert(sizeof(PageEntry) == 4, "PageEntry is too large"); class MemoryManager final { public: - explicit MemoryManager(Core::System& system); + explicit MemoryManager(Core::System& system_); ~MemoryManager(); /// Binds a renderer to the memory manager. - void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); [[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const; @@ -85,6 +85,9 @@ public: [[nodiscard]] u8* GetPointer(GPUVAddr addr); [[nodiscard]] const u8* GetPointer(GPUVAddr addr) const; + /// Returns the number of bytes until the end of the memory map containing the given GPU address + [[nodiscard]] size_t BytesToMapEnd(GPUVAddr gpu_addr) const noexcept; + /** * ReadBlock and WriteBlock are full read and write operations over virtual * GPU Memory. It's important to use these when GPU memory may not be continuous @@ -107,7 +110,6 @@ public: */ void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const; void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size); - void CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size); /** * IsGranularRange checks if a gpu region can be simply read with a pointer. @@ -116,6 +118,7 @@ public: [[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size); [[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align); + [[nodiscard]] GPUVAddr MapAllocate32(VAddr cpu_addr, std::size_t size); [[nodiscard]] std::optional<GPUVAddr> AllocateFixed(GPUVAddr gpu_addr, std::size_t size); [[nodiscard]] GPUVAddr Allocate(std::size_t size, std::size_t align); void Unmap(GPUVAddr gpu_addr, std::size_t size); @@ -124,17 +127,21 @@ private: [[nodiscard]] PageEntry GetPageEntry(GPUVAddr gpu_addr) const; void SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size = page_size); GPUVAddr UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size); - [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align) const; + [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align, + bool start_32bit_address = false) const; void TryLockPage(PageEntry page_entry, std::size_t size); void TryUnlockPage(PageEntry page_entry, std::size_t size); + void FlushRegion(GPUVAddr gpu_addr, size_t size) const; + [[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) { return (gpu_addr >> page_bits) & page_table_mask; } static constexpr u64 address_space_size = 1ULL << 40; static constexpr u64 address_space_start = 1ULL << 32; + static constexpr u64 address_space_start_low = 1ULL << 16; static constexpr u64 page_bits{16}; static constexpr u64 page_size{1 << page_bits}; static constexpr u64 page_mask{page_size - 1}; @@ -147,6 +154,11 @@ private: VideoCore::RasterizerInterface* rasterizer = nullptr; std::vector<PageEntry> page_table; + + using MapRange = std::pair<GPUVAddr, size_t>; + std::vector<MapRange> map_ranges; + + std::vector<std::pair<VAddr, std::size_t>> cache_invalidate_queue; }; } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp deleted file mode 100644 index 9da9fb4ff..000000000 --- a/src/video_core/morton.cpp +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <cstring> -#include "common/assert.h" -#include "common/common_types.h" -#include "video_core/morton.h" -#include "video_core/surface.h" -#include "video_core/textures/decoders.h" - -namespace VideoCore { - -using Surface::GetBytesPerPixel; -using Surface::PixelFormat; - -using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*); -using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>; - -template <bool morton_to_linear, PixelFormat format> -static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, - u32 tile_width_spacing, u8* buffer, u8* addr) { - constexpr u32 bytes_per_pixel = GetBytesPerPixel(format); - - // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual - // pixel values. - constexpr u32 tile_size_x{GetDefaultBlockWidth(format)}; - constexpr u32 tile_size_y{GetDefaultBlockHeight(format)}; - - if constexpr (morton_to_linear) { - Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, - stride, height, depth, block_height, block_depth, - tile_width_spacing); - } else { - Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x, - (height + tile_size_y - 1) / tile_size_y, depth, - bytes_per_pixel, bytes_per_pixel, addr, buffer, false, - block_height, block_depth, tile_width_spacing); - } -} - -static constexpr ConversionArray morton_to_linear_fns = { - MortonCopy<true, PixelFormat::A8B8G8R8_UNORM>, - MortonCopy<true, PixelFormat::A8B8G8R8_SNORM>, - MortonCopy<true, PixelFormat::A8B8G8R8_SINT>, - MortonCopy<true, PixelFormat::A8B8G8R8_UINT>, - MortonCopy<true, PixelFormat::R5G6B5_UNORM>, - MortonCopy<true, PixelFormat::B5G6R5_UNORM>, - MortonCopy<true, PixelFormat::A1R5G5B5_UNORM>, - MortonCopy<true, PixelFormat::A2B10G10R10_UNORM>, - MortonCopy<true, PixelFormat::A2B10G10R10_UINT>, - MortonCopy<true, PixelFormat::A1B5G5R5_UNORM>, - MortonCopy<true, PixelFormat::R8_UNORM>, - MortonCopy<true, PixelFormat::R8_SNORM>, - MortonCopy<true, PixelFormat::R8_SINT>, - MortonCopy<true, PixelFormat::R8_UINT>, - MortonCopy<true, PixelFormat::R16G16B16A16_FLOAT>, - MortonCopy<true, PixelFormat::R16G16B16A16_UNORM>, - MortonCopy<true, PixelFormat::R16G16B16A16_SNORM>, - MortonCopy<true, PixelFormat::R16G16B16A16_SINT>, - MortonCopy<true, PixelFormat::R16G16B16A16_UINT>, - MortonCopy<true, PixelFormat::B10G11R11_FLOAT>, - MortonCopy<true, PixelFormat::R32G32B32A32_UINT>, - MortonCopy<true, PixelFormat::BC1_RGBA_UNORM>, - MortonCopy<true, PixelFormat::BC2_UNORM>, - MortonCopy<true, PixelFormat::BC3_UNORM>, - MortonCopy<true, PixelFormat::BC4_UNORM>, - MortonCopy<true, PixelFormat::BC4_SNORM>, - MortonCopy<true, PixelFormat::BC5_UNORM>, - MortonCopy<true, PixelFormat::BC5_SNORM>, - MortonCopy<true, PixelFormat::BC7_UNORM>, - MortonCopy<true, PixelFormat::BC6H_UFLOAT>, - MortonCopy<true, PixelFormat::BC6H_SFLOAT>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4_UNORM>, - MortonCopy<true, PixelFormat::B8G8R8A8_UNORM>, - MortonCopy<true, PixelFormat::R32G32B32A32_FLOAT>, - MortonCopy<true, PixelFormat::R32G32B32A32_SINT>, - MortonCopy<true, PixelFormat::R32G32_FLOAT>, - MortonCopy<true, PixelFormat::R32G32_SINT>, - MortonCopy<true, PixelFormat::R32_FLOAT>, - MortonCopy<true, PixelFormat::R16_FLOAT>, - MortonCopy<true, PixelFormat::R16_UNORM>, - MortonCopy<true, PixelFormat::R16_SNORM>, - MortonCopy<true, PixelFormat::R16_UINT>, - MortonCopy<true, PixelFormat::R16_SINT>, - MortonCopy<true, PixelFormat::R16G16_UNORM>, - MortonCopy<true, PixelFormat::R16G16_FLOAT>, - MortonCopy<true, PixelFormat::R16G16_UINT>, - MortonCopy<true, PixelFormat::R16G16_SINT>, - MortonCopy<true, PixelFormat::R16G16_SNORM>, - MortonCopy<true, PixelFormat::R32G32B32_FLOAT>, - MortonCopy<true, PixelFormat::A8B8G8R8_SRGB>, - MortonCopy<true, PixelFormat::R8G8_UNORM>, - MortonCopy<true, PixelFormat::R8G8_SNORM>, - MortonCopy<true, PixelFormat::R8G8_SINT>, - MortonCopy<true, PixelFormat::R8G8_UINT>, - MortonCopy<true, PixelFormat::R32G32_UINT>, - MortonCopy<true, PixelFormat::R16G16B16X16_FLOAT>, - MortonCopy<true, PixelFormat::R32_UINT>, - MortonCopy<true, PixelFormat::R32_SINT>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4_UNORM>, - MortonCopy<true, PixelFormat::B8G8R8A8_SRGB>, - MortonCopy<true, PixelFormat::BC1_RGBA_SRGB>, - MortonCopy<true, PixelFormat::BC2_SRGB>, - MortonCopy<true, PixelFormat::BC3_SRGB>, - MortonCopy<true, PixelFormat::BC7_SRGB>, - MortonCopy<true, PixelFormat::A4B4G4R4_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_6X6_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_6X6_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X10_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_10X10_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_12X12_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_12X12_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X6_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_8X6_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_6X5_UNORM>, - MortonCopy<true, PixelFormat::ASTC_2D_6X5_SRGB>, - MortonCopy<true, PixelFormat::E5B9G9R9_FLOAT>, - MortonCopy<true, PixelFormat::D32_FLOAT>, - MortonCopy<true, PixelFormat::D16_UNORM>, - MortonCopy<true, PixelFormat::D24_UNORM_S8_UINT>, - MortonCopy<true, PixelFormat::S8_UINT_D24_UNORM>, - MortonCopy<true, PixelFormat::D32_FLOAT_S8_UINT>, -}; - -static constexpr ConversionArray linear_to_morton_fns = { - MortonCopy<false, PixelFormat::A8B8G8R8_UNORM>, - MortonCopy<false, PixelFormat::A8B8G8R8_SNORM>, - MortonCopy<false, PixelFormat::A8B8G8R8_SINT>, - MortonCopy<false, PixelFormat::A8B8G8R8_UINT>, - MortonCopy<false, PixelFormat::R5G6B5_UNORM>, - MortonCopy<false, PixelFormat::B5G6R5_UNORM>, - MortonCopy<false, PixelFormat::A1R5G5B5_UNORM>, - MortonCopy<false, PixelFormat::A2B10G10R10_UNORM>, - MortonCopy<false, PixelFormat::A2B10G10R10_UINT>, - MortonCopy<false, PixelFormat::A1B5G5R5_UNORM>, - MortonCopy<false, PixelFormat::R8_UNORM>, - MortonCopy<false, PixelFormat::R8_SNORM>, - MortonCopy<false, PixelFormat::R8_SINT>, - MortonCopy<false, PixelFormat::R8_UINT>, - MortonCopy<false, PixelFormat::R16G16B16A16_FLOAT>, - MortonCopy<false, PixelFormat::R16G16B16A16_SNORM>, - MortonCopy<false, PixelFormat::R16G16B16A16_SINT>, - MortonCopy<false, PixelFormat::R16G16B16A16_UNORM>, - MortonCopy<false, PixelFormat::R16G16B16A16_UINT>, - MortonCopy<false, PixelFormat::B10G11R11_FLOAT>, - MortonCopy<false, PixelFormat::R32G32B32A32_UINT>, - MortonCopy<false, PixelFormat::BC1_RGBA_UNORM>, - MortonCopy<false, PixelFormat::BC2_UNORM>, - MortonCopy<false, PixelFormat::BC3_UNORM>, - MortonCopy<false, PixelFormat::BC4_UNORM>, - MortonCopy<false, PixelFormat::BC4_SNORM>, - MortonCopy<false, PixelFormat::BC5_UNORM>, - MortonCopy<false, PixelFormat::BC5_SNORM>, - MortonCopy<false, PixelFormat::BC7_UNORM>, - MortonCopy<false, PixelFormat::BC6H_UFLOAT>, - MortonCopy<false, PixelFormat::BC6H_SFLOAT>, - // TODO(Subv): Swizzling ASTC formats are not supported - nullptr, - MortonCopy<false, PixelFormat::B8G8R8A8_UNORM>, - MortonCopy<false, PixelFormat::R32G32B32A32_FLOAT>, - MortonCopy<false, PixelFormat::R32G32B32A32_SINT>, - MortonCopy<false, PixelFormat::R32G32_FLOAT>, - MortonCopy<false, PixelFormat::R32G32_SINT>, - MortonCopy<false, PixelFormat::R32_FLOAT>, - MortonCopy<false, PixelFormat::R16_FLOAT>, - MortonCopy<false, PixelFormat::R16_UNORM>, - MortonCopy<false, PixelFormat::R16_SNORM>, - MortonCopy<false, PixelFormat::R16_UINT>, - MortonCopy<false, PixelFormat::R16_SINT>, - MortonCopy<false, PixelFormat::R16G16_UNORM>, - MortonCopy<false, PixelFormat::R16G16_FLOAT>, - MortonCopy<false, PixelFormat::R16G16_UINT>, - MortonCopy<false, PixelFormat::R16G16_SINT>, - MortonCopy<false, PixelFormat::R16G16_SNORM>, - MortonCopy<false, PixelFormat::R32G32B32_FLOAT>, - MortonCopy<false, PixelFormat::A8B8G8R8_SRGB>, - MortonCopy<false, PixelFormat::R8G8_UNORM>, - MortonCopy<false, PixelFormat::R8G8_SNORM>, - MortonCopy<false, PixelFormat::R8G8_SINT>, - MortonCopy<false, PixelFormat::R8G8_UINT>, - MortonCopy<false, PixelFormat::R32G32_UINT>, - MortonCopy<false, PixelFormat::R16G16B16X16_FLOAT>, - MortonCopy<false, PixelFormat::R32_UINT>, - MortonCopy<false, PixelFormat::R32_SINT>, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::B8G8R8A8_SRGB>, - MortonCopy<false, PixelFormat::BC1_RGBA_SRGB>, - MortonCopy<false, PixelFormat::BC2_SRGB>, - MortonCopy<false, PixelFormat::BC3_SRGB>, - MortonCopy<false, PixelFormat::BC7_SRGB>, - MortonCopy<false, PixelFormat::A4B4G4R4_UNORM>, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::E5B9G9R9_FLOAT>, - MortonCopy<false, PixelFormat::D32_FLOAT>, - MortonCopy<false, PixelFormat::D16_UNORM>, - MortonCopy<false, PixelFormat::D24_UNORM_S8_UINT>, - MortonCopy<false, PixelFormat::S8_UINT_D24_UNORM>, - MortonCopy<false, PixelFormat::D32_FLOAT_S8_UINT>, -}; - -static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) { - switch (mode) { - case MortonSwizzleMode::MortonToLinear: - return morton_to_linear_fns[static_cast<std::size_t>(format)]; - case MortonSwizzleMode::LinearToMorton: - return linear_to_morton_fns[static_cast<std::size_t>(format)]; - } - UNREACHABLE(); - return morton_to_linear_fns[static_cast<std::size_t>(format)]; -} - -void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, - u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, - u8* buffer, u8* addr) { - GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth, - tile_width_spacing, buffer, addr); -} - -} // namespace VideoCore diff --git a/src/video_core/morton.h b/src/video_core/morton.h deleted file mode 100644 index b714a7e3f..000000000 --- a/src/video_core/morton.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" -#include "video_core/surface.h" - -namespace VideoCore { - -enum class MortonSwizzleMode { MortonToLinear, LinearToMorton }; - -void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride, - u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, - u8* buffer, u8* addr); - -} // namespace VideoCore diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index 0d3a88765..203f2af05 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -28,8 +28,8 @@ namespace VideoCommon { template <class QueryCache, class HostCounter> class CounterStreamBase { public: - explicit CounterStreamBase(QueryCache& cache, VideoCore::QueryType type) - : cache{cache}, type{type} {} + explicit CounterStreamBase(QueryCache& cache_, VideoCore::QueryType type_) + : cache{cache_}, type{type_} {} /// Updates the state of the stream, enabling or disabling as needed. void Update(bool enabled) { @@ -91,14 +91,15 @@ private: std::shared_ptr<HostCounter> last; }; -template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter, - class QueryPool> +template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> class QueryCacheBase { public: - explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer) - : system{system}, rasterizer{rasterizer}, streams{{CounterStream{ - static_cast<QueryCache&>(*this), - VideoCore::QueryType::SamplesPassed}}} {} + explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::MemoryManager& gpu_memory_) + : rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, + gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this), + VideoCore::QueryType::SamplesPassed}}} {} void InvalidateRegion(VAddr addr, std::size_t size) { std::unique_lock lock{mutex}; @@ -118,29 +119,27 @@ public: */ void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) { std::unique_lock lock{mutex}; - auto& memory_manager = system.GPU().MemoryManager(); - const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr); - ASSERT(cpu_addr_opt); - VAddr cpu_addr = *cpu_addr_opt; + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + ASSERT(cpu_addr); - CachedQuery* query = TryGet(cpu_addr); + CachedQuery* query = TryGet(*cpu_addr); if (!query) { - ASSERT_OR_EXECUTE(cpu_addr_opt, return;); - const auto host_ptr = memory_manager.GetPointer(gpu_addr); + ASSERT_OR_EXECUTE(cpu_addr, return;); + u8* const host_ptr = gpu_memory.GetPointer(gpu_addr); - query = Register(type, cpu_addr, host_ptr, timestamp.has_value()); + query = Register(type, *cpu_addr, host_ptr, timestamp.has_value()); } query->BindCounter(Stream(type).Current(), timestamp); if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - AsyncFlushQuery(cpu_addr); + AsyncFlushQuery(*cpu_addr); } } /// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch. void UpdateCounters() { std::unique_lock lock{mutex}; - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable); } @@ -206,9 +205,6 @@ public: committed_flushes.pop_front(); } -protected: - std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; - private: /// Flushes a memory range to guest memory and removes it from the cache. void FlushAndRemoveRegion(VAddr addr, std::size_t size) { @@ -270,8 +266,9 @@ private: static constexpr std::uintptr_t PAGE_SIZE = 4096; static constexpr unsigned PAGE_BITS = 12; - Core::System& system; VideoCore::RasterizerInterface& rasterizer; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::MemoryManager& gpu_memory; std::recursive_mutex mutex; @@ -337,8 +334,8 @@ private: template <class HostCounter> class CachedQueryBase { public: - explicit CachedQueryBase(VAddr cpu_addr, u8* host_ptr) - : cpu_addr{cpu_addr}, host_ptr{host_ptr} {} + explicit CachedQueryBase(VAddr cpu_addr_, u8* host_ptr_) + : cpu_addr{cpu_addr_}, host_ptr{host_ptr_} {} virtual ~CachedQueryBase() = default; CachedQueryBase(CachedQueryBase&&) noexcept = default; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 3cbdac8e7..50491b758 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -7,6 +7,7 @@ #include <atomic> #include <functional> #include <optional> +#include <span> #include "common/common_types.h" #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" @@ -32,7 +33,7 @@ using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size class RasterizerInterface { public: - virtual ~RasterizerInterface() {} + virtual ~RasterizerInterface() = default; /// Dispatches a draw invocation virtual void Draw(bool is_indexed, bool is_instanced) = 0; @@ -49,6 +50,10 @@ public: /// Records a GPU query and caches it virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0; + /// Signal an uniform buffer binding + virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, + u32 size) = 0; + /// Signal a GPU based semaphore as a fence virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0; @@ -76,6 +81,9 @@ public: /// Sync memory between guest and host. virtual void SyncGuestHost() = 0; + /// Unmap memory range + virtual void UnmapMemory(VAddr addr, u64 size) = 0; + /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory /// and invalidated virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; @@ -83,6 +91,12 @@ public: /// Notify the host renderer to wait for previous primitive and compute operations. virtual void WaitForIdle() = 0; + /// Notify the host renderer to wait for reads and writes to render targets and flush caches. + virtual void FragmentBarrier() = 0; + + /// Notify the host renderer to make available previous render target writes. + virtual void TiledCacheBarrier() = 0; + /// Notify the rasterizer to send all written commands to the host GPU. virtual void FlushCommands() = 0; @@ -90,15 +104,15 @@ public: virtual void TickFrame() = 0; /// Attempt to use a faster method to perform a surface copy - virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, - const Tegra::Engines::Fermi2D::Config& copy_config) { + [[nodiscard]] virtual bool AccelerateSurfaceCopy( + const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) { return false; } /// Attempt to use a faster method to display the framebuffer to screen - virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, - u32 pixel_stride) { + [[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, + VAddr framebuffer_addr, u32 pixel_stride) { return false; } @@ -106,19 +120,16 @@ public: virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} /// Initialize disk cached resources for the game being emulated - virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false, - const DiskResourceLoadCallback& callback = {}) {} - - /// Initializes renderer dirty flags - virtual void SetupDirtyFlags() {} + virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, + const DiskResourceLoadCallback& callback) {} /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. - GuestDriverProfile& AccessGuestDriverProfile() { + [[nodiscard]] GuestDriverProfile& AccessGuestDriverProfile() { return guest_driver_profile; } /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. - const GuestDriverProfile& AccessGuestDriverProfile() const { + [[nodiscard]] const GuestDriverProfile& AccessGuestDriverProfile() const { return guest_driver_profile; } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 649074acd..320ee8d30 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -37,60 +37,43 @@ public: std::unique_ptr<Core::Frontend::GraphicsContext> context); virtual ~RendererBase(); - /// Initialize the renderer - virtual bool Init() = 0; - - /// Shutdown the renderer - virtual void ShutDown() = 0; - /// Finalize rendering the guest frame and draw into the presentation texture virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; - /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer - /// specific implementation) - /// Returns true if a frame was drawn - virtual bool TryPresent(int timeout_ms) = 0; + [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; // Getter/setter functions: // ------------------------ - f32 GetCurrentFPS() const { + [[nodiscard]] f32 GetCurrentFPS() const { return m_current_fps; } - int GetCurrentFrame() const { + [[nodiscard]] int GetCurrentFrame() const { return m_current_frame; } - RasterizerInterface& Rasterizer() { - return *rasterizer; - } - - const RasterizerInterface& Rasterizer() const { - return *rasterizer; - } - - Core::Frontend::GraphicsContext& Context() { + [[nodiscard]] Core::Frontend::GraphicsContext& Context() { return *context; } - const Core::Frontend::GraphicsContext& Context() const { + [[nodiscard]] const Core::Frontend::GraphicsContext& Context() const { return *context; } - Core::Frontend::EmuWindow& GetRenderWindow() { + [[nodiscard]] Core::Frontend::EmuWindow& GetRenderWindow() { return render_window; } - const Core::Frontend::EmuWindow& GetRenderWindow() const { + [[nodiscard]] const Core::Frontend::EmuWindow& GetRenderWindow() const { return render_window; } - RendererSettings& Settings() { + [[nodiscard]] RendererSettings& Settings() { return renderer_settings; } - const RendererSettings& Settings() const { + [[nodiscard]] const RendererSettings& Settings() const { return renderer_settings; } @@ -103,7 +86,6 @@ public: protected: Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. - std::unique_ptr<RasterizerInterface> rasterizer; std::unique_ptr<Core::Frontend::GraphicsContext> context; f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer int m_current_frame = 0; ///< Current frame, should be set by the renderer diff --git a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp index b7e9ed2e9..3e4d88c30 100644 --- a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp @@ -39,8 +39,8 @@ using Operation = const OperationNode&; constexpr std::array INTERNAL_FLAG_NAMES = {"ZERO", "SIGN", "CARRY", "OVERFLOW"}; char Swizzle(std::size_t component) { - ASSERT(component < 4); - return component["xyzw"]; + static constexpr std::string_view SWIZZLE{"xyzw"}; + return SWIZZLE.at(component); } constexpr bool IsGenericAttribute(Attribute::Index index) { @@ -71,7 +71,7 @@ std::string_view GetInputFlags(PixelImap attribute) { case PixelImap::Unused: break; } - UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute)); + UNIMPLEMENTED_MSG("Unknown attribute usage index={}", attribute); return {}; } @@ -123,7 +123,7 @@ std::string_view PrimitiveDescription(Tegra::Engines::Maxwell3D::Regs::Primitive case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency: return "TRIANGLES_ADJACENCY"; default: - UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology)); + UNIMPLEMENTED_MSG("topology={}", topology); return "POINTS"; } } @@ -137,7 +137,7 @@ std::string_view TopologyName(Tegra::Shader::OutputTopology topology) { case Tegra::Shader::OutputTopology::TriangleStrip: return "TRIANGLE_STRIP"; default: - UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology)); + UNIMPLEMENTED_MSG("Unknown output topology: {}", topology); return "points"; } } @@ -187,8 +187,8 @@ std::string TextureType(const MetaTexture& meta) { class ARBDecompiler final { public: - explicit ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry, - ShaderType stage, std::string_view identifier); + explicit ARBDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_, + ShaderType stage_, std::string_view identifier); std::string Code() const { return shader_source; @@ -224,7 +224,7 @@ private: std::string Visit(const Node& node); - std::pair<std::string, std::size_t> BuildCoords(Operation); + std::tuple<std::string, std::string, std::size_t> BuildCoords(Operation); std::string BuildAoffi(Operation); std::string GlobalMemoryPointer(const GmemNode& gmem); void Exit(); @@ -376,9 +376,11 @@ private: std::string temporary = AllocTemporary(); std::string address; std::string_view opname; + bool robust = false; if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) { address = GlobalMemoryPointer(*gmem); opname = "ATOM"; + robust = true; } else if (const auto smem = std::get_if<SmemNode>(&*operation[0])) { address = fmt::format("shared_mem[{}]", Visit(smem->GetAddress())); opname = "ATOMS"; @@ -386,7 +388,15 @@ private: UNREACHABLE(); return "{0, 0, 0, 0}"; } + if (robust) { + AddLine("IF NE.x;"); + } AddLine("{}.{}.{} {}, {}, {};", opname, op, type, temporary, Visit(operation[1]), address); + if (robust) { + AddLine("ELSE;"); + AddLine("MOV.S {}, 0;", temporary); + AddLine("ENDIF;"); + } return temporary; } @@ -792,9 +802,9 @@ private: }; }; -ARBDecompiler::ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry, - ShaderType stage, std::string_view identifier) - : device{device}, ir{ir}, registry{registry}, stage{stage} { +ARBDecompiler::ARBDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_, + ShaderType stage_, std::string_view identifier) + : device{device_}, ir{ir_}, registry{registry_}, stage{stage_} { DefineGlobalMemory(); AddLine("TEMP RC;"); @@ -980,10 +990,9 @@ void ARBDecompiler::DeclareLocalMemory() { } void ARBDecompiler::DeclareGlobalMemory() { - const std::size_t num_entries = ir.GetGlobalMemory().size(); + const size_t num_entries = ir.GetGlobalMemory().size(); if (num_entries > 0) { - const std::size_t num_vectors = Common::AlignUp(num_entries, 2) / 2; - AddLine("PARAM c[{}] = {{ program.local[0..{}] }};", num_vectors, num_vectors - 1); + AddLine("PARAM c[{}] = {{ program.local[0..{}] }};", num_entries, num_entries - 1); } } @@ -1125,44 +1134,44 @@ void ARBDecompiler::VisitAST(const ASTNode& node) { for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) { VisitAST(current); } - } else if (const auto ast = std::get_if<ASTIfThen>(&*node->GetInnerData())) { - const std::string condition = VisitExpression(ast->condition); + } else if (const auto if_then = std::get_if<ASTIfThen>(&*node->GetInnerData())) { + const std::string condition = VisitExpression(if_then->condition); ResetTemporaries(); AddLine("MOVC.U RC.x, {};", condition); AddLine("IF NE.x;"); - for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) { + for (ASTNode current = if_then->nodes.GetFirst(); current; current = current->GetNext()) { VisitAST(current); } AddLine("ENDIF;"); - } else if (const auto ast = std::get_if<ASTIfElse>(&*node->GetInnerData())) { + } else if (const auto if_else = std::get_if<ASTIfElse>(&*node->GetInnerData())) { AddLine("ELSE;"); - for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) { + for (ASTNode current = if_else->nodes.GetFirst(); current; current = current->GetNext()) { VisitAST(current); } - } else if (const auto ast = std::get_if<ASTBlockDecoded>(&*node->GetInnerData())) { - VisitBlock(ast->nodes); - } else if (const auto ast = std::get_if<ASTVarSet>(&*node->GetInnerData())) { - AddLine("MOV.U F{}, {};", ast->index, VisitExpression(ast->condition)); + } else if (const auto decoded = std::get_if<ASTBlockDecoded>(&*node->GetInnerData())) { + VisitBlock(decoded->nodes); + } else if (const auto var_set = std::get_if<ASTVarSet>(&*node->GetInnerData())) { + AddLine("MOV.U F{}, {};", var_set->index, VisitExpression(var_set->condition)); ResetTemporaries(); - } else if (const auto ast = std::get_if<ASTDoWhile>(&*node->GetInnerData())) { - const std::string condition = VisitExpression(ast->condition); + } else if (const auto do_while = std::get_if<ASTDoWhile>(&*node->GetInnerData())) { + const std::string condition = VisitExpression(do_while->condition); ResetTemporaries(); AddLine("REP;"); - for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) { + for (ASTNode current = do_while->nodes.GetFirst(); current; current = current->GetNext()) { VisitAST(current); } AddLine("MOVC.U RC.x, {};", condition); AddLine("BRK (NE.x);"); AddLine("ENDREP;"); - } else if (const auto ast = std::get_if<ASTReturn>(&*node->GetInnerData())) { - const bool is_true = ExprIsTrue(ast->condition); + } else if (const auto ast_return = std::get_if<ASTReturn>(&*node->GetInnerData())) { + const bool is_true = ExprIsTrue(ast_return->condition); if (!is_true) { - AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition)); + AddLine("MOVC.U RC.x, {};", VisitExpression(ast_return->condition)); AddLine("IF NE.x;"); ResetTemporaries(); } - if (ast->kills) { + if (ast_return->kills) { AddLine("KIL TR;"); } else { Exit(); @@ -1170,11 +1179,11 @@ void ARBDecompiler::VisitAST(const ASTNode& node) { if (!is_true) { AddLine("ENDIF;"); } - } else if (const auto ast = std::get_if<ASTBreak>(&*node->GetInnerData())) { - if (ExprIsTrue(ast->condition)) { + } else if (const auto ast_break = std::get_if<ASTBreak>(&*node->GetInnerData())) { + if (ExprIsTrue(ast_break->condition)) { AddLine("BRK;"); } else { - AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition)); + AddLine("MOVC.U RC.x, {};", VisitExpression(ast_break->condition)); AddLine("BRK (NE.x);"); ResetTemporaries(); } @@ -1342,7 +1351,7 @@ std::string ARBDecompiler::Visit(const Node& node) { GetGenericAttributeIndex(index), swizzle); } } - UNIMPLEMENTED_MSG("Unimplemented input attribute={}", static_cast<int>(index)); + UNIMPLEMENTED_MSG("Unimplemented input attribute={}", index); break; } return "{0, 0, 0, 0}.x"; @@ -1363,7 +1372,8 @@ std::string ARBDecompiler::Visit(const Node& node) { if (const auto gmem = std::get_if<GmemNode>(&*node)) { std::string temporary = AllocTemporary(); - AddLine("LOAD.U32 {}, {};", temporary, GlobalMemoryPointer(*gmem)); + AddLine("MOV {}, 0;", temporary); + AddLine("LOAD.U32 {} (NE.x), {};", temporary, GlobalMemoryPointer(*gmem)); return temporary; } @@ -1406,12 +1416,12 @@ std::string ARBDecompiler::Visit(const Node& node) { return {}; } -std::pair<std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operation) { +std::tuple<std::string, std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operation) { const auto& meta = std::get<MetaTexture>(operation.GetMeta()); UNIMPLEMENTED_IF(meta.sampler.is_indexed); - UNIMPLEMENTED_IF(meta.sampler.is_shadow && meta.sampler.is_array && - meta.sampler.type == Tegra::Shader::TextureType::TextureCube); + const bool is_extended = meta.sampler.is_shadow && meta.sampler.is_array && + meta.sampler.type == Tegra::Shader::TextureType::TextureCube; const std::size_t count = operation.GetOperandsCount(); std::string temporary = AllocVectorTemporary(); std::size_t i = 0; @@ -1419,12 +1429,21 @@ std::pair<std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operati AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), Visit(operation[i])); } if (meta.sampler.is_array) { - AddLine("I2F.S {}.{}, {};", temporary, Swizzle(i++), Visit(meta.array)); + AddLine("I2F.S {}.{}, {};", temporary, Swizzle(i), Visit(meta.array)); + ++i; } if (meta.sampler.is_shadow) { - AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i++), Visit(meta.depth_compare)); + std::string compare = Visit(meta.depth_compare); + if (is_extended) { + ASSERT(i == 4); + std::string extra_coord = AllocVectorTemporary(); + AddLine("MOV.F {}.x, {};", extra_coord, compare); + return {fmt::format("{}, {}", temporary, extra_coord), extra_coord, 0}; + } + AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), compare); + ++i; } - return {std::move(temporary), i}; + return {temporary, temporary, i}; } std::string ARBDecompiler::BuildAoffi(Operation operation) { @@ -1441,18 +1460,21 @@ std::string ARBDecompiler::BuildAoffi(Operation operation) { } std::string ARBDecompiler::GlobalMemoryPointer(const GmemNode& gmem) { + // Read a bindless SSBO, return its address and set CC accordingly + // address = c[binding].xy + // length = c[binding].z const u32 binding = global_memory_names.at(gmem.GetDescriptor()); - const char result_swizzle = binding % 2 == 0 ? 'x' : 'y'; const std::string pointer = AllocLongVectorTemporary(); std::string temporary = AllocTemporary(); - const u32 local_index = binding / 2; - AddLine("PK64.U {}, c[{}];", pointer, local_index); + AddLine("PK64.U {}, c[{}];", pointer, binding); AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem.GetRealAddress()), Visit(gmem.GetBaseAddress())); AddLine("CVT.U64.U32 {}.z, {};", pointer, temporary); - AddLine("ADD.U64 {}.x, {}.{}, {}.z;", pointer, pointer, result_swizzle, pointer); + AddLine("ADD.U64 {}.x, {}.x, {}.z;", pointer, pointer, pointer); + // Compare offset to length and set CC + AddLine("SLT.U.CC RC.x, {}, c[{}].z;", temporary, binding); return fmt::format("{}.x", pointer); } @@ -1463,9 +1485,7 @@ void ARBDecompiler::Exit() { } const auto safe_get_register = [this](u32 reg) -> std::string { - // TODO(Rodrigo): Replace with contains once C++20 releases - const auto& used_registers = ir.GetRegisters(); - if (used_registers.find(reg) != used_registers.end()) { + if (ir.GetRegisters().contains(reg)) { return fmt::format("R{}.x", reg); } return "{0, 0, 0, 0}.x"; @@ -1552,7 +1572,9 @@ std::string ARBDecompiler::Assign(Operation operation) { ResetTemporaries(); return {}; } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { + AddLine("IF NE.x;"); AddLine("STORE.U32 {}, {};", Visit(src), GlobalMemoryPointer(*gmem)); + AddLine("ENDIF;"); ResetTemporaries(); return {}; } else { @@ -1844,7 +1866,7 @@ std::string ARBDecompiler::LogicalAddCarry(Operation operation) { std::string ARBDecompiler::Texture(Operation operation) { const auto& meta = std::get<MetaTexture>(operation.GetMeta()); const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index; - const auto [temporary, swizzle] = BuildCoords(operation); + const auto [coords, temporary, swizzle] = BuildCoords(operation); std::string_view opcode = "TEX"; std::string extra; @@ -1873,7 +1895,7 @@ std::string ARBDecompiler::Texture(Operation operation) { } } - AddLine("{}.F {}, {},{} texture[{}], {}{};", opcode, temporary, temporary, extra, sampler_id, + AddLine("{}.F {}, {},{} texture[{}], {}{};", opcode, temporary, coords, extra, sampler_id, TextureType(meta), BuildAoffi(operation)); AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element)); return fmt::format("{}.x", temporary); @@ -1882,7 +1904,7 @@ std::string ARBDecompiler::Texture(Operation operation) { std::string ARBDecompiler::TextureGather(Operation operation) { const auto& meta = std::get<MetaTexture>(operation.GetMeta()); const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index; - const auto [temporary, swizzle] = BuildCoords(operation); + const auto [coords, temporary, swizzle] = BuildCoords(operation); std::string comp; if (!meta.sampler.is_shadow) { @@ -1892,7 +1914,7 @@ std::string ARBDecompiler::TextureGather(Operation operation) { AddLine("TXG.F {}, {}, texture[{}]{}, {}{};", temporary, temporary, sampler_id, comp, TextureType(meta), BuildAoffi(operation)); - AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element)); + AddLine("MOV.U {}.x, {}.{};", temporary, coords, Swizzle(meta.element)); return fmt::format("{}.x", temporary); } @@ -1930,13 +1952,13 @@ std::string ARBDecompiler::TextureQueryLod(Operation operation) { std::string ARBDecompiler::TexelFetch(Operation operation) { const auto& meta = std::get<MetaTexture>(operation.GetMeta()); const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index; - const auto [temporary, swizzle] = BuildCoords(operation); + const auto [coords, temporary, swizzle] = BuildCoords(operation); if (!meta.sampler.is_buffer) { ASSERT(swizzle < 4); AddLine("MOV.F {}.w, {};", temporary, Visit(meta.lod)); } - AddLine("TXF.F {}, {}, texture[{}], {}{};", temporary, temporary, sampler_id, TextureType(meta), + AddLine("TXF.F {}, {}, texture[{}], {}{};", temporary, coords, sampler_id, TextureType(meta), BuildAoffi(operation)); AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element)); return fmt::format("{}.x", temporary); @@ -1947,7 +1969,7 @@ std::string ARBDecompiler::TextureGradient(Operation operation) { const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index; const std::string ddx = AllocVectorTemporary(); const std::string ddy = AllocVectorTemporary(); - const std::string coord = BuildCoords(operation).first; + const std::string coord = std::get<1>(BuildCoords(operation)); const std::size_t num_components = meta.derivates.size() / 2; for (std::size_t index = 0; index < num_components; ++index) { diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index e866d8f2f..6da3906a4 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -2,98 +2,208 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <memory> +#include <span> -#include <glad/glad.h> - -#include "common/assert.h" -#include "common/microprofile.h" #include "video_core/buffer_cache/buffer_cache.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_device.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { +namespace { +struct BindlessSSBO { + GLuint64EXT address; + GLsizei length; + GLsizei padding; +}; +static_assert(sizeof(BindlessSSBO) == sizeof(GLuint) * 4); + +constexpr std::array PROGRAM_LUT{ + GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV, + GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV, +}; +} // Anonymous namespace + +Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) + : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} + +Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, + VAddr cpu_addr_, u64 size_bytes_) + : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_) { + buffer.Create(); + const std::string name = fmt::format("Buffer 0x{:x}", CpuAddr()); + glObjectLabel(GL_BUFFER, buffer.handle, static_cast<GLsizei>(name.size()), name.data()); + glNamedBufferData(buffer.handle, SizeBytes(), nullptr, GL_DYNAMIC_DRAW); + + if (runtime.has_unified_vertex_buffers) { + glGetNamedBufferParameterui64vNV(buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &address); + } +} -using Maxwell = Tegra::Engines::Maxwell3D::Regs; +void Buffer::ImmediateUpload(size_t offset, std::span<const u8> data) noexcept { + glNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset), + static_cast<GLsizeiptr>(data.size_bytes()), data.data()); +} -MICROPROFILE_DEFINE(OpenGL_Buffer_Download, "OpenGL", "Buffer Download", MP_RGB(192, 192, 128)); +void Buffer::ImmediateDownload(size_t offset, std::span<u8> data) noexcept { + glGetNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset), + static_cast<GLsizeiptr>(data.size_bytes()), data.data()); +} -Buffer::Buffer(const Device& device, VAddr cpu_addr, std::size_t size) - : VideoCommon::BufferBlock{cpu_addr, size} { - gl_buffer.Create(); - glNamedBufferData(gl_buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW); - if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) { - glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_WRITE); - glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address); +void Buffer::MakeResident(GLenum access) noexcept { + // Abuse GLenum's order to exit early + // GL_NONE (default) < GL_READ_ONLY < GL_READ_WRITE + if (access <= current_residency_access || buffer.handle == 0) { + return; + } + if (std::exchange(current_residency_access, access) != GL_NONE) { + // If the buffer is already resident, remove its residency before promoting it + glMakeNamedBufferNonResidentNV(buffer.handle); } + glMakeNamedBufferResidentNV(buffer.handle, access); } -Buffer::~Buffer() = default; - -void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) { - glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), - data); +BufferCacheRuntime::BufferCacheRuntime(const Device& device_) + : device{device_}, has_fast_buffer_sub_data{device.HasFastBufferSubData()}, + use_assembly_shaders{device.UseAssemblyShaders()}, + has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()}, + stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} { + GLint gl_max_attributes; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes); + max_attributes = static_cast<u32>(gl_max_attributes); + for (auto& stage_uniforms : fast_uniforms) { + for (OGLBuffer& buffer : stage_uniforms) { + buffer.Create(); + glNamedBufferData(buffer.handle, BufferCache::SKIP_CACHE_SIZE, nullptr, GL_STREAM_DRAW); + } + } + for (auto& stage_uniforms : copy_uniforms) { + for (OGLBuffer& buffer : stage_uniforms) { + buffer.Create(); + glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY); + } + } + for (OGLBuffer& buffer : copy_compute_uniforms) { + buffer.Create(); + glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY); + } } -void Buffer::Download(std::size_t offset, std::size_t size, u8* data) { - MICROPROFILE_SCOPE(OpenGL_Buffer_Download); - const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size); - const GLintptr gl_offset = static_cast<GLintptr>(offset); - if (read_buffer.handle == 0) { - read_buffer.Create(); - glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr, - GL_STREAM_READ); +void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, + std::span<const VideoCommon::BufferCopy> copies) { + for (const VideoCommon::BufferCopy& copy : copies) { + glCopyNamedBufferSubData( + src_buffer.Handle(), dst_buffer.Handle(), static_cast<GLintptr>(copy.src_offset), + static_cast<GLintptr>(copy.dst_offset), static_cast<GLsizeiptr>(copy.size)); } - glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); - glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size); - glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data); } -void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) { - glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset), - static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size)); +void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) { + if (has_unified_vertex_buffers) { + buffer.MakeResident(GL_READ_ONLY); + glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset, + static_cast<GLsizeiptr>(size)); + } else { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle()); + index_buffer_offset = offset; + } } -OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, - const Device& device_, std::size_t stream_size) - : GenericBufferCache{rasterizer, system, - std::make_unique<OGLStreamBuffer>(device_, stream_size, true)}, - device{device_} { - if (!device.HasFastBufferSubData()) { +void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, + u32 stride) { + if (index >= max_attributes) { return; } - - static constexpr GLsizeiptr size = static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize); - glCreateBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs)); - for (const GLuint cbuf : cbufs) { - glNamedBufferData(cbuf, size, nullptr, GL_STREAM_DRAW); + if (has_unified_vertex_buffers) { + buffer.MakeResident(GL_READ_ONLY); + glBindVertexBuffer(index, 0, 0, static_cast<GLsizei>(stride)); + glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index, + buffer.HostGpuAddr() + offset, static_cast<GLsizeiptr>(size)); + } else { + glBindVertexBuffer(index, buffer.Handle(), static_cast<GLintptr>(offset), + static_cast<GLsizei>(stride)); } } -OGLBufferCache::~OGLBufferCache() { - glDeleteBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs)); +void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, + u32 offset, u32 size) { + if (use_assembly_shaders) { + GLuint handle; + if (offset != 0) { + handle = copy_uniforms[stage][binding_index].handle; + glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size); + } else { + handle = buffer.Handle(); + } + glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0, + static_cast<GLsizeiptr>(size)); + } else { + const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer; + const GLuint binding = base_binding + binding_index; + glBindBufferRange(GL_UNIFORM_BUFFER, binding, buffer.Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); + } } -std::shared_ptr<Buffer> OGLBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) { - return std::make_shared<Buffer>(device, cpu_addr, size); +void BufferCacheRuntime::BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset, + u32 size) { + if (use_assembly_shaders) { + GLuint handle; + if (offset != 0) { + handle = copy_compute_uniforms[binding_index].handle; + glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size); + } else { + handle = buffer.Handle(); + } + glBindBufferRangeNV(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding_index, handle, 0, + static_cast<GLsizeiptr>(size)); + } else { + glBindBufferRange(GL_UNIFORM_BUFFER, binding_index, buffer.Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); + } } -OGLBufferCache::BufferInfo OGLBufferCache::GetEmptyBuffer(std::size_t) { - return {0, 0, 0}; +void BufferCacheRuntime::BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer, + u32 offset, u32 size, bool is_written) { + if (use_assembly_shaders) { + const BindlessSSBO ssbo{ + .address = buffer.HostGpuAddr() + offset, + .length = static_cast<GLsizei>(size), + .padding = 0, + }; + buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY); + glProgramLocalParametersI4uivNV(PROGRAM_LUT[stage], binding_index, 1, + reinterpret_cast<const GLuint*>(&ssbo)); + } else { + const GLuint base_binding = device.GetBaseBindings(stage).shader_storage_buffer; + const GLuint binding = base_binding + binding_index; + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, buffer.Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); + } } -OGLBufferCache::BufferInfo OGLBufferCache::ConstBufferUpload(const void* raw_pointer, - std::size_t size) { - DEBUG_ASSERT(cbuf_cursor < std::size(cbufs)); - const GLuint cbuf = cbufs[cbuf_cursor++]; +void BufferCacheRuntime::BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset, + u32 size, bool is_written) { + if (use_assembly_shaders) { + const BindlessSSBO ssbo{ + .address = buffer.HostGpuAddr() + offset, + .length = static_cast<GLsizei>(size), + .padding = 0, + }; + buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY); + glProgramLocalParametersI4uivNV(GL_COMPUTE_PROGRAM_NV, binding_index, 1, + reinterpret_cast<const GLuint*>(&ssbo)); + } else if (size == 0) { + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, 0, 0, 0); + } else { + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, buffer.Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); + } +} - glNamedBufferSubData(cbuf, 0, static_cast<GLsizeiptr>(size), raw_pointer); - return {cbuf, 0, 0}; +void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, + u32 size) { + glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer.Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 88fdc0536..d8b20a9af 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -5,76 +5,157 @@ #pragma once #include <array> -#include <memory> +#include <span> +#include "common/alignment.h" #include "common/common_types.h" +#include "common/dynamic_library.h" #include "video_core/buffer_cache/buffer_cache.h" -#include "video_core/engines/maxwell_3d.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_stream_buffer.h" -namespace Core { -class System; -} - namespace OpenGL { -class Device; -class OGLStreamBuffer; -class RasterizerOpenGL; +class BufferCacheRuntime; -class Buffer : public VideoCommon::BufferBlock { +class Buffer : public VideoCommon::BufferBase<VideoCore::RasterizerInterface> { public: - explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size); - ~Buffer(); + explicit Buffer(BufferCacheRuntime&, VideoCore::RasterizerInterface& rasterizer, VAddr cpu_addr, + u64 size_bytes); + explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams); - void Upload(std::size_t offset, std::size_t size, const u8* data); + void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept; - void Download(std::size_t offset, std::size_t size, u8* data); + void ImmediateDownload(size_t offset, std::span<u8> data) noexcept; - void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size); + void MakeResident(GLenum access) noexcept; - GLuint Handle() const noexcept { - return gl_buffer.handle; + [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept { + return address; } - u64 Address() const noexcept { - return gpu_address; + [[nodiscard]] GLuint Handle() const noexcept { + return buffer.handle; } private: - OGLBuffer gl_buffer; - OGLBuffer read_buffer; - u64 gpu_address = 0; + GLuint64EXT address = 0; + OGLBuffer buffer; + GLenum current_residency_access = GL_NONE; }; -using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>; -class OGLBufferCache final : public GenericBufferCache { +class BufferCacheRuntime { + friend Buffer; + public: - explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, - const Device& device, std::size_t stream_size); - ~OGLBufferCache(); + static constexpr u8 INVALID_BINDING = std::numeric_limits<u8>::max(); + + explicit BufferCacheRuntime(const Device& device_); + + void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, + std::span<const VideoCommon::BufferCopy> copies); + + void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); + + void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); + + void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); + + void BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size); + + void BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size, + bool is_written); + + void BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size, + bool is_written); + + void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size); + + void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) { + if (use_assembly_shaders) { + const GLuint handle = fast_uniforms[stage][binding_index].handle; + const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size); + glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0, gl_size); + } else { + const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer; + const GLuint binding = base_binding + binding_index; + glBindBufferRange(GL_UNIFORM_BUFFER, binding, + fast_uniforms[stage][binding_index].handle, 0, + static_cast<GLsizeiptr>(size)); + } + } - BufferInfo GetEmptyBuffer(std::size_t) override; + void PushFastUniformBuffer(size_t stage, u32 binding_index, std::span<const u8> data) { + if (use_assembly_shaders) { + glProgramBufferParametersIuivNV( + PABO_LUT[stage], binding_index, 0, + static_cast<GLsizei>(data.size_bytes() / sizeof(GLuint)), + reinterpret_cast<const GLuint*>(data.data())); + } else { + glNamedBufferSubData(fast_uniforms[stage][binding_index].handle, 0, + static_cast<GLsizeiptr>(data.size_bytes()), data.data()); + } + } - void Acquire() noexcept { - cbuf_cursor = 0; + std::span<u8> BindMappedUniformBuffer(size_t stage, u32 binding_index, u32 size) noexcept { + const auto [mapped_span, offset] = stream_buffer->Request(static_cast<size_t>(size)); + const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer; + const GLuint binding = base_binding + binding_index; + glBindBufferRange(GL_UNIFORM_BUFFER, binding, stream_buffer->Handle(), + static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size)); + return mapped_span; } -protected: - std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override; + [[nodiscard]] const GLvoid* IndexOffset() const noexcept { + return reinterpret_cast<const GLvoid*>(static_cast<uintptr_t>(index_buffer_offset)); + } - BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) override; + [[nodiscard]] bool HasFastBufferSubData() const noexcept { + return has_fast_buffer_sub_data; + } private: - static constexpr std::size_t NUM_CBUFS = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers * - Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; + static constexpr std::array PABO_LUT{ + GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV, + GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV, + GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV, + }; const Device& device; - std::size_t cbuf_cursor = 0; - std::array<GLuint, NUM_CBUFS> cbufs{}; + bool has_fast_buffer_sub_data = false; + bool use_assembly_shaders = false; + bool has_unified_vertex_buffers = false; + + u32 max_attributes = 0; + + std::optional<StreamBuffer> stream_buffer; + + std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>, + VideoCommon::NUM_STAGES> + fast_uniforms; + std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>, + VideoCommon::NUM_STAGES> + copy_uniforms; + std::array<OGLBuffer, VideoCommon::NUM_COMPUTE_UNIFORM_BUFFERS> copy_compute_uniforms; + + u32 index_buffer_offset = 0; +}; + +struct BufferCacheParams { + using Runtime = OpenGL::BufferCacheRuntime; + using Buffer = OpenGL::Buffer; + + static constexpr bool IS_OPENGL = true; + static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = true; + static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true; + static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true; + static constexpr bool NEEDS_BIND_STORAGE_INDEX = true; + static constexpr bool USE_MEMORY_MAPS = false; }; +using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index e7d95149f..48d5c4a5e 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -5,9 +5,11 @@ #include <algorithm> #include <array> #include <cstddef> +#include <cstdlib> #include <cstring> #include <limits> #include <optional> +#include <span> #include <vector> #include <glad/glad.h> @@ -19,35 +21,35 @@ #include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { - namespace { - // One uniform block is reserved for emulation purposes constexpr u32 ReservedUniformBlocks = 1; constexpr u32 NumStages = 5; -constexpr std::array LimitUBOs = { +constexpr std::array LIMIT_UBOS = { GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_GEOMETRY_UNIFORM_BLOCKS, - GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMPUTE_UNIFORM_BLOCKS}; - -constexpr std::array LimitSSBOs = { + GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMPUTE_UNIFORM_BLOCKS, +}; +constexpr std::array LIMIT_SSBOS = { GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, - GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS}; - -constexpr std::array LimitSamplers = {GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, - GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, - GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, - GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, - GL_MAX_TEXTURE_IMAGE_UNITS, - GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS}; - -constexpr std::array LimitImages = { + GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, +}; +constexpr std::array LIMIT_SAMPLERS = { + GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, + GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, + GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, + GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, + GL_MAX_TEXTURE_IMAGE_UNITS, + GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, +}; +constexpr std::array LIMIT_IMAGES = { GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS, GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, GL_MAX_GEOMETRY_IMAGE_UNIFORMS, - GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMPUTE_IMAGE_UNIFORMS}; + GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMPUTE_IMAGE_UNIFORMS, +}; template <typename T> T GetInteger(GLenum pname) { @@ -76,8 +78,8 @@ std::vector<std::string_view> GetExtensions() { return extensions; } -bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) { - return std::find(images.begin(), images.end(), extension) != images.end(); +bool HasExtension(std::span<const std::string_view> extensions, std::string_view extension) { + return std::ranges::find(extensions, extension) != extensions.end(); } u32 Extract(u32& base, u32& num, u32 amount, std::optional<GLenum> limit = {}) { @@ -91,8 +93,8 @@ u32 Extract(u32& base, u32& num, u32 amount, std::optional<GLenum> limit = {}) { std::array<u32, Tegra::Engines::MaxShaderTypes> BuildMaxUniformBuffers() noexcept { std::array<u32, Tegra::Engines::MaxShaderTypes> max; - std::transform(LimitUBOs.begin(), LimitUBOs.end(), max.begin(), - [](GLenum pname) { return GetInteger<u32>(pname); }); + std::ranges::transform(LIMIT_UBOS, max.begin(), + [](GLenum pname) { return GetInteger<u32>(pname); }); return max; } @@ -115,9 +117,10 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin for (std::size_t i = 0; i < NumStages; ++i) { const std::size_t stage = stage_swizzle[i]; bindings[stage] = { - Extract(base_ubo, num_ubos, total_ubos / NumStages, LimitUBOs[stage]), - Extract(base_ssbo, num_ssbos, total_ssbos / NumStages, LimitSSBOs[stage]), - Extract(base_samplers, num_samplers, total_samplers / NumStages, LimitSamplers[stage])}; + Extract(base_ubo, num_ubos, total_ubos / NumStages, LIMIT_UBOS[stage]), + Extract(base_ssbo, num_ssbos, total_ssbos / NumStages, LIMIT_SSBOS[stage]), + Extract(base_samplers, num_samplers, total_samplers / NumStages, + LIMIT_SAMPLERS[stage])}; } u32 num_images = GetInteger<u32>(GL_MAX_IMAGE_UNITS); @@ -130,7 +133,7 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin // Reserve at least 4 image bindings on the fragment stage. bindings[4].image = - Extract(base_images, num_images, std::max(4U, num_images / NumStages), LimitImages[4]); + Extract(base_images, num_images, std::max(4U, num_images / NumStages), LIMIT_IMAGES[4]); // This is guaranteed to be at least 1. const u32 total_extracted_images = num_images / (NumStages - 1); @@ -142,7 +145,7 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin continue; } bindings[stage].image = - Extract(base_images, num_images, total_extracted_images, LimitImages[stage]); + Extract(base_images, num_images, total_extracted_images, LIMIT_IMAGES[stage]); } // Compute doesn't care about any of this. @@ -188,17 +191,24 @@ bool IsASTCSupported() { return true; } +[[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) { + const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED"); + return nsight || HasExtension(extensions, "GL_EXT_debug_tool"); +} } // Anonymous namespace -Device::Device() - : max_uniform_buffers{BuildMaxUniformBuffers()}, base_bindings{BuildBaseBindings()} { +Device::Device() { + if (!GLAD_GL_VERSION_4_6) { + LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available"); + throw std::runtime_error{"Insufficient version"}; + } const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); - const std::string_view renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER)); const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); const std::vector extensions = GetExtensions(); const bool is_nvidia = vendor == "NVIDIA Corporation"; const bool is_amd = vendor == "ATI Technologies Inc."; + const bool is_intel = vendor == "Intel"; bool disable_fast_buffer_sub_data = false; if (is_nvidia && version == "4.6.0 NVIDIA 443.24") { @@ -208,8 +218,10 @@ Device::Device() disable_fast_buffer_sub_data = true; } - uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); - shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); + max_uniform_buffers = BuildMaxUniformBuffers(); + base_bindings = BuildBaseBindings(); + uniform_buffer_alignment = GetInteger<size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); + shader_storage_alignment = GetInteger<size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS); max_compute_shared_memory_size = GetInteger<u32>(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE); @@ -223,8 +235,10 @@ Device::Device() has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = is_amd; has_precise_bug = TestPreciseBug(); + has_broken_texture_view_formats = is_amd || is_intel; has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2; has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory; + has_debugging_tool_attached = IsDebugToolAttached(extensions); // At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive // uniform buffers as "push constants" @@ -235,10 +249,13 @@ Device::Device() GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2; use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue(); + use_driver_cache = is_nvidia; LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); + LOG_INFO(Render_OpenGL, "Renderer_BrokenTextureViewFormats: {}", + has_broken_texture_view_formats); if (Settings::values.use_assembly_shaders.GetValue() && !use_assembly_shaders) { LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported"); diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index 8a4b6b9fc..ee053776d 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -10,11 +10,9 @@ namespace OpenGL { -static constexpr u32 EmulationUniformBlockBinding = 0; - -class Device final { +class Device { public: - struct BaseBindings final { + struct BaseBindings { u32 uniform_buffer{}; u32 shader_storage_buffer{}; u32 sampler{}; @@ -36,11 +34,11 @@ public: return GetBaseBindings(static_cast<std::size_t>(shader_type)); } - std::size_t GetUniformBufferAlignment() const { + size_t GetUniformBufferAlignment() const { return uniform_buffer_alignment; } - std::size_t GetShaderStorageBufferAlignment() const { + size_t GetShaderStorageBufferAlignment() const { return shader_storage_alignment; } @@ -96,6 +94,10 @@ public: return has_precise_bug; } + bool HasBrokenTextureViewFormats() const { + return has_broken_texture_view_formats; + } + bool HasFastBufferSubData() const { return has_fast_buffer_sub_data; } @@ -104,6 +106,10 @@ public: return has_nv_viewport_array2; } + bool HasDebuggingToolAttached() const { + return has_debugging_tool_attached; + } + bool UseAssemblyShaders() const { return use_assembly_shaders; } @@ -112,14 +118,18 @@ public: return use_asynchronous_shaders; } + bool UseDriverCache() const { + return use_driver_cache; + } + private: static bool TestVariableAoffi(); static bool TestPreciseBug(); std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{}; std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{}; - std::size_t uniform_buffer_alignment{}; - std::size_t shader_storage_alignment{}; + size_t uniform_buffer_alignment{}; + size_t shader_storage_alignment{}; u32 max_vertex_attributes{}; u32 max_varyings{}; u32 max_compute_shared_memory_size{}; @@ -133,10 +143,13 @@ private: bool has_variable_aoffi{}; bool has_component_indexing_bug{}; bool has_precise_bug{}; + bool has_broken_texture_view_formats{}; bool has_fast_buffer_sub_data{}; bool has_nv_viewport_array2{}; + bool has_debugging_tool_attached{}; bool use_assembly_shaders{}; bool use_asynchronous_shaders{}; + bool use_driver_cache{}; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp index 3d2588dd2..151290101 100644 --- a/src/video_core/renderer_opengl/gl_fence_manager.cpp +++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp @@ -11,10 +11,10 @@ namespace OpenGL { -GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {} +GLInnerFence::GLInnerFence(u32 payload_, bool is_stubbed_) : FenceBase{payload_, is_stubbed_} {} -GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed) - : FenceBase(address, payload, is_stubbed) {} +GLInnerFence::GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_) + : FenceBase{address_, payload_, is_stubbed_} {} GLInnerFence::~GLInnerFence() = default; @@ -45,11 +45,10 @@ void GLInnerFence::Wait() { glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED); } -FenceManagerOpenGL::FenceManagerOpenGL(Core::System& system, - VideoCore::RasterizerInterface& rasterizer, - TextureCacheOpenGL& texture_cache, - OGLBufferCache& buffer_cache, QueryCache& query_cache) - : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache) {} +FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer_, + Tegra::GPU& gpu_, TextureCache& texture_cache_, + BufferCache& buffer_cache_, QueryCache& query_cache_) + : GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_} {} Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) { return std::make_shared<GLInnerFence>(value, is_stubbed); diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h index 1686cf5c8..e714aa115 100644 --- a/src/video_core/renderer_opengl/gl_fence_manager.h +++ b/src/video_core/renderer_opengl/gl_fence_manager.h @@ -17,8 +17,8 @@ namespace OpenGL { class GLInnerFence : public VideoCommon::FenceBase { public: - GLInnerFence(u32 payload, bool is_stubbed); - GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed); + explicit GLInnerFence(u32 payload_, bool is_stubbed_); + explicit GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_); ~GLInnerFence(); void Queue(); @@ -32,14 +32,13 @@ private: }; using Fence = std::shared_ptr<GLInnerFence>; -using GenericFenceManager = - VideoCommon::FenceManager<Fence, TextureCacheOpenGL, OGLBufferCache, QueryCache>; +using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>; class FenceManagerOpenGL final : public GenericFenceManager { public: - FenceManagerOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache, - QueryCache& query_cache); + explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu, + TextureCache& texture_cache, BufferCache& buffer_cache, + QueryCache& query_cache); protected: Fence CreateFence(u32 value, bool is_stubbed) override; diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp deleted file mode 100644 index b8a512cb6..000000000 --- a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <tuple> -#include <unordered_map> -#include <utility> - -#include <glad/glad.h> - -#include "common/common_types.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/renderer_opengl/gl_framebuffer_cache.h" - -namespace OpenGL { - -using Maxwell = Tegra::Engines::Maxwell3D::Regs; -using VideoCore::Surface::SurfaceType; - -FramebufferCacheOpenGL::FramebufferCacheOpenGL() = default; - -FramebufferCacheOpenGL::~FramebufferCacheOpenGL() = default; - -GLuint FramebufferCacheOpenGL::GetFramebuffer(const FramebufferCacheKey& key) { - const auto [entry, is_cache_miss] = cache.try_emplace(key); - auto& framebuffer{entry->second}; - if (is_cache_miss) { - framebuffer = CreateFramebuffer(key); - } - return framebuffer.handle; -} - -OGLFramebuffer FramebufferCacheOpenGL::CreateFramebuffer(const FramebufferCacheKey& key) { - OGLFramebuffer framebuffer; - framebuffer.Create(); - - // TODO(Rodrigo): Use DSA here after Nvidia fixes their framebuffer DSA bugs. - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle); - - if (key.zeta) { - const bool stencil = key.zeta->GetSurfaceParams().type == SurfaceType::DepthStencil; - const GLenum attach_target = stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; - key.zeta->Attach(attach_target, GL_DRAW_FRAMEBUFFER); - } - - std::size_t num_buffers = 0; - std::array<GLenum, Maxwell::NumRenderTargets> targets; - - for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { - if (!key.colors[index]) { - targets[index] = GL_NONE; - continue; - } - const GLenum attach_target = GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index); - key.colors[index]->Attach(attach_target, GL_DRAW_FRAMEBUFFER); - - const u32 attachment = (key.color_attachments >> (BitsPerAttachment * index)) & 0b1111; - targets[index] = GL_COLOR_ATTACHMENT0 + attachment; - num_buffers = index + 1; - } - - if (num_buffers > 0) { - glDrawBuffers(static_cast<GLsizei>(num_buffers), std::data(targets)); - } else { - glDrawBuffer(GL_NONE); - } - - return framebuffer; -} - -std::size_t FramebufferCacheKey::Hash() const noexcept { - std::size_t hash = std::hash<View>{}(zeta); - for (const auto& color : colors) { - hash ^= std::hash<View>{}(color); - } - hash ^= static_cast<std::size_t>(color_attachments) << 16; - return hash; -} - -bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const noexcept { - return std::tie(colors, zeta, color_attachments) == - std::tie(rhs.colors, rhs.zeta, rhs.color_attachments); -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.h b/src/video_core/renderer_opengl/gl_framebuffer_cache.h deleted file mode 100644 index 8f698fee0..000000000 --- a/src/video_core/renderer_opengl/gl_framebuffer_cache.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <cstddef> -#include <unordered_map> - -#include <glad/glad.h> - -#include "common/common_types.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_texture_cache.h" - -namespace OpenGL { - -constexpr std::size_t BitsPerAttachment = 4; - -struct FramebufferCacheKey { - View zeta; - std::array<View, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors; - u32 color_attachments = 0; - - std::size_t Hash() const noexcept; - - bool operator==(const FramebufferCacheKey& rhs) const noexcept; - - bool operator!=(const FramebufferCacheKey& rhs) const noexcept { - return !operator==(rhs); - } - - void SetAttachment(std::size_t index, u32 attachment) { - color_attachments |= attachment << (BitsPerAttachment * index); - } -}; - -} // namespace OpenGL - -namespace std { - -template <> -struct hash<OpenGL::FramebufferCacheKey> { - std::size_t operator()(const OpenGL::FramebufferCacheKey& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace OpenGL { - -class FramebufferCacheOpenGL { -public: - FramebufferCacheOpenGL(); - ~FramebufferCacheOpenGL(); - - GLuint GetFramebuffer(const FramebufferCacheKey& key); - -private: - OGLFramebuffer CreateFramebuffer(const FramebufferCacheKey& key); - - std::unordered_map<FramebufferCacheKey, OGLFramebuffer> cache; -}; - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp index d7ba57aca..acebbf5f4 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.cpp +++ b/src/video_core/renderer_opengl/gl_query_cache.cpp @@ -30,12 +30,9 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) { } // Anonymous namespace -QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer) - : VideoCommon::QueryCacheBase< - QueryCache, CachedQuery, CounterStream, HostCounter, - std::vector<OGLQuery>>{system, - static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)}, - gl_rasterizer{gl_rasterizer} {} +QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::MemoryManager& gpu_memory_) + : QueryCacheBase(rasterizer_, maxwell3d_, gpu_memory_), gl_rasterizer{rasterizer_} {} QueryCache::~QueryCache() = default; @@ -60,10 +57,11 @@ bool QueryCache::AnyCommandQueued() const noexcept { return gl_rasterizer.AnyCommandQueued(); } -HostCounter::HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency, - VideoCore::QueryType type) - : VideoCommon::HostCounterBase<QueryCache, HostCounter>{std::move(dependency)}, cache{cache}, - type{type}, query{cache.AllocateQuery(type)} { +HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, + VideoCore::QueryType type_) + : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, query{ + cache.AllocateQuery( + type)} { glBeginQuery(GetTarget(type), query.handle); } @@ -87,11 +85,14 @@ u64 HostCounter::BlockingQuery() const { return static_cast<u64>(value); } -CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr) - : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {} +CachedQuery::CachedQuery(QueryCache& cache_, VideoCore::QueryType type_, VAddr cpu_addr_, + u8* host_ptr_) + : CachedQueryBase{cpu_addr_, host_ptr_}, cache{&cache_}, type{type_} {} + +CachedQuery::~CachedQuery() = default; CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept - : VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {} + : CachedQueryBase(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {} CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept { cache = rhs.cache; diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h index d8e7052a1..7bbe5cfe9 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.h +++ b/src/video_core/renderer_opengl/gl_query_cache.h @@ -26,10 +26,11 @@ class RasterizerOpenGL; using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; -class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, - HostCounter, std::vector<OGLQuery>> { +class QueryCache final + : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> { public: - explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer); + explicit QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::MemoryManager& gpu_memory_); ~QueryCache(); OGLQuery AllocateQuery(VideoCore::QueryType type); @@ -40,12 +41,13 @@ public: private: RasterizerOpenGL& gl_rasterizer; + std::array<std::vector<OGLQuery>, VideoCore::NumQueryTypes> query_pools; }; class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { public: - explicit HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency, - VideoCore::QueryType type); + explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_, + VideoCore::QueryType type_); ~HostCounter(); void EndQuery(); @@ -60,12 +62,14 @@ private: class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> { public: - explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, - u8* host_ptr); - CachedQuery(CachedQuery&& rhs) noexcept; - CachedQuery(const CachedQuery&) = delete; + explicit CachedQuery(QueryCache& cache_, VideoCore::QueryType type_, VAddr cpu_addr_, + u8* host_ptr_); + ~CachedQuery() override; + CachedQuery(CachedQuery&& rhs) noexcept; CachedQuery& operator=(CachedQuery&& rhs) noexcept; + + CachedQuery(const CachedQuery&) = delete; CachedQuery& operator=(const CachedQuery&) = delete; void Flush() override; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 4af5824cd..418644108 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -25,48 +25,49 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/shader_type.h" #include "video_core/memory_manager.h" +#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_query_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_cache.h" +#include "video_core/renderer_opengl/gl_texture_cache.h" #include "video_core/renderer_opengl/maxwell_to_gl.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/shader_cache.h" +#include "video_core/texture_cache/texture_cache.h" namespace OpenGL { using Maxwell = Tegra::Engines::Maxwell3D::Regs; +using GLvec4 = std::array<GLfloat, 4>; using Tegra::Engines::ShaderType; using VideoCore::Surface::PixelFormat; using VideoCore::Surface::SurfaceTarget; using VideoCore::Surface::SurfaceType; -MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Format Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_VB, "OpenGL", "Vertex Buffer Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_Shader, "OpenGL", "Shader Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_UBO, "OpenGL", "Const Buffer Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_Index, "OpenGL", "Index Buffer Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_Texture, "OpenGL", "Texture Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Clears, "OpenGL", "Clears", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); -MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100)); +MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Management", MP_RGB(100, 255, 100)); namespace { -constexpr std::size_t NUM_CONST_BUFFERS_PER_STAGE = 18; -constexpr std::size_t NUM_CONST_BUFFERS_BYTES_PER_STAGE = - NUM_CONST_BUFFERS_PER_STAGE * Maxwell::MaxConstBufferSize; -constexpr std::size_t TOTAL_CONST_BUFFER_BYTES = - NUM_CONST_BUFFERS_BYTES_PER_STAGE * Maxwell::MaxShaderStage; +constexpr size_t NUM_SUPPORTED_VERTEX_ATTRIBUTES = 16; -constexpr std::size_t NUM_SUPPORTED_VERTEX_ATTRIBUTES = 16; -constexpr std::size_t NUM_SUPPORTED_VERTEX_BINDINGS = 16; +struct TextureHandle { + constexpr TextureHandle(u32 data, bool via_header_index) { + const Tegra::Texture::TextureHandle handle{data}; + image = handle.tic_id; + sampler = via_header_index ? image : handle.tsc_id.Value(); + } + + u32 image; + u32 sampler; +}; template <typename Engine, typename Entry> -Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry, - ShaderType shader_type, std::size_t index = 0) { +TextureHandle GetTextureInfo(const Engine& engine, bool via_header_index, const Entry& entry, + ShaderType shader_type, size_t index = 0) { if constexpr (std::is_same_v<Entry, SamplerEntry>) { if (entry.is_separated) { const u32 buffer_1 = entry.buffer; @@ -75,36 +76,16 @@ Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry const u32 offset_2 = entry.secondary_offset; const u32 handle_1 = engine.AccessConstBuffer32(shader_type, buffer_1, offset_1); const u32 handle_2 = engine.AccessConstBuffer32(shader_type, buffer_2, offset_2); - return engine.GetTextureInfo(handle_1 | handle_2); + return TextureHandle(handle_1 | handle_2, via_header_index); } } if (entry.is_bindless) { - const u32 handle = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset); - return engine.GetTextureInfo(handle); - } - - const auto& gpu_profile = engine.AccessGuestDriverProfile(); - const u32 offset = entry.offset + static_cast<u32>(index * gpu_profile.GetTextureHandlerSize()); - if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) { - return engine.GetStageTexture(shader_type, offset); - } else { - return engine.GetTexture(offset); - } -} - -std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer, - const ConstBufferEntry& entry) { - if (!entry.IsIndirect()) { - return entry.GetSize(); + const u32 raw = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset); + return TextureHandle(raw, via_header_index); } - - if (buffer.size > Maxwell::MaxConstBufferSize) { - LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", buffer.size, - Maxwell::MaxConstBufferSize); - return Maxwell::MaxConstBufferSize; - } - - return buffer.size; + const u32 buffer = engine.GetBoundBuffer(); + const u64 offset = (entry.offset + index) * sizeof(u32); + return TextureHandle(engine.AccessConstBuffer32(shader_type, buffer, offset), via_header_index); } /// Translates hardware transform feedback indices @@ -131,7 +112,7 @@ std::pair<GLint, GLint> TransformFeedbackEnum(u8 location) { case 43: return {GL_BACK_SECONDARY_COLOR_NV, 0}; } - UNIMPLEMENTED_MSG("index={}", static_cast<int>(index)); + UNIMPLEMENTED_MSG("index={}", index); return {GL_POSITION, 0}; } @@ -139,72 +120,74 @@ void oglEnable(GLenum cap, bool state) { (state ? glEnable : glDisable)(cap); } -void UpdateBindlessPointers(GLenum target, GLuint64EXT* pointers, std::size_t num_entries) { - if (num_entries == 0) { - return; +ImageViewType ImageViewTypeFromEntry(const SamplerEntry& entry) { + if (entry.is_buffer) { + return ImageViewType::Buffer; } - if (num_entries % 2 == 1) { - pointers[num_entries] = 0; + switch (entry.type) { + case Tegra::Shader::TextureType::Texture1D: + return entry.is_array ? ImageViewType::e1DArray : ImageViewType::e1D; + case Tegra::Shader::TextureType::Texture2D: + return entry.is_array ? ImageViewType::e2DArray : ImageViewType::e2D; + case Tegra::Shader::TextureType::Texture3D: + return ImageViewType::e3D; + case Tegra::Shader::TextureType::TextureCube: + return entry.is_array ? ImageViewType::CubeArray : ImageViewType::Cube; } - const GLsizei num_vectors = static_cast<GLsizei>((num_entries + 1) / 2); - glProgramLocalParametersI4uivNV(target, 0, num_vectors, - reinterpret_cast<const GLuint*>(pointers)); + UNREACHABLE(); + return ImageViewType::e2D; } -} // Anonymous namespace - -RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, - const Device& device, ScreenInfo& info, - ProgramManager& program_manager, StateTracker& state_tracker) - : RasterizerAccelerated{system.Memory()}, device{device}, texture_cache{system, *this, device, - state_tracker}, - shader_cache{*this, system, emu_window, device}, query_cache{system, *this}, - buffer_cache{*this, system, device, STREAM_BUFFER_SIZE}, - fence_manager{system, *this, texture_cache, buffer_cache, query_cache}, system{system}, - screen_info{info}, program_manager{program_manager}, state_tracker{state_tracker}, - async_shaders{emu_window} { - CheckExtensions(); - - unified_uniform_buffer.Create(); - glNamedBufferStorage(unified_uniform_buffer.handle, TOTAL_CONST_BUFFER_BYTES, nullptr, 0); - - if (device.UseAssemblyShaders()) { - glCreateBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data()); - for (const GLuint cbuf : staging_cbufs) { - glNamedBufferStorage(cbuf, static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize), - nullptr, 0); - } +ImageViewType ImageViewTypeFromEntry(const ImageEntry& entry) { + switch (entry.type) { + case Tegra::Shader::ImageType::Texture1D: + return ImageViewType::e1D; + case Tegra::Shader::ImageType::Texture1DArray: + return ImageViewType::e1DArray; + case Tegra::Shader::ImageType::Texture2D: + return ImageViewType::e2D; + case Tegra::Shader::ImageType::Texture2DArray: + return ImageViewType::e2DArray; + case Tegra::Shader::ImageType::Texture3D: + return ImageViewType::e3D; + case Tegra::Shader::ImageType::TextureBuffer: + return ImageViewType::Buffer; } + UNREACHABLE(); + return ImageViewType::e2D; +} + +} // Anonymous namespace +RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_, + Core::Memory::Memory& cpu_memory_, const Device& device_, + ScreenInfo& screen_info_, ProgramManager& program_manager_, + StateTracker& state_tracker_) + : RasterizerAccelerated(cpu_memory_), gpu(gpu_), maxwell3d(gpu.Maxwell3D()), + kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_), + screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_), + texture_cache_runtime(device, program_manager, state_tracker), + texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory), + buffer_cache_runtime(device), + buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime), + shader_cache(*this, emu_window_, gpu, maxwell3d, kepler_compute, gpu_memory, device), + query_cache(*this, maxwell3d, gpu_memory), + fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache), + async_shaders(emu_window_) { if (device.UseAsynchronousShaders()) { async_shaders.AllocateWorkers(); } } -RasterizerOpenGL::~RasterizerOpenGL() { - if (device.UseAssemblyShaders()) { - glDeleteBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data()); - } -} - -void RasterizerOpenGL::CheckExtensions() { - if (!GLAD_GL_ARB_texture_filter_anisotropic && !GLAD_GL_EXT_texture_filter_anisotropic) { - LOG_WARNING( - Render_OpenGL, - "Anisotropic filter is not supported! This can cause graphical issues in some games."); - } -} +RasterizerOpenGL::~RasterizerOpenGL() = default; -void RasterizerOpenGL::SetupVertexFormat() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; +void RasterizerOpenGL::SyncVertexFormats() { + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::VertexFormats]) { return; } flags[Dirty::VertexFormats] = false; - MICROPROFILE_SCOPE(OpenGL_VAO); - // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. Enables // the first 16 vertex attributes always, as we don't know which ones are actually used until // shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now to @@ -217,7 +200,7 @@ void RasterizerOpenGL::SetupVertexFormat() { } flags[Dirty::VertexFormat0 + index] = false; - const auto attrib = gpu.regs.vertex_attrib_format[index]; + const auto attrib = maxwell3d.regs.vertex_attrib_format[index]; const auto gl_index = static_cast<GLuint>(index); // Disable constant attributes. @@ -240,64 +223,14 @@ void RasterizerOpenGL::SetupVertexFormat() { } } -void RasterizerOpenGL::SetupVertexBuffer() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; - if (!flags[Dirty::VertexBuffers]) { - return; - } - flags[Dirty::VertexBuffers] = false; - - MICROPROFILE_SCOPE(OpenGL_VB); - - const bool use_unified_memory = device.HasVertexBufferUnifiedMemory(); - - // Upload all guest vertex arrays sequentially to our buffer - const auto& regs = gpu.regs; - for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) { - if (!flags[Dirty::VertexBuffer0 + index]) { - continue; - } - flags[Dirty::VertexBuffer0 + index] = false; - - const auto& vertex_array = regs.vertex_array[index]; - if (!vertex_array.IsEnabled()) { - continue; - } - - const GPUVAddr start = vertex_array.StartAddress(); - const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); - ASSERT(end >= start); - - const GLuint gl_index = static_cast<GLuint>(index); - const u64 size = end - start; - if (size == 0) { - glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride); - if (use_unified_memory) { - glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index, 0, 0); - } - continue; - } - const auto info = buffer_cache.UploadMemory(start, size); - if (use_unified_memory) { - glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride); - glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index, - info.address + info.offset, size); - } else { - glBindVertexBuffer(gl_index, info.handle, info.offset, vertex_array.stride); - } - } -} - -void RasterizerOpenGL::SetupVertexInstances() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; +void RasterizerOpenGL::SyncVertexInstances() { + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::VertexInstances]) { return; } flags[Dirty::VertexInstances] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) { if (!flags[Dirty::VertexInstance0 + index]) { continue; @@ -311,26 +244,21 @@ void RasterizerOpenGL::SetupVertexInstances() { } } -GLintptr RasterizerOpenGL::SetupIndexBuffer() { - MICROPROFILE_SCOPE(OpenGL_Index); - const auto& regs = system.GPU().Maxwell3D().regs; - const std::size_t size = CalculateIndexBufferSize(); - const auto info = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.handle); - return info.offset; -} - -void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { - MICROPROFILE_SCOPE(OpenGL_Shader); - auto& gpu = system.GPU().Maxwell3D(); +void RasterizerOpenGL::SetupShaders(bool is_indexed) { u32 clip_distances = 0; + std::array<Shader*, Maxwell::MaxShaderStage> shaders{}; + image_view_indices.clear(); + sampler_handles.clear(); + + texture_cache.SynchronizeGraphicsDescriptors(); + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { - const auto& shader_config = gpu.regs.shader_config[index]; + const auto& shader_config = maxwell3d.regs.shader_config[index]; const auto program{static_cast<Maxwell::ShaderProgram>(index)}; // Skip stages that are not enabled - if (!gpu.regs.IsShaderConfigEnabled(index)) { + if (!maxwell3d.regs.IsShaderConfigEnabled(index)) { switch (program) { case Maxwell::ShaderProgram::Geometry: program_manager.UseGeometryShader(0); @@ -343,7 +271,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { } continue; } - // Currently this stages are not supported in the OpenGL backend. // TODO(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL if (program == Maxwell::ShaderProgram::TesselationControl || @@ -352,7 +279,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { } Shader* const shader = shader_cache.GetStageProgram(program, async_shaders); - const GLuint program_handle = shader->IsBuilt() ? shader->GetHandle() : 0; switch (program) { case Maxwell::ShaderProgram::VertexA: @@ -368,14 +294,25 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { default: UNIMPLEMENTED_MSG("Unimplemented shader index={}, enable={}, offset=0x{:08X}", index, shader_config.enable.Value(), shader_config.offset); + break; } // Stage indices are 0 - 5 - const std::size_t stage = index == 0 ? 0 : index - 1; - SetupDrawConstBuffers(stage, shader); - SetupDrawGlobalMemory(stage, shader); - SetupDrawTextures(stage, shader); - SetupDrawImages(stage, shader); + const size_t stage = index == 0 ? 0 : index - 1; + shaders[stage] = shader; + + SetupDrawTextures(shader, stage); + SetupDrawImages(shader, stage); + + buffer_cache.SetEnabledUniformBuffers(stage, shader->GetEntries().enabled_uniform_buffers); + + buffer_cache.UnbindGraphicsStorageBuffers(stage); + u32 ssbo_index = 0; + for (const auto& buffer : shader->GetEntries().global_memory_entries) { + buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, buffer.cbuf_index, + buffer.cbuf_offset, buffer.is_written); + ++ssbo_index; + } // Workaround for Intel drivers. // When a clip distance is enabled but not set in the shader it crops parts of the screen @@ -389,146 +326,43 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { ++index; } } - SyncClipEnabled(clip_distances); - gpu.dirty.flags[Dirty::Shaders] = false; -} - -std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; - - std::size_t size = 0; - for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { - if (!regs.vertex_array[index].IsEnabled()) - continue; - - const GPUVAddr start = regs.vertex_array[index].StartAddress(); - const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + maxwell3d.dirty.flags[Dirty::Shaders] = false; - size += end - start; - ASSERT(end >= start); - } - - return size; -} - -std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; - - return static_cast<std::size_t>(regs.index_array.count) * - static_cast<std::size_t>(regs.index_array.FormatSizeInBytes()); -} - -void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, - const VideoCore::DiskResourceLoadCallback& callback) { - shader_cache.LoadDiskCache(stop_loading, callback); -} - -void RasterizerOpenGL::SetupDirtyFlags() { - state_tracker.Initialize(); -} - -void RasterizerOpenGL::ConfigureFramebuffers() { - MICROPROFILE_SCOPE(OpenGL_Framebuffer); - auto& gpu = system.GPU().Maxwell3D(); - if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) { - return; - } - gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false; + buffer_cache.UpdateGraphicsBuffers(is_indexed); - texture_cache.GuardRenderTargets(true); + const std::span indices_span(image_view_indices.data(), image_view_indices.size()); + texture_cache.FillGraphicsImageViews(indices_span, image_view_ids); - View depth_surface = texture_cache.GetDepthBufferSurface(true); + buffer_cache.BindHostGeometryBuffers(is_indexed); - const auto& regs = gpu.regs; - UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0); - - // Bind the framebuffer surfaces - FramebufferCacheKey key; - const auto colors_count = static_cast<std::size_t>(regs.rt_control.count); - for (std::size_t index = 0; index < colors_count; ++index) { - View color_surface{texture_cache.GetColorBufferSurface(index, true)}; - if (!color_surface) { + size_t image_view_index = 0; + size_t texture_index = 0; + size_t image_index = 0; + for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { + const Shader* const shader = shaders[stage]; + if (!shader) { continue; } - // Assume that a surface will be written to if it is used as a framebuffer, even - // if the shader doesn't actually write to it. - texture_cache.MarkColorBufferInUse(index); - - key.SetAttachment(index, regs.rt_control.GetMap(index)); - key.colors[index] = std::move(color_surface); + buffer_cache.BindHostStageBuffers(stage); + const auto& base = device.GetBaseBindings(stage); + BindTextures(shader->GetEntries(), base.sampler, base.image, image_view_index, + texture_index, image_index); } - - if (depth_surface) { - // Assume that a surface will be written to if it is used as a framebuffer, even if - // the shader doesn't actually write to it. - texture_cache.MarkDepthBufferInUse(); - key.zeta = std::move(depth_surface); - } - - texture_cache.GuardRenderTargets(false); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key)); } -void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) { - auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; - - texture_cache.GuardRenderTargets(true); - View color_surface; - - if (using_color) { - // Determine if we have to preserve the contents. - // First we have to make sure all clear masks are enabled. - bool preserve_contents = !regs.clear_buffers.R || !regs.clear_buffers.G || - !regs.clear_buffers.B || !regs.clear_buffers.A; - const std::size_t index = regs.clear_buffers.RT; - if (regs.clear_flags.scissor) { - // Then we have to confirm scissor testing clears the whole image. - const auto& scissor = regs.scissor_test[0]; - preserve_contents |= scissor.min_x > 0; - preserve_contents |= scissor.min_y > 0; - preserve_contents |= scissor.max_x < regs.rt[index].width; - preserve_contents |= scissor.max_y < regs.rt[index].height; - } - - color_surface = texture_cache.GetColorBufferSurface(index, preserve_contents); - texture_cache.MarkColorBufferInUse(index); - } - - View depth_surface; - if (using_depth_stencil) { - bool preserve_contents = false; - if (regs.clear_flags.scissor) { - // For depth stencil clears we only have to confirm scissor test covers the whole image. - const auto& scissor = regs.scissor_test[0]; - preserve_contents |= scissor.min_x > 0; - preserve_contents |= scissor.min_y > 0; - preserve_contents |= scissor.max_x < regs.zeta_width; - preserve_contents |= scissor.max_y < regs.zeta_height; - } - - depth_surface = texture_cache.GetDepthBufferSurface(preserve_contents); - texture_cache.MarkDepthBufferInUse(); - } - texture_cache.GuardRenderTargets(false); - - FramebufferCacheKey key; - key.colors[0] = std::move(color_surface); - key.zeta = std::move(depth_surface); - - state_tracker.NotifyFramebuffer(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key)); +void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) { + shader_cache.LoadDiskCache(title_id, stop_loading, callback); } void RasterizerOpenGL::Clear() { - const auto& gpu = system.GPU().Maxwell3D(); - if (!gpu.ShouldExecute()) { + MICROPROFILE_SCOPE(OpenGL_Clears); + if (!maxwell3d.ShouldExecute()) { return; } - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; bool use_color{}; bool use_depth{}; bool use_stencil{}; @@ -537,8 +371,9 @@ void RasterizerOpenGL::Clear() { regs.clear_buffers.A) { use_color = true; - state_tracker.NotifyColorMask0(); - glColorMaski(0, regs.clear_buffers.R != 0, regs.clear_buffers.G != 0, + const GLuint index = regs.clear_buffers.RT; + state_tracker.NotifyColorMask(index); + glColorMaski(index, regs.clear_buffers.R != 0, regs.clear_buffers.G != 0, regs.clear_buffers.B != 0, regs.clear_buffers.A != 0); // TODO(Rodrigo): Determine if clamping is used on clears @@ -571,15 +406,15 @@ void RasterizerOpenGL::Clear() { state_tracker.NotifyScissor0(); glDisablei(GL_SCISSOR_TEST, 0); } - UNIMPLEMENTED_IF(regs.clear_flags.viewport); - ConfigureClearFramebuffer(use_color, use_depth || use_stencil); + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.UpdateRenderTargets(true); + state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle()); if (use_color) { - glClearBufferfv(GL_COLOR, 0, regs.clear_color); + glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color); } - if (use_depth && use_stencil) { glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); } else if (use_depth) { @@ -587,115 +422,35 @@ void RasterizerOpenGL::Clear() { } else if (use_stencil) { glClearBufferiv(GL_STENCIL, 0, ®s.clear_stencil); } - ++num_queued_commands; } void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { MICROPROFILE_SCOPE(OpenGL_Drawing); - auto& gpu = system.GPU().Maxwell3D(); query_cache.UpdateCounters(); - SyncViewport(); - SyncRasterizeEnable(); - SyncPolygonModes(); - SyncColorMask(); - SyncFragmentColorClampState(); - SyncMultiSampleState(); - SyncDepthTestState(); - SyncDepthClamp(); - SyncStencilTestState(); - SyncBlendState(); - SyncLogicOpState(); - SyncCullMode(); - SyncPrimitiveRestart(); - SyncScissorTest(); - SyncPointState(); - SyncLineState(); - SyncPolygonOffset(); - SyncAlphaTest(); - SyncFramebufferSRGB(); - - buffer_cache.Acquire(); - current_cbuf = 0; - - std::size_t buffer_size = CalculateVertexArraysSize(); - - // Add space for index buffer - if (is_indexed) { - buffer_size = Common::AlignUp(buffer_size, 4) + CalculateIndexBufferSize(); - } - - // Uniform space for the 5 shader stages - buffer_size = - Common::AlignUp<std::size_t>(buffer_size, 4) + - (sizeof(MaxwellUniformData) + device.GetUniformBufferAlignment()) * Maxwell::MaxShaderStage; - - // Add space for at least 18 constant buffers - buffer_size += Maxwell::MaxConstBuffers * - (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); - - // Prepare the vertex array. - const bool invalidated = buffer_cache.Map(buffer_size); - - if (invalidated) { - // When the stream buffer has been invalidated, we have to consider vertex buffers as dirty - auto& dirty = gpu.dirty.flags; - dirty[Dirty::VertexBuffers] = true; - for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) { - dirty[index] = true; - } - } - - // Prepare vertex array format. - SetupVertexFormat(); - - // Upload vertex and index data. - SetupVertexBuffer(); - SetupVertexInstances(); - GLintptr index_buffer_offset = 0; - if (is_indexed) { - index_buffer_offset = SetupIndexBuffer(); - } - - // Setup emulation uniform buffer. - if (!device.UseAssemblyShaders()) { - MaxwellUniformData ubo; - ubo.SetFromRegs(gpu); - const auto info = - buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment()); - glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset, - static_cast<GLsizeiptr>(sizeof(ubo))); - } + SyncState(); // Setup shaders and their used resources. - texture_cache.GuardSamplers(true); - const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology); - SetupShaders(primitive_mode); - texture_cache.GuardSamplers(false); - - ConfigureFramebuffers(); - - // Signal the buffer cache that we are not going to upload more things. - buffer_cache.Unmap(); + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; + SetupShaders(is_indexed); + texture_cache.UpdateRenderTargets(false); + state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle()); program_manager.BindGraphicsPipeline(); - if (texture_cache.TextureBarrier()) { - glTextureBarrier(); - } - + const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology); BeginTransformFeedback(primitive_mode); - const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance); + const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance); const GLsizei num_instances = - static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1); + static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1); if (is_indexed) { - const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base); - const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count); - const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset); - const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format); + const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base); + const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count); + const GLvoid* const offset = buffer_cache_runtime.IndexOffset(); + const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format); if (num_instances == 1 && base_instance == 0 && base_vertex == 0) { glDrawElements(primitive_mode, num_vertices, format, offset); } else if (num_instances == 1 && base_instance == 0) { @@ -714,8 +469,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { base_instance); } } else { - const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first); - const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count); + const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first); + const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count); if (num_instances == 1 && base_instance == 0) { glDrawArrays(primitive_mode, base_vertex, num_vertices); } else if (base_instance == 0) { @@ -730,30 +485,28 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { ++num_queued_commands; - system.GPU().TickWork(); + gpu.TickWork(); } void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { - buffer_cache.Acquire(); - current_cbuf = 0; - - auto kernel = shader_cache.GetComputeKernel(code_addr); - program_manager.BindCompute(kernel->GetHandle()); - - SetupComputeTextures(kernel); - SetupComputeImages(kernel); + Shader* const kernel = shader_cache.GetComputeKernel(code_addr); - const std::size_t buffer_size = - Tegra::Engines::KeplerCompute::NumConstBuffers * - (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); - buffer_cache.Map(buffer_size); + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; + BindComputeTextures(kernel); - SetupComputeConstBuffers(kernel); - SetupComputeGlobalMemory(kernel); - - buffer_cache.Unmap(); - - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + const auto& entries = kernel->GetEntries(); + buffer_cache.SetEnabledComputeUniformBuffers(entries.enabled_uniform_buffers); + buffer_cache.UnbindComputeStorageBuffers(); + u32 ssbo_index = 0; + for (const auto& buffer : entries.global_memory_entries) { + buffer_cache.BindComputeStorageBuffer(ssbo_index, buffer.cbuf_index, buffer.cbuf_offset, + buffer.is_written); + ++ssbo_index; + } + buffer_cache.UpdateComputeBuffers(); + buffer_cache.BindHostComputeBuffers(); + + const auto& launch_desc = kepler_compute.launch_description; glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); ++num_queued_commands; } @@ -767,6 +520,12 @@ void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, query_cache.Query(gpu_addr, type, timestamp); } +void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, + u32 size) { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); +} + void RasterizerOpenGL::FlushAll() {} void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { @@ -774,16 +533,24 @@ void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.FlushRegion(addr, size); - buffer_cache.FlushRegion(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.DownloadMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.DownloadMemory(addr, size); + } query_cache.FlushRegion(addr, size); } bool RasterizerOpenGL::MustFlushRegion(VAddr addr, u64 size) { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; if (!Settings::IsGPULevelHigh()) { - return buffer_cache.MustFlushRegion(addr, size); + return buffer_cache.IsRegionGpuModified(addr, size); } - return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size); + return texture_cache.IsRegionGpuModified(addr, size) || + buffer_cache.IsRegionGpuModified(addr, size); } void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { @@ -791,9 +558,15 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.InvalidateRegion(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.WriteMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.WriteMemory(addr, size); + } shader_cache.InvalidateRegion(addr, size); - buffer_cache.InvalidateRegion(addr, size); query_cache.InvalidateRegion(addr, size); } @@ -802,30 +575,47 @@ void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.OnCPUWrite(addr, size); shader_cache.OnCPUWrite(addr, size); - buffer_cache.OnCPUWrite(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.WriteMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.CachedWriteMemory(addr, size); + } } void RasterizerOpenGL::SyncGuestHost() { MICROPROFILE_SCOPE(OpenGL_CacheManagement); - texture_cache.SyncGuestHost(); - buffer_cache.SyncGuestHost(); shader_cache.SyncGuestHost(); + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.FlushCachedWrites(); + } +} + +void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) { + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.UnmapMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.WriteMemory(addr, size); + } + shader_cache.OnCPUWrite(addr, size); } void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { - auto& memory_manager{gpu.MemoryManager()}; - memory_manager.Write<u32>(addr, value); + gpu_memory.Write<u32>(addr, value); return; } fence_manager.SignalSemaphore(addr, value); } void RasterizerOpenGL::SignalSyncPoint(u32 value) { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { gpu.IncrementSyncPoint(value); return; @@ -834,7 +624,6 @@ void RasterizerOpenGL::SignalSyncPoint(u32 value) { } void RasterizerOpenGL::ReleaseFences() { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { return; } @@ -849,14 +638,15 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { } void RasterizerOpenGL::WaitForIdle() { - // Place a barrier on everything that is not framebuffer related. - // This is related to another flag that is not currently implemented. - glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT | - GL_UNIFORM_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | - GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT | - GL_PIXEL_BUFFER_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | - GL_BUFFER_UPDATE_BARRIER_BIT | GL_TRANSFORM_FEEDBACK_BARRIER_BIT | - GL_SHADER_STORAGE_BARRIER_BIT | GL_QUERY_BUFFER_BARRIER_BIT); + glMemoryBarrier(GL_ALL_BARRIER_BITS); +} + +void RasterizerOpenGL::FragmentBarrier() { + glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); +} + +void RasterizerOpenGL::TiledCacheBarrier() { + glTextureBarrier(); } void RasterizerOpenGL::FlushCommands() { @@ -872,288 +662,174 @@ void RasterizerOpenGL::TickFrame() { // Ticking a frame means that buffers will be swapped, calling glFlush implicitly. num_queued_commands = 0; - buffer_cache.TickFrame(); + fence_manager.TickFrame(); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.TickFrame(); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.TickFrame(); + } } -bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, +bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Config& copy_config) { MICROPROFILE_SCOPE(OpenGL_Blits); - texture_cache.DoFermiCopy(src, dst, copy_config); + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.BlitImage(dst, src, copy_config); return true; } bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) { - if (!framebuffer_addr) { - return {}; + if (framebuffer_addr == 0) { + return false; } - MICROPROFILE_SCOPE(OpenGL_CacheManagement); - const auto surface{texture_cache.TryFindFramebufferSurface(framebuffer_addr)}; - if (!surface) { - return {}; + std::scoped_lock lock{texture_cache.mutex}; + ImageView* const image_view{texture_cache.TryFindFramebufferImageView(framebuffer_addr)}; + if (!image_view) { + return false; } - // Verify that the cached surface is the same size and format as the requested framebuffer - const auto& params{surface->GetSurfaceParams()}; - const auto& pixel_format{ - VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)}; - ASSERT_MSG(params.width == config.width, "Framebuffer width is different"); - ASSERT_MSG(params.height == config.height, "Framebuffer height is different"); - - if (params.pixel_format != pixel_format) { - LOG_DEBUG(Render_OpenGL, "Framebuffer pixel_format is different"); - } - - screen_info.display_texture = surface->GetTexture(); - screen_info.display_srgb = surface->GetSurfaceParams().srgb_conversion; + // ASSERT_MSG(image_view->size.width == config.width, "Framebuffer width is different"); + // ASSERT_MSG(image_view->size.height == config.height, "Framebuffer height is different"); + screen_info.display_texture = image_view->Handle(ImageViewType::e2D); + screen_info.display_srgb = VideoCore::Surface::IsPixelFormatSRGB(image_view->format); return true; } -void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* shader) { - static constexpr std::array PARAMETER_LUT = { - GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV, - GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV, - GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV}; - - MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& stages = system.GPU().Maxwell3D().state.shader_stages; - const auto& shader_stage = stages[stage_index]; - const auto& entries = shader->GetEntries(); - const bool use_unified = entries.use_unified_uniforms; - const std::size_t base_unified_offset = stage_index * NUM_CONST_BUFFERS_BYTES_PER_STAGE; - - const auto base_bindings = device.GetBaseBindings(stage_index); - u32 binding = device.UseAssemblyShaders() ? 0 : base_bindings.uniform_buffer; - for (const auto& entry : entries.const_buffers) { - const u32 index = entry.GetIndex(); - const auto& buffer = shader_stage.const_buffers[index]; - SetupConstBuffer(PARAMETER_LUT[stage_index], binding, buffer, entry, use_unified, - base_unified_offset + index * Maxwell::MaxConstBufferSize); - ++binding; - } - if (use_unified) { - const u32 index = static_cast<u32>(base_bindings.shader_storage_buffer + - entries.global_memory_entries.size()); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle, - base_unified_offset, NUM_CONST_BUFFERS_BYTES_PER_STAGE); - } -} - -void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) { - MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; - const auto& entries = kernel->GetEntries(); - const bool use_unified = entries.use_unified_uniforms; - - u32 binding = 0; - for (const auto& entry : entries.const_buffers) { - const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; - const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); - Tegra::Engines::ConstBufferInfo buffer; - buffer.address = config.Address(); - buffer.size = config.size; - buffer.enabled = mask[entry.GetIndex()]; - SetupConstBuffer(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding, buffer, entry, - use_unified, entry.GetIndex() * Maxwell::MaxConstBufferSize); - ++binding; - } - if (use_unified) { - const GLuint index = static_cast<GLuint>(entries.global_memory_entries.size()); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle, 0, - NUM_CONST_BUFFERS_BYTES_PER_STAGE); - } -} - -void RasterizerOpenGL::SetupConstBuffer(GLenum stage, u32 binding, - const Tegra::Engines::ConstBufferInfo& buffer, - const ConstBufferEntry& entry, bool use_unified, - std::size_t unified_offset) { - if (!buffer.enabled) { - // Set values to zero to unbind buffers - if (device.UseAssemblyShaders()) { - glBindBufferRangeNV(stage, entry.GetIndex(), 0, 0, 0); - } else { - glBindBufferRange(GL_UNIFORM_BUFFER, binding, 0, 0, sizeof(float)); - } - return; - } +void RasterizerOpenGL::BindComputeTextures(Shader* kernel) { + image_view_indices.clear(); + sampler_handles.clear(); - // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140 - // UBO alignment requirements. - const std::size_t size = Common::AlignUp(GetConstBufferSize(buffer, entry), sizeof(GLvec4)); + texture_cache.SynchronizeComputeDescriptors(); - const bool fast_upload = !use_unified && device.HasFastBufferSubData(); + SetupComputeTextures(kernel); + SetupComputeImages(kernel); - const std::size_t alignment = use_unified ? 4 : device.GetUniformBufferAlignment(); - const GPUVAddr gpu_addr = buffer.address; - auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, false, fast_upload); + const std::span indices_span(image_view_indices.data(), image_view_indices.size()); + texture_cache.FillComputeImageViews(indices_span, image_view_ids); - if (device.UseAssemblyShaders()) { - UNIMPLEMENTED_IF(use_unified); - if (info.offset != 0) { - const GLuint staging_cbuf = staging_cbufs[current_cbuf++]; - glCopyNamedBufferSubData(info.handle, staging_cbuf, info.offset, 0, size); - info.handle = staging_cbuf; - info.offset = 0; + program_manager.BindCompute(kernel->GetHandle()); + size_t image_view_index = 0; + size_t texture_index = 0; + size_t image_index = 0; + BindTextures(kernel->GetEntries(), 0, 0, image_view_index, texture_index, image_index); +} + +void RasterizerOpenGL::BindTextures(const ShaderEntries& entries, GLuint base_texture, + GLuint base_image, size_t& image_view_index, + size_t& texture_index, size_t& image_index) { + const GLuint* const samplers = sampler_handles.data() + texture_index; + const GLuint* const textures = texture_handles.data() + texture_index; + const GLuint* const images = image_handles.data() + image_index; + + const size_t num_samplers = entries.samplers.size(); + for (const auto& sampler : entries.samplers) { + for (size_t i = 0; i < sampler.size; ++i) { + const ImageViewId image_view_id = image_view_ids[image_view_index++]; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + const GLuint handle = image_view.Handle(ImageViewTypeFromEntry(sampler)); + texture_handles[texture_index++] = handle; } - glBindBufferRangeNV(stage, binding, info.handle, info.offset, size); - return; } - - if (use_unified) { - glCopyNamedBufferSubData(info.handle, unified_uniform_buffer.handle, info.offset, - unified_offset, size); - } else { - glBindBufferRange(GL_UNIFORM_BUFFER, binding, info.handle, info.offset, size); + const size_t num_images = entries.images.size(); + for (size_t unit = 0; unit < num_images; ++unit) { + // TODO: Mark as modified + const ImageViewId image_view_id = image_view_ids[image_view_index++]; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + const GLuint handle = image_view.Handle(ImageViewTypeFromEntry(entries.images[unit])); + image_handles[image_index] = handle; + ++image_index; } -} - -void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader) { - static constexpr std::array TARGET_LUT = { - GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV, - GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV, - }; - - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; - const auto& cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]}; - const auto& entries{shader->GetEntries().global_memory_entries}; - - std::array<GLuint64EXT, 32> pointers; - ASSERT(entries.size() < pointers.size()); - - const bool assembly_shaders = device.UseAssemblyShaders(); - u32 binding = assembly_shaders ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer; - for (const auto& entry : entries) { - const GPUVAddr addr{cbufs.const_buffers[entry.cbuf_index].address + entry.cbuf_offset}; - const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)}; - const u32 size{memory_manager.Read<u32>(addr + 8)}; - SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]); - ++binding; - } - if (assembly_shaders) { - UpdateBindlessPointers(TARGET_LUT[stage_index], pointers.data(), entries.size()); - } -} - -void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) { - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; - const auto& cbufs{gpu.KeplerCompute().launch_description.const_buffer_config}; - const auto& entries{kernel->GetEntries().global_memory_entries}; - - std::array<GLuint64EXT, 32> pointers; - ASSERT(entries.size() < pointers.size()); - - u32 binding = 0; - for (const auto& entry : entries) { - const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset}; - const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)}; - const u32 size{memory_manager.Read<u32>(addr + 8)}; - SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]); - ++binding; - } - if (device.UseAssemblyShaders()) { - UpdateBindlessPointers(GL_COMPUTE_PROGRAM_NV, pointers.data(), entries.size()); + if (num_samplers > 0) { + glBindSamplers(base_texture, static_cast<GLsizei>(num_samplers), samplers); + glBindTextures(base_texture, static_cast<GLsizei>(num_samplers), textures); } -} - -void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, - GPUVAddr gpu_addr, std::size_t size, - GLuint64EXT* pointer) { - const std::size_t alignment{device.GetShaderStorageBufferAlignment()}; - const auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.is_written); - if (device.UseAssemblyShaders()) { - *pointer = info.address + info.offset; - } else { - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, info.handle, info.offset, - static_cast<GLsizeiptr>(size)); + if (num_images > 0) { + glBindImageTextures(base_image, static_cast<GLsizei>(num_images), images); } } -void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader) { - MICROPROFILE_SCOPE(OpenGL_Texture); - const auto& maxwell3d = system.GPU().Maxwell3D(); - u32 binding = device.GetBaseBindings(stage_index).sampler; +void RasterizerOpenGL::SetupDrawTextures(const Shader* shader, size_t stage_index) { + const bool via_header_index = + maxwell3d.regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : shader->GetEntries().samplers) { const auto shader_type = static_cast<ShaderType>(stage_index); - for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(maxwell3d, entry, shader_type, i); - SetupTexture(binding++, texture, entry); + for (size_t index = 0; index < entry.size; ++index) { + const auto handle = + GetTextureInfo(maxwell3d, via_header_index, entry, shader_type, index); + const Sampler* const sampler = texture_cache.GetGraphicsSampler(handle.sampler); + sampler_handles.push_back(sampler->Handle()); + image_view_indices.push_back(handle.image); } } } -void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) { - MICROPROFILE_SCOPE(OpenGL_Texture); - const auto& compute = system.GPU().KeplerCompute(); - u32 binding = 0; +void RasterizerOpenGL::SetupComputeTextures(const Shader* kernel) { + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : kernel->GetEntries().samplers) { - for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i); - SetupTexture(binding++, texture, entry); + for (size_t i = 0; i < entry.size; ++i) { + const auto handle = + GetTextureInfo(kepler_compute, via_header_index, entry, ShaderType::Compute, i); + const Sampler* const sampler = texture_cache.GetComputeSampler(handle.sampler); + sampler_handles.push_back(sampler->Handle()); + image_view_indices.push_back(handle.image); } } } -void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, - const SamplerEntry& entry) { - const auto view = texture_cache.GetTextureSurface(texture.tic, entry); - if (!view) { - // Can occur when texture addr is null or its memory is unmapped/invalid - glBindSampler(binding, 0); - glBindTextureUnit(binding, 0); - return; - } - const GLuint handle = view->GetTexture(texture.tic.x_source, texture.tic.y_source, - texture.tic.z_source, texture.tic.w_source); - glBindTextureUnit(binding, handle); - if (!view->GetSurfaceParams().IsBuffer()) { - glBindSampler(binding, sampler_cache.GetSampler(texture.tsc)); - } -} - -void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) { - const auto& maxwell3d = system.GPU().Maxwell3D(); - u32 binding = device.GetBaseBindings(stage_index).image; +void RasterizerOpenGL::SetupDrawImages(const Shader* shader, size_t stage_index) { + const bool via_header_index = + maxwell3d.regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : shader->GetEntries().images) { - const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index); - const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic; - SetupImage(binding++, tic, entry); + const auto shader_type = static_cast<ShaderType>(stage_index); + const auto handle = GetTextureInfo(maxwell3d, via_header_index, entry, shader_type); + image_view_indices.push_back(handle.image); } } -void RasterizerOpenGL::SetupComputeImages(Shader* shader) { - const auto& compute = system.GPU().KeplerCompute(); - u32 binding = 0; +void RasterizerOpenGL::SetupComputeImages(const Shader* shader) { + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : shader->GetEntries().images) { - const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic; - SetupImage(binding++, tic, entry); + const auto handle = + GetTextureInfo(kepler_compute, via_header_index, entry, ShaderType::Compute); + image_view_indices.push_back(handle.image); } } -void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, - const ImageEntry& entry) { - const auto view = texture_cache.GetImageSurface(tic, entry); - if (!view) { - glBindImageTexture(binding, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8); - return; - } - if (entry.is_written) { - view->MarkAsModified(texture_cache.Tick()); - } - const GLuint handle = view->GetTexture(tic.x_source, tic.y_source, tic.z_source, tic.w_source); - glBindImageTexture(binding, handle, 0, GL_TRUE, 0, GL_READ_WRITE, view->GetFormat()); +void RasterizerOpenGL::SyncState() { + SyncViewport(); + SyncRasterizeEnable(); + SyncPolygonModes(); + SyncColorMask(); + SyncFragmentColorClampState(); + SyncMultiSampleState(); + SyncDepthTestState(); + SyncDepthClamp(); + SyncStencilTestState(); + SyncBlendState(); + SyncLogicOpState(); + SyncCullMode(); + SyncPrimitiveRestart(); + SyncScissorTest(); + SyncPointState(); + SyncLineState(); + SyncPolygonOffset(); + SyncAlphaTest(); + SyncFramebufferSRGB(); + SyncVertexFormats(); + SyncVertexInstances(); } void RasterizerOpenGL::SyncViewport() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; - const auto& regs = gpu.regs; + auto& flags = maxwell3d.dirty.flags; + const auto& regs = maxwell3d.regs; const bool dirty_viewport = flags[Dirty::Viewports]; const bool dirty_clip_control = flags[Dirty::ClipControl]; @@ -1180,15 +856,17 @@ void RasterizerOpenGL::SyncViewport() { flags[Dirty::ClipControl] = false; bool flip_y = false; - if (regs.viewport_transform[0].scale_y < 0.0) { + if (regs.viewport_transform[0].scale_y < 0.0f) { flip_y = !flip_y; } if (regs.screen_y_control.y_negate != 0) { flip_y = !flip_y; } - glClipControl(flip_y ? GL_UPPER_LEFT : GL_LOWER_LEFT, - regs.depth_mode == Maxwell::DepthMode::ZeroToOne ? GL_ZERO_TO_ONE - : GL_NEGATIVE_ONE_TO_ONE); + const bool is_zero_to_one = regs.depth_mode == Maxwell::DepthMode::ZeroToOne; + const GLenum origin = flip_y ? GL_UPPER_LEFT : GL_LOWER_LEFT; + const GLenum depth = is_zero_to_one ? GL_ZERO_TO_ONE : GL_NEGATIVE_ONE_TO_ONE; + state_tracker.ClipControl(origin, depth); + state_tracker.SetYNegate(regs.screen_y_control.y_negate != 0); } if (dirty_viewport) { @@ -1225,25 +903,23 @@ void RasterizerOpenGL::SyncViewport() { } void RasterizerOpenGL::SyncDepthClamp() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::DepthClampEnabled]) { return; } flags[Dirty::DepthClampEnabled] = false; - oglEnable(GL_DEPTH_CLAMP, gpu.regs.view_volume_clip_control.depth_clamp_disabled == 0); + oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0); } void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::ClipDistances] && !flags[Dirty::Shaders]) { return; } flags[Dirty::ClipDistances] = false; - clip_mask &= gpu.regs.clip_distance_enabled; + clip_mask &= maxwell3d.regs.clip_distance_enabled; if (clip_mask == last_clip_distance_mask) { return; } @@ -1259,9 +935,8 @@ void RasterizerOpenGL::SyncClipCoef() { } void RasterizerOpenGL::SyncCullMode() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; - const auto& regs = gpu.regs; + auto& flags = maxwell3d.dirty.flags; + const auto& regs = maxwell3d.regs; if (flags[Dirty::CullTest]) { flags[Dirty::CullTest] = false; @@ -1276,26 +951,24 @@ void RasterizerOpenGL::SyncCullMode() { } void RasterizerOpenGL::SyncPrimitiveRestart() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::PrimitiveRestart]) { return; } flags[Dirty::PrimitiveRestart] = false; - if (gpu.regs.primitive_restart.enabled) { + if (maxwell3d.regs.primitive_restart.enabled) { glEnable(GL_PRIMITIVE_RESTART); - glPrimitiveRestartIndex(gpu.regs.primitive_restart.index); + glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index); } else { glDisable(GL_PRIMITIVE_RESTART); } } void RasterizerOpenGL::SyncDepthTestState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; + const auto& regs = maxwell3d.regs; - const auto& regs = gpu.regs; if (flags[Dirty::DepthMask]) { flags[Dirty::DepthMask] = false; glDepthMask(regs.depth_write_enabled ? GL_TRUE : GL_FALSE); @@ -1313,14 +986,13 @@ void RasterizerOpenGL::SyncDepthTestState() { } void RasterizerOpenGL::SyncStencilTestState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::StencilTest]) { return; } flags[Dirty::StencilTest] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; oglEnable(GL_STENCIL_TEST, regs.stencil_enable); glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func), @@ -1345,25 +1017,24 @@ void RasterizerOpenGL::SyncStencilTestState() { } void RasterizerOpenGL::SyncRasterizeEnable() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::RasterizeEnable]) { return; } flags[Dirty::RasterizeEnable] = false; - oglEnable(GL_RASTERIZER_DISCARD, gpu.regs.rasterize_enable == 0); + oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0); } void RasterizerOpenGL::SyncPolygonModes() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::PolygonModes]) { return; } flags[Dirty::PolygonModes] = false; - if (gpu.regs.fill_rectangle) { + const auto& regs = maxwell3d.regs; + if (regs.fill_rectangle) { if (!GLAD_GL_NV_fill_rectangle) { LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported"); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -1376,27 +1047,26 @@ void RasterizerOpenGL::SyncPolygonModes() { return; } - if (gpu.regs.polygon_mode_front == gpu.regs.polygon_mode_back) { + if (regs.polygon_mode_front == regs.polygon_mode_back) { flags[Dirty::PolygonModeFront] = false; flags[Dirty::PolygonModeBack] = false; - glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front)); + glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_front)); return; } if (flags[Dirty::PolygonModeFront]) { flags[Dirty::PolygonModeFront] = false; - glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front)); + glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(regs.polygon_mode_front)); } if (flags[Dirty::PolygonModeBack]) { flags[Dirty::PolygonModeBack] = false; - glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_back)); + glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_back)); } } void RasterizerOpenGL::SyncColorMask() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::ColorMasks]) { return; } @@ -1405,7 +1075,7 @@ void RasterizerOpenGL::SyncColorMask() { const bool force = flags[Dirty::ColorMaskCommon]; flags[Dirty::ColorMaskCommon] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; if (regs.color_mask_common) { if (!force && !flags[Dirty::ColorMask0]) { return; @@ -1430,33 +1100,30 @@ void RasterizerOpenGL::SyncColorMask() { } void RasterizerOpenGL::SyncMultiSampleState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::MultisampleControl]) { return; } flags[Dirty::MultisampleControl] = false; - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage); oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one); } void RasterizerOpenGL::SyncFragmentColorClampState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::FragmentClampColor]) { return; } flags[Dirty::FragmentClampColor] = false; - glClampColor(GL_CLAMP_FRAGMENT_COLOR, gpu.regs.frag_color_clamp ? GL_TRUE : GL_FALSE); + glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE); } void RasterizerOpenGL::SyncBlendState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; - const auto& regs = gpu.regs; + auto& flags = maxwell3d.dirty.flags; + const auto& regs = maxwell3d.regs; if (flags[Dirty::BlendColor]) { flags[Dirty::BlendColor] = false; @@ -1513,14 +1180,13 @@ void RasterizerOpenGL::SyncBlendState() { } void RasterizerOpenGL::SyncLogicOpState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::LogicOp]) { return; } flags[Dirty::LogicOp] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; if (regs.logic_op.enable) { glEnable(GL_COLOR_LOGIC_OP); glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation)); @@ -1530,14 +1196,13 @@ void RasterizerOpenGL::SyncLogicOpState() { } void RasterizerOpenGL::SyncScissorTest() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::Scissors]) { return; } flags[Dirty::Scissors] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; for (std::size_t index = 0; index < Maxwell::NumViewports; ++index) { if (!flags[Dirty::Scissor0 + index]) { continue; @@ -1556,49 +1221,38 @@ void RasterizerOpenGL::SyncScissorTest() { } void RasterizerOpenGL::SyncPointState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::PointSize]) { return; } flags[Dirty::PointSize] = false; - oglEnable(GL_POINT_SPRITE, gpu.regs.point_sprite_enable); - - if (gpu.regs.vp_point_size.enable) { - // By definition of GL_POINT_SIZE, it only matters if GL_PROGRAM_POINT_SIZE is disabled. - glEnable(GL_PROGRAM_POINT_SIZE); - return; - } + oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable); + oglEnable(GL_PROGRAM_POINT_SIZE, maxwell3d.regs.vp_point_size.enable); - // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid - // in OpenGL). - glPointSize(std::max(1.0f, gpu.regs.point_size)); - glDisable(GL_PROGRAM_POINT_SIZE); + glPointSize(std::max(1.0f, maxwell3d.regs.point_size)); } void RasterizerOpenGL::SyncLineState() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::LineWidth]) { return; } flags[Dirty::LineWidth] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable); glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased); } void RasterizerOpenGL::SyncPolygonOffset() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::PolygonOffset]) { return; } flags[Dirty::PolygonOffset] = false; - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable); oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable); oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable); @@ -1612,18 +1266,13 @@ void RasterizerOpenGL::SyncPolygonOffset() { } void RasterizerOpenGL::SyncAlphaTest() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::AlphaTest]) { return; } flags[Dirty::AlphaTest] = false; - const auto& regs = gpu.regs; - if (regs.alpha_test_enabled && regs.rt_control.count > 1) { - LOG_WARNING(Render_OpenGL, "Alpha testing with more than one render target is not tested"); - } - + const auto& regs = maxwell3d.regs; if (regs.alpha_test_enabled) { glEnable(GL_ALPHA_TEST); glAlphaFunc(MaxwellToGL::ComparisonOp(regs.alpha_test_func), regs.alpha_test_ref); @@ -1633,20 +1282,19 @@ void RasterizerOpenGL::SyncAlphaTest() { } void RasterizerOpenGL::SyncFramebufferSRGB() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::FramebufferSRGB]) { return; } flags[Dirty::FramebufferSRGB] = false; - oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb); + oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb); } void RasterizerOpenGL::SyncTransformFeedback() { // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal // when this is required. - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; static constexpr std::size_t STRIDE = 3; std::array<GLint, 128 * STRIDE * Maxwell::NumTransformFeedbackBuffers> attribs; @@ -1698,40 +1346,17 @@ void RasterizerOpenGL::SyncTransformFeedback() { } void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } - if (device.UseAssemblyShaders()) { SyncTransformFeedback(); } - UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) || regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) || regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry)); - - for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) { - const auto& binding = regs.tfb_bindings[index]; - if (!binding.buffer_enable) { - if (enabled_transform_feedback_buffers[index]) { - glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), 0, 0, - 0); - } - enabled_transform_feedback_buffers[index] = false; - continue; - } - enabled_transform_feedback_buffers[index] = true; - - auto& tfb_buffer = transform_feedback_buffers[index]; - tfb_buffer.Create(); - - const GLuint handle = tfb_buffer.handle; - const std::size_t size = binding.buffer_size; - glNamedBufferData(handle, static_cast<GLsizeiptr>(size), nullptr, GL_STREAM_COPY); - glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), handle, 0, - static_cast<GLsizeiptr>(size)); - } + UNIMPLEMENTED_IF(primitive_mode != GL_POINTS); // We may have to call BeginTransformFeedbackNV here since they seem to call different // implementations on Nvidia's driver (the pointer is different) but we are using @@ -1741,27 +1366,11 @@ void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) { } void RasterizerOpenGL::EndTransformFeedback() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } - glEndTransformFeedback(); - - for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) { - const auto& binding = regs.tfb_bindings[index]; - if (!binding.buffer_enable) { - continue; - } - UNIMPLEMENTED_IF(binding.buffer_offset != 0); - - const GLuint handle = transform_feedback_buffers[index].handle; - const GPUVAddr gpu_addr = binding.Address(); - const std::size_t size = binding.buffer_size; - const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true); - glCopyNamedBufferSubData(handle, info.handle, 0, info.offset, - static_cast<GLsizeiptr>(size)); - } } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index ccc6f50f6..3745cf637 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -7,12 +7,13 @@ #include <array> #include <atomic> #include <cstddef> -#include <map> #include <memory> #include <optional> #include <tuple> #include <utility> +#include <boost/container/static_vector.hpp> + #include <glad/glad.h> #include "common/common_types.h" @@ -23,21 +24,18 @@ #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_fence_manager.h" -#include "video_core/renderer_opengl/gl_framebuffer_cache.h" #include "video_core/renderer_opengl/gl_query_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_sampler_cache.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state_tracker.h" #include "video_core/renderer_opengl/gl_texture_cache.h" -#include "video_core/renderer_opengl/utils.h" #include "video_core/shader/async_shaders.h" #include "video_core/textures/texture.h" -namespace Core { -class System; +namespace Core::Memory { +class Memory; } namespace Core::Frontend { @@ -51,13 +49,21 @@ class MemoryManager; namespace OpenGL { struct ScreenInfo; -struct DrawParameters; +struct ShaderEntries; + +struct BindlessSSBO { + GLuint64EXT address; + GLsizei length; + GLsizei padding; +}; +static_assert(sizeof(BindlessSSBO) * CHAR_BIT == 128); class RasterizerOpenGL : public VideoCore::RasterizerAccelerated { public: - explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, - const Device& device, ScreenInfo& info, - ProgramManager& program_manager, StateTracker& state_tracker); + explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_, + Core::Memory::Memory& cpu_memory_, const Device& device_, + ScreenInfo& screen_info_, ProgramManager& program_manager_, + StateTracker& state_tracker_); ~RasterizerOpenGL() override; void Draw(bool is_indexed, bool is_instanced) override; @@ -65,27 +71,30 @@ public: void DispatchCompute(GPUVAddr code_addr) override; void ResetCounter(VideoCore::QueryType type) override; void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; + void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; void FlushAll() override; void FlushRegion(VAddr addr, u64 size) override; bool MustFlushRegion(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size) override; void OnCPUWrite(VAddr addr, u64 size) override; void SyncGuestHost() override; + void UnmapMemory(VAddr addr, u64 size) override; void SignalSemaphore(GPUVAddr addr, u32 value) override; void SignalSyncPoint(u32 value) override; void ReleaseFences() override; void FlushAndInvalidateRegion(VAddr addr, u64 size) override; void WaitForIdle() override; + void FragmentBarrier() override; + void TiledCacheBarrier() override; void FlushCommands() override; void TickFrame() override; - bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, + bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Config& copy_config) override; bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; - void LoadDiskResources(const std::atomic_bool& stop_loading, + void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) override; - void SetupDirtyFlags() override; /// Returns true when there are commands queued to the OpenGL server. bool AnyCommandQueued() const { @@ -101,51 +110,29 @@ public: } private: - /// Configures the color and depth framebuffer states. - void ConfigureFramebuffers(); - - /// Configures the color and depth framebuffer for clearing. - void ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil); - - /// Configures the current constbuffers to use for the draw command. - void SetupDrawConstBuffers(std::size_t stage_index, Shader* shader); - - /// Configures the current constbuffers to use for the kernel invocation. - void SetupComputeConstBuffers(Shader* kernel); - - /// Configures a constant buffer. - void SetupConstBuffer(GLenum stage, u32 binding, const Tegra::Engines::ConstBufferInfo& buffer, - const ConstBufferEntry& entry, bool use_unified, - std::size_t unified_offset); + static constexpr size_t MAX_TEXTURES = 192; + static constexpr size_t MAX_IMAGES = 48; + static constexpr size_t MAX_IMAGE_VIEWS = MAX_TEXTURES + MAX_IMAGES; - /// Configures the current global memory entries to use for the draw command. - void SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader); + void BindComputeTextures(Shader* kernel); - /// Configures the current global memory entries to use for the kernel invocation. - void SetupComputeGlobalMemory(Shader* kernel); - - /// Configures a global memory buffer. - void SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, GPUVAddr gpu_addr, - std::size_t size, GLuint64EXT* pointer); + void BindTextures(const ShaderEntries& entries, GLuint base_texture, GLuint base_image, + size_t& image_view_index, size_t& texture_index, size_t& image_index); /// Configures the current textures to use for the draw command. - void SetupDrawTextures(std::size_t stage_index, Shader* shader); + void SetupDrawTextures(const Shader* shader, size_t stage_index); /// Configures the textures used in a compute shader. - void SetupComputeTextures(Shader* kernel); - - /// Configures a texture. - void SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, - const SamplerEntry& entry); + void SetupComputeTextures(const Shader* kernel); /// Configures images in a graphics shader. - void SetupDrawImages(std::size_t stage_index, Shader* shader); + void SetupDrawImages(const Shader* shader, size_t stage_index); /// Configures images in a compute shader. - void SetupComputeImages(Shader* shader); + void SetupComputeImages(const Shader* shader); - /// Configures an image. - void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, const ImageEntry& entry); + /// Syncs state to match guest's + void SyncState(); /// Syncs the viewport and depth range to match the guest state void SyncViewport(); @@ -210,6 +197,12 @@ private: /// Syncs the framebuffer sRGB state to match the guest state void SyncFramebufferSRGB(); + /// Syncs vertex formats to match the guest state + void SyncVertexFormats(); + + /// Syncs vertex instances to match the guest state + void SyncVertexInstances(); + /// Syncs transform feedback state to match guest state /// @note Only valid on assembly shaders void SyncTransformFeedback(); @@ -220,56 +213,35 @@ private: /// End a transform feedback void EndTransformFeedback(); - /// Check for extension that are not strictly required but are needed for correct emulation - void CheckExtensions(); - - std::size_t CalculateVertexArraysSize() const; - - std::size_t CalculateIndexBufferSize() const; + void SetupShaders(bool is_indexed); - /// Updates the current vertex format - void SetupVertexFormat(); - - void SetupVertexBuffer(); - void SetupVertexInstances(); - - GLintptr SetupIndexBuffer(); - - void SetupShaders(GLenum primitive_mode); + Tegra::GPU& gpu; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; const Device& device; + ScreenInfo& screen_info; + ProgramManager& program_manager; + StateTracker& state_tracker; - TextureCacheOpenGL texture_cache; + TextureCacheRuntime texture_cache_runtime; + TextureCache texture_cache; + BufferCacheRuntime buffer_cache_runtime; + BufferCache buffer_cache; ShaderCacheOpenGL shader_cache; - SamplerCacheOpenGL sampler_cache; - FramebufferCacheOpenGL framebuffer_cache; QueryCache query_cache; - OGLBufferCache buffer_cache; FenceManagerOpenGL fence_manager; - Core::System& system; - ScreenInfo& screen_info; - ProgramManager& program_manager; - StateTracker& state_tracker; VideoCommon::Shader::AsyncShaders async_shaders; - static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; - - GLint vertex_binding = 0; - - std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers> - transform_feedback_buffers; - std::bitset<Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers> - enabled_transform_feedback_buffers; - - static constexpr std::size_t NUM_CONSTANT_BUFFERS = - Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers * - Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; - std::array<GLuint, NUM_CONSTANT_BUFFERS> staging_cbufs{}; - std::size_t current_cbuf = 0; - OGLBuffer unified_uniform_buffer; + boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices; + std::array<ImageViewId, MAX_IMAGE_VIEWS> image_view_ids; + boost::container::static_vector<GLuint, MAX_TEXTURES> sampler_handles; + std::array<GLuint, MAX_TEXTURES> texture_handles{}; + std::array<GLuint, MAX_IMAGES> image_handles{}; - /// Number of commands queued to the OpenGL driver. Reseted on flush. + /// Number of commands queued to the OpenGL driver. Resetted on flush. std::size_t num_queued_commands = 0; u32 last_clip_distance_mask = 0; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index 0ebcec427..3428e5e21 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -71,7 +71,7 @@ void OGLSampler::Create() { return; MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - glGenSamplers(1, &handle); + glCreateSamplers(1, &handle); } void OGLSampler::Release() { @@ -171,12 +171,6 @@ void OGLBuffer::Release() { handle = 0; } -void OGLBuffer::MakeStreamCopy(std::size_t buffer_size) { - ASSERT_OR_EXECUTE((handle != 0 && buffer_size != 0), { return; }); - - glNamedBufferData(handle, buffer_size, nullptr, GL_STREAM_COPY); -} - void OGLSync::Create() { if (handle != 0) return; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index f48398669..552d79db4 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -234,9 +234,6 @@ public: /// Deletes the internal OpenGL resource void Release(); - // Converts the buffer into a stream copy buffer with a fixed size - void MakeStreamCopy(std::size_t buffer_size); - GLuint handle = 0; }; diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.cpp b/src/video_core/renderer_opengl/gl_sampler_cache.cpp deleted file mode 100644 index 5c174879a..000000000 --- a/src/video_core/renderer_opengl/gl_sampler_cache.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/logging/log.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_sampler_cache.h" -#include "video_core/renderer_opengl/maxwell_to_gl.h" - -namespace OpenGL { - -SamplerCacheOpenGL::SamplerCacheOpenGL() = default; - -SamplerCacheOpenGL::~SamplerCacheOpenGL() = default; - -OGLSampler SamplerCacheOpenGL::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const { - OGLSampler sampler; - sampler.Create(); - - const GLuint sampler_id{sampler.handle}; - glSamplerParameteri( - sampler_id, GL_TEXTURE_MAG_FILTER, - MaxwellToGL::TextureFilterMode(tsc.mag_filter, Tegra::Texture::TextureMipmapFilter::None)); - glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER, - MaxwellToGL::TextureFilterMode(tsc.min_filter, tsc.mipmap_filter)); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(tsc.wrap_u)); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(tsc.wrap_v)); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(tsc.wrap_p)); - glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE, - tsc.depth_compare_enabled == 1 ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE); - glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC, - MaxwellToGL::DepthCompareFunc(tsc.depth_compare_func)); - glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, tsc.GetBorderColor().data()); - glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, tsc.GetMinLod()); - glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, tsc.GetMaxLod()); - glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, tsc.GetLodBias()); - if (GLAD_GL_ARB_texture_filter_anisotropic) { - glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, tsc.GetMaxAnisotropy()); - } else if (GLAD_GL_EXT_texture_filter_anisotropic) { - glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, tsc.GetMaxAnisotropy()); - } else { - LOG_WARNING(Render_OpenGL, "Anisotropy not supported by host GPU driver"); - } - - return sampler; -} - -GLuint SamplerCacheOpenGL::ToSamplerType(const OGLSampler& sampler) const { - return sampler.handle; -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.h b/src/video_core/renderer_opengl/gl_sampler_cache.h deleted file mode 100644 index 34ee37f00..000000000 --- a/src/video_core/renderer_opengl/gl_sampler_cache.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <glad/glad.h> - -#include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/sampler_cache.h" - -namespace OpenGL { - -class SamplerCacheOpenGL final : public VideoCommon::SamplerCache<GLuint, OGLSampler> { -public: - explicit SamplerCacheOpenGL(); - ~SamplerCacheOpenGL(); - -protected: - OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override; - - GLuint ToSamplerType(const OGLSampler& sampler) const override; -}; - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index a07d56ef0..529570ff0 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -27,7 +27,6 @@ #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_disk_cache.h" #include "video_core/renderer_opengl/gl_state_tracker.h" -#include "video_core/renderer_opengl/utils.h" #include "video_core/shader/memory_util.h" #include "video_core/shader/registry.h" #include "video_core/shader/shader_ir.h" @@ -160,6 +159,10 @@ std::unordered_set<GLenum> GetSupportedFormats() { ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 unique_identifier, const ShaderIR& ir, const Registry& registry, bool hint_retrievable) { + if (device.UseDriverCache()) { + // Ignore hint retrievable if we are using the driver cache + hint_retrievable = false; + } const std::string shader_id = MakeShaderID(unique_identifier, shader_type); LOG_INFO(Render_OpenGL, "{}", shader_id); @@ -198,10 +201,10 @@ ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 u return program; } -Shader::Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry_, ShaderEntries entries_, - ProgramSharedPtr program_, bool is_built) +Shader::Shader(std::shared_ptr<Registry> registry_, ShaderEntries entries_, + ProgramSharedPtr program_, bool is_built_) : registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)}, - is_built(is_built) { + is_built{is_built_} { handle = program->assembly_program.handle; if (handle == 0) { handle = program->source_program.handle; @@ -239,12 +242,11 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory( ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) { const auto shader_type = GetShaderType(program_type); - auto& gpu = params.system.GPU(); + auto& gpu = params.gpu; gpu.ShaderNotify().MarkSharderBuilding(); auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D()); - if (!async_shaders.IsShaderAsync(params.system.GPU()) || - !params.device.UseAsynchronousShaders()) { + if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) { const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry); // TODO(Rodrigo): Handle VertexA shaders // std::optional<ShaderIR> ir_b; @@ -287,11 +289,10 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory( std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code) { - auto& gpu = params.system.GPU(); + auto& gpu = params.gpu; gpu.ShaderNotify().MarkSharderBuilding(); - auto& engine = gpu.KeplerCompute(); - auto registry = std::make_shared<Registry>(ShaderType::Compute, engine); + auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine); const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry); const u64 uid = params.unique_identifier; auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry); @@ -320,22 +321,26 @@ std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params, precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program)); } -ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, - Core::Frontend::EmuWindow& emu_window, const Device& device) - : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system}, - emu_window{emu_window}, device{device}, disk_cache{system} {} +ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_, + Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_, const Device& device_) + : ShaderCache{rasterizer_}, emu_window{emu_window_}, gpu{gpu_}, gpu_memory{gpu_memory_}, + maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_}, device{device_} {} ShaderCacheOpenGL::~ShaderCacheOpenGL() = default; -void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, +void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { + disk_cache.BindTitleID(title_id); const std::optional transferable = disk_cache.LoadTransferable(); if (!transferable) { return; } std::vector<ShaderDiskCachePrecompiled> gl_cache; - if (!device.UseAssemblyShaders()) { + if (!device.UseAssemblyShaders() && !device.UseDriverCache()) { // Only load precompiled cache when we are not using assembly shaders gl_cache = disk_cache.LoadPrecompiled(); } @@ -355,8 +360,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, std::atomic_bool gl_cache_failed = false; const auto find_precompiled = [&gl_cache](u64 id) { - return std::find_if(gl_cache.begin(), gl_cache.end(), - [id](const auto& entry) { return entry.unique_identifier == id; }); + return std::ranges::find(gl_cache, id, &ShaderDiskCachePrecompiled::unique_identifier); }; const auto worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin, @@ -431,8 +435,8 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, return; } - if (device.UseAssemblyShaders()) { - // Don't store precompiled binaries for assembly shaders. + if (device.UseAssemblyShaders() || device.UseDriverCache()) { + // Don't store precompiled binaries for assembly shaders or when using the driver cache return; } @@ -457,7 +461,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram( const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry, const std::unordered_set<GLenum>& supported_formats) { - if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) { + if (!supported_formats.contains(precompiled_entry.binary_format)) { LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format, removing"); return {}; } @@ -481,21 +485,19 @@ ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram( Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program, VideoCommon::Shader::AsyncShaders& async_shaders) { - if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) { + if (!maxwell3d.dirty.flags[Dirty::Shaders]) { auto* last_shader = last_shaders[static_cast<std::size_t>(program)]; if (last_shader->IsBuilt()) { return last_shader; } } - auto& memory_manager{system.GPU().MemoryManager()}; - const GPUVAddr address{GetShaderAddress(system, program)}; + const GPUVAddr address{GetShaderAddress(maxwell3d, program)}; if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) { auto completed_work = async_shaders.GetCompletedWork(); for (auto& work : completed_work) { Shader* shader = TryGet(work.cpu_address); - auto& gpu = system.GPU(); gpu.ShaderNotify().MarkShaderComplete(); if (shader == nullptr) { continue; @@ -507,14 +509,13 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program, shader->AsyncGLASMBuilt(std::move(work.program.glasm)); } + auto& registry = shader->GetRegistry(); + ShaderDiskCacheEntry entry; entry.type = work.shader_type; entry.code = std::move(work.code); entry.code_b = std::move(work.code_b); entry.unique_identifier = work.uid; - - auto& registry = shader->GetRegistry(); - entry.bound_buffer = registry.GetBoundBuffer(); entry.graphics_info = registry.GetGraphicsInfo(); entry.keys = registry.GetKeys(); @@ -525,28 +526,28 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program, } // Look up shader in the cache based on address - const auto cpu_addr{memory_manager.GpuToCpuAddress(address)}; + const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)}; if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) { return last_shaders[static_cast<std::size_t>(program)] = shader; } - const auto host_ptr{memory_manager.GetPointer(address)}; + const u8* const host_ptr{gpu_memory.GetPointer(address)}; // No shader found - create a new one - ProgramCode code{GetShaderCode(memory_manager, address, host_ptr, false)}; + ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)}; ProgramCode code_b; if (program == Maxwell::ShaderProgram::VertexA) { - const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)}; - const u8* host_ptr_b = memory_manager.GetPointer(address_b); - code_b = GetShaderCode(memory_manager, address_b, host_ptr_b, false); + const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)}; + const u8* host_ptr_b = gpu_memory.GetPointer(address_b); + code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false); } const std::size_t code_size = code.size() * sizeof(u64); const u64 unique_identifier = GetUniqueIdentifier( GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b); - const ShaderParameters params{system, disk_cache, device, - *cpu_addr, host_ptr, unique_identifier}; + const ShaderParameters params{gpu, maxwell3d, disk_cache, device, + *cpu_addr, host_ptr, unique_identifier}; std::unique_ptr<Shader> shader; const auto found = runtime_cache.find(unique_identifier); @@ -568,21 +569,20 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program, } Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) { - auto& memory_manager{system.GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(code_addr)}; + const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)}; if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) { return kernel; } - const auto host_ptr{memory_manager.GetPointer(code_addr)}; // No kernel found, create a new one - ProgramCode code{GetShaderCode(memory_manager, code_addr, host_ptr, true)}; + const u8* host_ptr{gpu_memory.GetPointer(code_addr)}; + ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)}; const std::size_t code_size{code.size() * sizeof(u64)}; const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)}; - const ShaderParameters params{system, disk_cache, device, - *cpu_addr, host_ptr, unique_identifier}; + const ShaderParameters params{gpu, kepler_compute, disk_cache, device, + *cpu_addr, host_ptr, unique_identifier}; std::unique_ptr<Shader> kernel; const auto found = runtime_cache.find(unique_identifier); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 7528ac686..2aed0697e 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -25,8 +25,8 @@ #include "video_core/shader/shader_ir.h" #include "video_core/shader_cache.h" -namespace Core { -class System; +namespace Tegra { +class MemoryManager; } namespace Core::Frontend { @@ -57,11 +57,12 @@ struct PrecompiledShader { }; struct ShaderParameters { - Core::System& system; + Tegra::GPU& gpu; + Tegra::Engines::ConstBufferEngineInterface& engine; ShaderDiskCacheOpenGL& disk_cache; const Device& device; VAddr cpu_addr; - u8* host_ptr; + const u8* host_ptr; u64 unique_identifier; }; @@ -107,7 +108,7 @@ public: private: explicit Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry, ShaderEntries entries, - ProgramSharedPtr program, bool is_built = true); + ProgramSharedPtr program, bool is_built_ = true); std::shared_ptr<VideoCommon::Shader::Registry> registry; ShaderEntries entries; @@ -118,12 +119,15 @@ private: class ShaderCacheOpenGL final : public VideoCommon::ShaderCache<Shader> { public: - explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, - Core::Frontend::EmuWindow& emu_window, const Device& device); + explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_, + Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_, const Device& device_); ~ShaderCacheOpenGL() override; /// Loads disk cache for the current game - void LoadDiskCache(const std::atomic_bool& stop_loading, + void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback); /// Gets the current specified shader stage program @@ -138,9 +142,13 @@ private: const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry, const std::unordered_set<GLenum>& supported_formats); - Core::System& system; Core::Frontend::EmuWindow& emu_window; + Tegra::GPU& gpu; + Tegra::MemoryManager& gpu_memory; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; const Device& device; + ShaderDiskCacheOpenGL disk_cache; std::unordered_map<u64, PrecompiledShader> runtime_cache; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 3f75fcd2b..ac78d344c 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -14,6 +14,7 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/div_ceil.h" #include "common/logging/log.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/shader_type.h" @@ -38,11 +39,9 @@ using Tegra::Shader::IpaSampleMode; using Tegra::Shader::PixelImap; using Tegra::Shader::Register; using Tegra::Shader::TextureType; -using VideoCommon::Shader::BuildTransformFeedback; -using VideoCommon::Shader::Registry; -using namespace std::string_literals; using namespace VideoCommon::Shader; +using namespace std::string_literals; using Maxwell = Tegra::Engines::Maxwell3D::Regs; using Operation = const OperationNode&; @@ -65,7 +64,7 @@ using TextureIR = std::variant<TextureOffset, TextureDerivates, TextureArgument> constexpr u32 MAX_CONSTBUFFER_SCALARS = static_cast<u32>(Maxwell::MaxConstBufferSize) / sizeof(u32); constexpr u32 MAX_CONSTBUFFER_ELEMENTS = MAX_CONSTBUFFER_SCALARS / sizeof(u32); -constexpr std::string_view CommonDeclarations = R"(#define ftoi floatBitsToInt +constexpr std::string_view COMMON_DECLARATIONS = R"(#define ftoi floatBitsToInt #define ftou floatBitsToUint #define itof intBitsToFloat #define utof uintBitsToFloat @@ -78,10 +77,6 @@ bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{ const float fswzadd_modifiers_a[] = float[4](-1.0f, 1.0f, -1.0f, 0.0f ); const float fswzadd_modifiers_b[] = float[4](-1.0f, -1.0f, 1.0f, -1.0f ); - -layout (std140, binding = {}) uniform vs_config {{ - float y_direction; -}}; )"; class ShaderWriter final { @@ -131,7 +126,7 @@ private: class Expression final { public: - Expression(std::string code, Type type) : code{std::move(code)}, type{type} { + Expression(std::string code_, Type type_) : code{std::move(code_)}, type{type_} { ASSERT(type != Type::Void); } Expression() : type{Type::Void} {} @@ -148,8 +143,8 @@ public: ASSERT(type == Type::Void); } - std::string As(Type type) const { - switch (type) { + std::string As(Type type_) const { + switch (type_) { case Type::Bool: return AsBool(); case Type::Bool2: @@ -316,7 +311,7 @@ std::pair<const char*, u32> GetPrimitiveDescription(Maxwell::PrimitiveTopology t case Maxwell::PrimitiveTopology::TriangleStripAdjacency: return {"triangles_adjacency", 6}; default: - UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology)); + UNIMPLEMENTED_MSG("topology={}", topology); return {"points", 1}; } } @@ -342,7 +337,7 @@ std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { case Tegra::Shader::OutputTopology::TriangleStrip: return "triangle_strip"; default: - UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology)); + UNIMPLEMENTED_MSG("Unknown output topology: {}", topology); return "points"; } } @@ -403,13 +398,6 @@ std::string FlowStackTopName(MetaStackClass stack) { return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack)); } -bool UseUnifiedUniforms(const Device& device, const ShaderIR& ir, ShaderType stage) { - const u32 num_ubos = static_cast<u32>(ir.GetConstantBuffers().size()); - // We waste one UBO for emulation - const u32 num_available_ubos = device.GetMaxUniformBuffers(stage) - 1; - return num_ubos > num_available_ubos; -} - struct GenericVaryingDescription { std::string name; u8 first_element = 0; @@ -418,11 +406,11 @@ struct GenericVaryingDescription { class GLSLDecompiler final { public: - explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry, - ShaderType stage, std::string_view identifier, std::string_view suffix) - : device{device}, ir{ir}, registry{registry}, stage{stage}, identifier{identifier}, - suffix{suffix}, header{ir.GetHeader()}, use_unified_uniforms{ - UseUnifiedUniforms(device, ir, stage)} { + explicit GLSLDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_, + ShaderType stage_, std::string_view identifier_, + std::string_view suffix_) + : device{device_}, ir{ir_}, registry{registry_}, stage{stage_}, + identifier{identifier_}, suffix{suffix_}, header{ir.GetHeader()} { if (stage != ShaderType::Compute) { transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo()); } @@ -516,7 +504,8 @@ private: if (!identifier.empty()) { code.AddLine("// {}", identifier); } - code.AddLine("#version 440 {}", ir.UsesLegacyVaryings() ? "compatibility" : "core"); + const bool use_compatibility = ir.UsesLegacyVaryings() || ir.UsesYNegate(); + code.AddLine("#version 440 {}", use_compatibility ? "compatibility" : "core"); code.AddLine("#extension GL_ARB_separate_shader_objects : enable"); if (device.HasShaderBallot()) { code.AddLine("#extension GL_ARB_shader_ballot : require"); @@ -542,7 +531,7 @@ private: code.AddNewLine(); - code.AddLine(CommonDeclarations, EmulationUniformBlockBinding); + code.AddLine(COMMON_DECLARATIONS); } void DeclareVertex() { @@ -744,7 +733,7 @@ private: case PixelImap::Unused: break; } - UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute)); + UNIMPLEMENTED_MSG("Unknown attribute usage index={}", attribute); return {}; } @@ -777,16 +766,16 @@ private: name = "gs_" + name + "[]"; } - std::string suffix; + std::string suffix_; if (stage == ShaderType::Fragment) { const auto input_mode{header.ps.GetPixelImap(location)}; if (input_mode == PixelImap::Unused) { return; } - suffix = GetInputFlags(input_mode); + suffix_ = GetInputFlags(input_mode); } - code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix, name); + code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix_, name); } void DeclareOutputAttributes() { @@ -813,7 +802,7 @@ private: const u8 location = static_cast<u8>(static_cast<u32>(index) * 4 + element); const auto it = transform_feedback.find(location); if (it == transform_feedback.end()) { - return {}; + return std::nullopt; } return it->second.components; } @@ -865,20 +854,9 @@ private: } void DeclareConstantBuffers() { - if (use_unified_uniforms) { - const u32 binding = device.GetBaseBindings(stage).shader_storage_buffer + - static_cast<u32>(ir.GetGlobalMemory().size()); - code.AddLine("layout (std430, binding = {}) readonly buffer UnifiedUniforms {{", - binding); - code.AddLine(" uint cbufs[];"); - code.AddLine("}};"); - code.AddNewLine(); - return; - } - u32 binding = device.GetBaseBindings(stage).uniform_buffer; - for (const auto [index, info] : ir.GetConstantBuffers()) { - const u32 num_elements = Common::AlignUp(info.GetSize(), 4) / 4; + for (const auto& [index, info] : ir.GetConstantBuffers()) { + const u32 num_elements = Common::DivCeil(info.GetSize(), 4 * sizeof(u32)); const u32 size = info.IsIndirect() ? MAX_CONSTBUFFER_ELEMENTS : num_elements; code.AddLine("layout (std140, binding = {}) uniform {} {{", binding++, GetConstBufferBlock(index)); @@ -1081,29 +1059,17 @@ private: if (const auto cbuf = std::get_if<CbufNode>(&*node)) { const Node offset = cbuf->GetOffset(); - const u32 base_unified_offset = cbuf->GetIndex() * MAX_CONSTBUFFER_SCALARS; if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) { // Direct access const u32 offset_imm = immediate->GetValue(); ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); - if (use_unified_uniforms) { - return {fmt::format("cbufs[{}]", base_unified_offset + offset_imm / 4), - Type::Uint}; - } else { - return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), - offset_imm / (4 * 4), (offset_imm / 4) % 4), - Type::Uint}; - } - } - - // Indirect access - if (use_unified_uniforms) { - return {fmt::format("cbufs[{} + ({} >> 2)]", base_unified_offset, - Visit(offset).AsUint()), + return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), + offset_imm / (4 * 4), (offset_imm / 4) % 4), Type::Uint}; } + // Indirect access const std::string final_offset = code.GenerateTemporary(); code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint()); @@ -1251,7 +1217,7 @@ private: } break; } - UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); + UNIMPLEMENTED_MSG("Unhandled input attribute: {}", attribute); return {"0", Type::Int}; } @@ -1295,21 +1261,21 @@ private: switch (element) { case 0: UNIMPLEMENTED(); - return {}; + return std::nullopt; case 1: if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) { - return {}; + return std::nullopt; } return {{"gl_Layer", Type::Int}}; case 2: if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) { - return {}; + return std::nullopt; } return {{"gl_ViewportIndex", Type::Int}}; case 3: return {{"gl_PointSize", Type::Float}}; } - return {}; + return std::nullopt; case Attribute::Index::FrontColor: return {{"gl_FrontColor"s + GetSwizzle(element), Type::Float}}; case Attribute::Index::FrontSecondaryColor: @@ -1331,8 +1297,8 @@ private: GetSwizzle(element)), Type::Float}}; } - UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); - return {}; + UNIMPLEMENTED_MSG("Unhandled output attribute: {}", attribute); + return std::nullopt; } } @@ -1443,8 +1409,10 @@ private: return expr + ", vec2(0.0), vec2(0.0))"; case TextureType::TextureCube: return expr + ", vec3(0.0), vec3(0.0))"; + default: + UNREACHABLE(); + break; } - UNREACHABLE(); } for (const auto& variant : extras) { @@ -2054,15 +2022,19 @@ private: } Expression Texture(Operation operation) { - const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); - ASSERT(meta); - - std::string expr = GenerateTexture( - operation, "", {TextureOffset{}, TextureArgument{Type::Float, meta->bias}}); - if (meta->sampler.is_shadow) { - expr = "vec4(" + expr + ')'; + const auto meta = std::get<MetaTexture>(operation.GetMeta()); + const bool separate_dc = meta.sampler.type == TextureType::TextureCube && + meta.sampler.is_array && meta.sampler.is_shadow; + // TODO: Replace this with an array and make GenerateTexture use C++20 std::span + const std::vector<TextureIR> extras{ + TextureOffset{}, + TextureArgument{Type::Float, meta.bias}, + }; + std::string expr = GenerateTexture(operation, "", extras, separate_dc); + if (meta.sampler.is_shadow) { + expr = fmt::format("vec4({})", expr); } - return {expr + GetSwizzle(meta->element), Type::Float}; + return {expr + GetSwizzle(meta.element), Type::Float}; } Expression TextureLod(Operation operation) { @@ -2094,13 +2066,13 @@ private: const auto type = meta.sampler.is_shadow ? Type::Float : Type::Int; const bool separate_dc = meta.sampler.is_shadow; - std::vector<TextureIR> ir; + std::vector<TextureIR> ir_; if (meta.sampler.is_shadow) { - ir = {TextureOffset{}}; + ir_ = {TextureOffset{}}; } else { - ir = {TextureOffset{}, TextureArgument{type, meta.component}}; + ir_ = {TextureOffset{}, TextureArgument{type, meta.component}}; } - return {GenerateTexture(operation, "Gather", ir, separate_dc) + GetSwizzle(meta.element), + return {GenerateTexture(operation, "Gather", ir_, separate_dc) + GetSwizzle(meta.element), Type::Float}; } @@ -2287,7 +2259,6 @@ private: } } } - if (header.ps.omap.depth) { // The depth output is always 2 registers after the last color output, and current_reg // already contains one past the last color register. @@ -2331,7 +2302,8 @@ private: } Expression YNegate(Operation operation) { - return {"y_direction", Type::Float}; + // Y_NEGATE is mapped to this uniform value + return {"gl_FrontMaterial.ambient.a", Type::Float}; } template <u32 element> @@ -2746,11 +2718,11 @@ private: } } - std::string GetSampler(const Sampler& sampler) const { + std::string GetSampler(const SamplerEntry& sampler) const { return AppendSuffix(sampler.index, "sampler"); } - std::string GetImage(const Image& image) const { + std::string GetImage(const ImageEntry& image) const { return AppendSuffix(image.index, "image"); } @@ -2781,7 +2753,6 @@ private: const std::string_view identifier; const std::string_view suffix; const Header header; - const bool use_unified_uniforms; std::unordered_map<u8, VaryingTFB> transform_feedback; ShaderWriter code; @@ -2795,7 +2766,7 @@ std::string GetFlowVariable(u32 index) { class ExprDecompiler { public: - explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {} + explicit ExprDecompiler(GLSLDecompiler& decomp_) : decomp{decomp_} {} void operator()(const ExprAnd& expr) { inner += '('; @@ -2850,7 +2821,7 @@ private: class ASTDecompiler { public: - explicit ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {} + explicit ASTDecompiler(GLSLDecompiler& decomp_) : decomp{decomp_} {} void operator()(const ASTProgram& ast) { ASTNode current = ast.nodes.GetFirst(); @@ -2997,8 +2968,10 @@ ShaderEntries MakeEntries(const Device& device, const ShaderIR& ir, ShaderType s for (std::size_t i = 0; i < std::size(clip_distances); ++i) { entries.clip_distances = (clip_distances[i] ? 1U : 0U) << i; } + for (const auto& buffer : entries.const_buffers) { + entries.enabled_uniform_buffers |= 1U << buffer.GetIndex(); + } entries.shader_length = ir.GetLength(); - entries.use_unified_uniforms = UseUnifiedUniforms(device, ir, stage); return entries; } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 451c9689a..0397a000c 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -20,13 +20,13 @@ namespace OpenGL { class Device; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -using SamplerEntry = VideoCommon::Shader::Sampler; -using ImageEntry = VideoCommon::Shader::Image; +using SamplerEntry = VideoCommon::Shader::SamplerEntry; +using ImageEntry = VideoCommon::Shader::ImageEntry; class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer { public: - explicit ConstBufferEntry(u32 max_offset, bool is_indirect, u32 index) - : VideoCommon::Shader::ConstBuffer{max_offset, is_indirect}, index{index} {} + explicit ConstBufferEntry(u32 max_offset_, bool is_indirect_, u32 index_) + : ConstBuffer{max_offset_, is_indirect_}, index{index_} {} u32 GetIndex() const { return index; @@ -37,10 +37,10 @@ private: }; struct GlobalMemoryEntry { - constexpr explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, bool is_read, - bool is_written) - : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_read{is_read}, is_written{ - is_written} {} + constexpr explicit GlobalMemoryEntry(u32 cbuf_index_, u32 cbuf_offset_, bool is_read_, + bool is_written_) + : cbuf_index{cbuf_index_}, cbuf_offset{cbuf_offset_}, is_read{is_read_}, is_written{ + is_written_} {} u32 cbuf_index = 0; u32 cbuf_offset = 0; @@ -55,7 +55,7 @@ struct ShaderEntries { std::vector<ImageEntry> images; std::size_t shader_length{}; u32 clip_distances{}; - bool use_unified_uniforms{}; + u32 enabled_uniform_buffers{}; }; ShaderEntries MakeEntries(const Device& device, const VideoCommon::Shader::ShaderIR& ir, diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index 40c0877c1..955b2abc4 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -206,13 +206,17 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { flat_bindless_samplers.size(); } -ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {} +ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default; ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default; +void ShaderDiskCacheOpenGL::BindTitleID(u64 title_id_) { + title_id = title_id_; +} + std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() { // Skip games without title id - const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; + const bool has_title_id = title_id != 0; if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) { return std::nullopt; } @@ -313,8 +317,7 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo return std::nullopt; } } - - return std::move(entries); + return entries; } void ShaderDiskCacheOpenGL::InvalidateTransferable() { @@ -340,7 +343,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) { } const u64 id = entry.unique_identifier; - if (stored_transferable.find(id) != stored_transferable.end()) { + if (stored_transferable.contains(id)) { // The shader already exists return; } @@ -474,7 +477,7 @@ std::string ShaderDiskCacheOpenGL::GetBaseDir() const { } std::string ShaderDiskCacheOpenGL::GetTitleID() const { - return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID()); + return fmt::format("{:016X}", title_id); } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h index db2bb73bc..aef841c1d 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -21,10 +21,6 @@ #include "video_core/engines/shader_type.h" #include "video_core/shader/registry.h" -namespace Core { -class System; -} - namespace Common::FS { class IOFile; } @@ -70,9 +66,12 @@ struct ShaderDiskCachePrecompiled { class ShaderDiskCacheOpenGL { public: - explicit ShaderDiskCacheOpenGL(Core::System& system); + explicit ShaderDiskCacheOpenGL(); ~ShaderDiskCacheOpenGL(); + /// Binds a title ID for all future operations. + void BindTitleID(u64 title_id); + /// Loads transferable cache. If file has a old version or on failure, it deletes the file. std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable(); @@ -157,8 +156,6 @@ private: return LoadArrayFromPrecompiled(&object, 1); } - Core::System& system; - // Stores whole precompiled cache which will be read from or saved to the precompiled chache // file FileSys::VectorVfsFile precompiled_cache_virtual_file; @@ -168,8 +165,11 @@ private: // Stored transferable shaders std::unordered_set<u64> stored_transferable; + /// Title ID to operate on + u64 title_id = 0; + // The cache has been loaded at boot - bool is_usable{}; + bool is_usable = false; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 691c6c79b..553e6e8d6 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -83,6 +83,21 @@ void ProgramManager::RestoreGuestPipeline() { } } +void ProgramManager::BindHostCompute(GLuint program) { + if (use_assembly_programs) { + glDisable(GL_COMPUTE_PROGRAM_NV); + } + glUseProgram(program); + is_graphics_bound = false; +} + +void ProgramManager::RestoreGuestCompute() { + if (use_assembly_programs) { + glEnable(GL_COMPUTE_PROGRAM_NV); + glUseProgram(0); + } +} + void ProgramManager::UseVertexShader(GLuint program) { if (use_assembly_programs) { BindProgram(GL_VERTEX_PROGRAM_NV, program, current_state.vertex, vertex_enabled); diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 950e0dfcb..ad42cce74 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -45,6 +45,12 @@ public: /// Rewinds BindHostPipeline state changes. void RestoreGuestPipeline(); + /// Binds an OpenGL GLSL program object unsynchronized with the guest state. + void BindHostCompute(GLuint program); + + /// Rewinds BindHostCompute state changes. + void RestoreGuestCompute(); + void UseVertexShader(GLuint program); void UseGeometryShader(GLuint program); void UseFragmentShader(GLuint program); diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp index d24fad3de..dbdf5230f 100644 --- a/src/video_core/renderer_opengl/gl_state_tracker.cpp +++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp @@ -13,7 +13,7 @@ #include "video_core/renderer_opengl/gl_state_tracker.h" #define OFF(field_name) MAXWELL3D_REG_INDEX(field_name) -#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / sizeof(u32)) +#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32))) namespace OpenGL { @@ -36,16 +36,10 @@ void SetupDirtyColorMasks(Tables& tables) { FillBlock(tables[1], OFF(color_mask), NUM(color_mask), ColorMasks); } -void SetupDirtyVertexArrays(Tables& tables) { - static constexpr std::size_t num_array = 3; +void SetupDirtyVertexInstances(Tables& tables) { static constexpr std::size_t instance_base_offset = 3; for (std::size_t i = 0; i < Regs::NumVertexArrays; ++i) { const std::size_t array_offset = OFF(vertex_array) + i * NUM(vertex_array[0]); - const std::size_t limit_offset = OFF(vertex_array_limit) + i * NUM(vertex_array_limit[0]); - - FillBlock(tables, array_offset, num_array, VertexBuffer0 + i, VertexBuffers); - FillBlock(tables, limit_offset, NUM(vertex_array_limit), VertexBuffer0 + i, VertexBuffers); - const std::size_t instance_array_offset = array_offset + instance_base_offset; tables[0][instance_array_offset] = static_cast<u8>(VertexInstance0 + i); tables[1][instance_array_offset] = VertexInstances; @@ -214,16 +208,14 @@ void SetupDirtyMisc(Tables& tables) { } // Anonymous namespace -StateTracker::StateTracker(Core::System& system) : system{system} {} - -void StateTracker::Initialize() { - auto& dirty = system.GPU().Maxwell3D().dirty; +StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} { + auto& dirty = gpu.Maxwell3D().dirty; auto& tables = dirty.tables; - SetupDirtyRenderTargets(tables); + SetupDirtyFlags(tables); SetupDirtyColorMasks(tables); SetupDirtyViewports(tables); SetupDirtyScissors(tables); - SetupDirtyVertexArrays(tables); + SetupDirtyVertexInstances(tables); SetupDirtyVertexFormat(tables); SetupDirtyShaders(tables); SetupDirtyPolygonModes(tables); @@ -243,12 +235,6 @@ void StateTracker::Initialize() { SetupDirtyClipControl(tables); SetupDirtyDepthClampEnabled(tables); SetupDirtyMisc(tables); - - auto& store = dirty.on_write_stores; - store[VertexBuffers] = true; - for (std::size_t i = 0; i < Regs::NumVertexArrays; ++i) { - store[VertexBuffer0 + i] = true; - } } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h index 0f823288e..94c905116 100644 --- a/src/video_core/renderer_opengl/gl_state_tracker.h +++ b/src/video_core/renderer_opengl/gl_state_tracker.h @@ -13,8 +13,8 @@ #include "video_core/dirty_flags.h" #include "video_core/engines/maxwell_3d.h" -namespace Core { -class System; +namespace Tegra { +class GPU; } namespace OpenGL { @@ -28,10 +28,6 @@ enum : u8 { VertexFormat0, VertexFormat31 = VertexFormat0 + 31, - VertexBuffers, - VertexBuffer0, - VertexBuffer31 = VertexBuffer0 + 31, - VertexInstances, VertexInstance0, VertexInstance31 = VertexInstance0 + 31, @@ -90,9 +86,7 @@ static_assert(Last <= std::numeric_limits<u8>::max()); class StateTracker { public: - explicit StateTracker(Core::System& system); - - void Initialize(); + explicit StateTracker(Tegra::GPU& gpu); void BindIndexBuffer(GLuint new_index_buffer) { if (index_buffer == new_index_buffer) { @@ -102,14 +96,40 @@ public: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, new_index_buffer); } + void BindFramebuffer(GLuint new_framebuffer) { + if (framebuffer == new_framebuffer) { + return; + } + framebuffer = new_framebuffer; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); + } + + void ClipControl(GLenum new_origin, GLenum new_depth) { + if (new_origin == origin && new_depth == depth) { + return; + } + origin = new_origin; + depth = new_depth; + glClipControl(origin, depth); + } + + void SetYNegate(bool new_y_negate) { + if (new_y_negate == y_negate) { + return; + } + // Y_NEGATE is mapped to gl_FrontMaterial.ambient.a + y_negate = new_y_negate; + const std::array ambient{0.0f, 0.0f, 0.0f, y_negate ? -1.0f : 1.0f}; + glMaterialfv(GL_FRONT, GL_AMBIENT, ambient.data()); + } + void NotifyScreenDrawVertexArray() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::VertexFormats] = true; flags[OpenGL::Dirty::VertexFormat0 + 0] = true; flags[OpenGL::Dirty::VertexFormat0 + 1] = true; - flags[OpenGL::Dirty::VertexBuffers] = true; - flags[OpenGL::Dirty::VertexBuffer0] = true; + flags[VideoCommon::Dirty::VertexBuffers] = true; + flags[VideoCommon::Dirty::VertexBuffer0] = true; flags[OpenGL::Dirty::VertexInstances] = true; flags[OpenGL::Dirty::VertexInstance0 + 0] = true; @@ -117,100 +137,87 @@ public: } void NotifyPolygonModes() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::PolygonModes] = true; flags[OpenGL::Dirty::PolygonModeFront] = true; flags[OpenGL::Dirty::PolygonModeBack] = true; } void NotifyViewport0() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::Viewports] = true; flags[OpenGL::Dirty::Viewport0] = true; } void NotifyScissor0() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::Scissors] = true; flags[OpenGL::Dirty::Scissor0] = true; } - void NotifyColorMask0() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; + void NotifyColorMask(size_t index) { flags[OpenGL::Dirty::ColorMasks] = true; - flags[OpenGL::Dirty::ColorMask0] = true; + flags[OpenGL::Dirty::ColorMask0 + index] = true; } void NotifyBlend0() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::BlendStates] = true; flags[OpenGL::Dirty::BlendState0] = true; } void NotifyFramebuffer() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[VideoCommon::Dirty::RenderTargets] = true; } void NotifyFrontFace() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::FrontFace] = true; } void NotifyCullTest() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::CullTest] = true; } void NotifyDepthMask() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::DepthMask] = true; } void NotifyDepthTest() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::DepthTest] = true; } void NotifyStencilTest() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::StencilTest] = true; } void NotifyPolygonOffset() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::PolygonOffset] = true; } void NotifyRasterizeEnable() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::RasterizeEnable] = true; } void NotifyFramebufferSRGB() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::FramebufferSRGB] = true; } void NotifyLogicOp() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::LogicOp] = true; } void NotifyClipControl() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::ClipControl] = true; } void NotifyAlphaTest() { - auto& flags = system.GPU().Maxwell3D().dirty.flags; flags[OpenGL::Dirty::AlphaTest] = true; } private: - Core::System& system; + Tegra::Engines::Maxwell3D::DirtyState::Flags& flags; + GLuint framebuffer = 0; GLuint index_buffer = 0; + GLenum origin = GL_LOWER_LEFT; + GLenum depth = GL_NEGATIVE_ONE_TO_ONE; + bool y_negate = false; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index 887995cf4..77b3ee0fe 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -1,80 +1,64 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <tuple> -#include <vector> +#include <array> +#include <memory> +#include <span> + +#include <glad/glad.h> #include "common/alignment.h" #include "common/assert.h" -#include "common/microprofile.h" -#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_stream_buffer.h" -MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning", - MP_RGB(128, 128, 192)); - namespace OpenGL { -OGLStreamBuffer::OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage) - : buffer_size(size) { - gl_buffer.Create(); - - GLsizeiptr allocate_size = size; - if (vertex_data_usage) { - // On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer - // read position is near the end and is an out-of-bound access to the vertex buffer. This is - // probably a bug in the driver and is related to the usage of vec3<byte> attributes in the - // vertex array. Doubling the allocation size for the vertex buffer seems to avoid the - // crash. - allocate_size *= 2; - } - - static constexpr GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; - glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags); - mapped_ptr = static_cast<u8*>( - glMapNamedBufferRange(gl_buffer.handle, 0, buffer_size, flags | GL_MAP_FLUSH_EXPLICIT_BIT)); - - if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) { - glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_ONLY); - glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address); +StreamBuffer::StreamBuffer() { + static constexpr GLenum flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; + buffer.Create(); + glObjectLabel(GL_BUFFER, buffer.handle, -1, "Stream Buffer"); + glNamedBufferStorage(buffer.handle, STREAM_BUFFER_SIZE, nullptr, flags); + mapped_pointer = + static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, STREAM_BUFFER_SIZE, flags)); + for (OGLSync& sync : fences) { + sync.Create(); } } -OGLStreamBuffer::~OGLStreamBuffer() { - glUnmapNamedBuffer(gl_buffer.handle); - gl_buffer.Release(); -} - -std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr alignment) { - ASSERT(size <= buffer_size); - ASSERT(alignment <= buffer_size); - mapped_size = size; - - if (alignment > 0) { - buffer_pos = Common::AlignUp<std::size_t>(buffer_pos, alignment); +std::pair<std::span<u8>, size_t> StreamBuffer::Request(size_t size) noexcept { + ASSERT(size < REGION_SIZE); + for (size_t region = Region(used_iterator), region_end = Region(iterator); region < region_end; + ++region) { + fences[region].Create(); } + used_iterator = iterator; - bool invalidate = false; - if (buffer_pos + size > buffer_size) { - MICROPROFILE_SCOPE(OpenGL_StreamBuffer); - glInvalidateBufferData(gl_buffer.handle); - - buffer_pos = 0; - invalidate = true; + for (size_t region = Region(free_iterator) + 1, + region_end = std::min(Region(iterator + size) + 1, NUM_SYNCS); + region < region_end; ++region) { + glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED); + fences[region].Release(); } - - return std::make_tuple(mapped_ptr + buffer_pos, buffer_pos, invalidate); -} - -void OGLStreamBuffer::Unmap(GLsizeiptr size) { - ASSERT(size <= mapped_size); - - if (size > 0) { - glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos, size); + if (iterator + size >= free_iterator) { + free_iterator = iterator + size; } - - buffer_pos += size; + if (iterator + size > STREAM_BUFFER_SIZE) { + for (size_t region = Region(used_iterator); region < NUM_SYNCS; ++region) { + fences[region].Create(); + } + used_iterator = 0; + iterator = 0; + free_iterator = size; + + for (size_t region = 0, region_end = Region(size); region <= region_end; ++region) { + glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED); + fences[region].Release(); + } + } + const size_t offset = iterator; + iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); + return {std::span(mapped_pointer + offset, size), offset}; } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index 307a67113..6dbb6bfba 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -1,55 +1,50 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <tuple> +#include <array> +#include <memory> +#include <span> +#include <utility> + #include <glad/glad.h> + #include "common/common_types.h" #include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { -class Device; +class StreamBuffer { + static constexpr size_t STREAM_BUFFER_SIZE = 64 * 1024 * 1024; + static constexpr size_t NUM_SYNCS = 16; + static constexpr size_t REGION_SIZE = STREAM_BUFFER_SIZE / NUM_SYNCS; + static constexpr size_t MAX_ALIGNMENT = 256; + static_assert(STREAM_BUFFER_SIZE % MAX_ALIGNMENT == 0); + static_assert(STREAM_BUFFER_SIZE % NUM_SYNCS == 0); + static_assert(REGION_SIZE % MAX_ALIGNMENT == 0); -class OGLStreamBuffer : private NonCopyable { public: - explicit OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage); - ~OGLStreamBuffer(); - - /* - * Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes - * and the optional alignment requirement. - * If the buffer is full, the whole buffer is reallocated which invalidates old chunks. - * The return values are the pointer to the new chunk, the offset within the buffer, - * and the invalidation flag for previous chunks. - * The actual used size must be specified on unmapping the chunk. - */ - std::tuple<u8*, GLintptr, bool> Map(GLsizeiptr size, GLintptr alignment = 0); - - void Unmap(GLsizeiptr size); - - GLuint Handle() const { - return gl_buffer.handle; - } + explicit StreamBuffer(); - u64 Address() const { - return gpu_address; - } + [[nodiscard]] std::pair<std::span<u8>, size_t> Request(size_t size) noexcept; - GLsizeiptr Size() const noexcept { - return buffer_size; + [[nodiscard]] GLuint Handle() const noexcept { + return buffer.handle; } private: - OGLBuffer gl_buffer; + [[nodiscard]] static size_t Region(size_t offset) noexcept { + return offset / REGION_SIZE; + } - GLuint64EXT gpu_address = 0; - GLintptr buffer_pos = 0; - GLsizeiptr buffer_size = 0; - GLsizeiptr mapped_size = 0; - u8* mapped_ptr = nullptr; + size_t iterator = 0; + size_t used_iterator = 0; + size_t free_iterator = 0; + u8* mapped_pointer = nullptr; + OGLBuffer buffer; + std::array<OGLSync, NUM_SYNCS> fences; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index f403f388a..12434db67 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -2,37 +2,60 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/assert.h" -#include "common/bit_util.h" -#include "common/common_types.h" -#include "common/microprofile.h" -#include "common/scope_exit.h" -#include "core/core.h" -#include "video_core/morton.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" +#include <algorithm> +#include <array> +#include <bit> +#include <string> + +#include <glad/glad.h> + +#include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state_tracker.h" #include "video_core/renderer_opengl/gl_texture_cache.h" -#include "video_core/renderer_opengl/utils.h" -#include "video_core/texture_cache/surface_base.h" +#include "video_core/renderer_opengl/maxwell_to_gl.h" +#include "video_core/renderer_opengl/util_shaders.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/format_lookup_table.h" +#include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/texture_cache.h" -#include "video_core/textures/convert.h" -#include "video_core/textures/texture.h" +#include "video_core/textures/decoders.h" namespace OpenGL { -using Tegra::Texture::SwizzleSource; -using VideoCore::MortonSwizzleMode; +namespace { +using Tegra::Texture::SwizzleSource; +using Tegra::Texture::TextureMipmapFilter; +using Tegra::Texture::TextureType; +using Tegra::Texture::TICEntry; +using Tegra::Texture::TSCEntry; +using VideoCommon::CalculateLevelStrideAlignment; +using VideoCommon::ImageCopy; +using VideoCommon::ImageFlagBits; +using VideoCommon::ImageType; +using VideoCommon::NUM_RT; +using VideoCommon::SamplesLog2; +using VideoCommon::SwizzleParameters; +using VideoCore::Surface::BytesPerBlock; +using VideoCore::Surface::IsPixelFormatASTC; +using VideoCore::Surface::IsPixelFormatSRGB; +using VideoCore::Surface::MaxPixelFormat; using VideoCore::Surface::PixelFormat; -using VideoCore::Surface::SurfaceTarget; using VideoCore::Surface::SurfaceType; -MICROPROFILE_DEFINE(OpenGL_Texture_Upload, "OpenGL", "Texture Upload", MP_RGB(128, 192, 128)); -MICROPROFILE_DEFINE(OpenGL_Texture_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 128)); -MICROPROFILE_DEFINE(OpenGL_Texture_Buffer_Copy, "OpenGL", "Texture Buffer Copy", - MP_RGB(128, 192, 128)); +struct CopyOrigin { + GLint level; + GLint x; + GLint y; + GLint z; +}; -namespace { +struct CopyRegion { + GLsizei width; + GLsizei height; + GLsizei depth; +}; struct FormatTuple { GLenum internal_format; @@ -40,7 +63,7 @@ struct FormatTuple { GLenum type = GL_NONE; }; -constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{ +constexpr std::array<FormatTuple, MaxPixelFormat> FORMAT_TABLE = {{ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_UNORM {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // A8B8G8R8_SNORM {GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE}, // A8B8G8R8_SINT @@ -103,72 +126,113 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM - // Compressed sRGB formats - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB - {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB - {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB - {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB - {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB - {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB - {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB - {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB - {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB - {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM - {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB - {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT - - // Depth formats - {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT - {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM - - // DepthStencil formats - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB + {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB + {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB + {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB + {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB + {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB + {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB + {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB + {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB + {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM + {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB + {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT + {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT + {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // D32_FLOAT_S8_UINT }}; +constexpr std::array ACCELERATED_FORMATS{ + GL_RGBA32F, GL_RGBA16F, GL_RG32F, GL_RG16F, GL_R11F_G11F_B10F, GL_R32F, + GL_R16F, GL_RGBA32UI, GL_RGBA16UI, GL_RGB10_A2UI, GL_RGBA8UI, GL_RG32UI, + GL_RG16UI, GL_RG8UI, GL_R32UI, GL_R16UI, GL_R8UI, GL_RGBA32I, + GL_RGBA16I, GL_RGBA8I, GL_RG32I, GL_RG16I, GL_RG8I, GL_R32I, + GL_R16I, GL_R8I, GL_RGBA16, GL_RGB10_A2, GL_RGBA8, GL_RG16, + GL_RG8, GL_R16, GL_R8, GL_RGBA16_SNORM, GL_RGBA8_SNORM, GL_RG16_SNORM, + GL_RG8_SNORM, GL_R16_SNORM, GL_R8_SNORM, +}; + const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { - ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size()); - return tex_format_tuples[static_cast<std::size_t>(pixel_format)]; + ASSERT(static_cast<size_t>(pixel_format) < FORMAT_TABLE.size()); + return FORMAT_TABLE[static_cast<size_t>(pixel_format)]; } -GLenum GetTextureTarget(const SurfaceTarget& target) { - switch (target) { - case SurfaceTarget::TextureBuffer: +GLenum ImageTarget(const VideoCommon::ImageInfo& info) { + switch (info.type) { + case ImageType::e1D: + return GL_TEXTURE_1D_ARRAY; + case ImageType::e2D: + if (info.num_samples > 1) { + return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + } + return GL_TEXTURE_2D_ARRAY; + case ImageType::e3D: + return GL_TEXTURE_3D; + case ImageType::Linear: + return GL_TEXTURE_2D_ARRAY; + case ImageType::Buffer: return GL_TEXTURE_BUFFER; - case SurfaceTarget::Texture1D: + } + UNREACHABLE_MSG("Invalid image type={}", info.type); + return GL_NONE; +} + +GLenum ImageTarget(ImageViewType type, int num_samples = 1) { + const bool is_multisampled = num_samples > 1; + switch (type) { + case ImageViewType::e1D: return GL_TEXTURE_1D; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D; - case SurfaceTarget::Texture3D: + case ImageViewType::e2D: + return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + case ImageViewType::Cube: + return GL_TEXTURE_CUBE_MAP; + case ImageViewType::e3D: return GL_TEXTURE_3D; - case SurfaceTarget::Texture1DArray: + case ImageViewType::e1DArray: return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2DArray: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP; - case SurfaceTarget::TextureCubeArray: + case ImageViewType::e2DArray: + return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_ARRAY; + case ImageViewType::CubeArray: return GL_TEXTURE_CUBE_MAP_ARRAY; + case ImageViewType::Rect: + return GL_TEXTURE_RECTANGLE; + case ImageViewType::Buffer: + return GL_TEXTURE_BUFFER; } - UNREACHABLE(); - return {}; + UNREACHABLE_MSG("Invalid image view type={}", type); + return GL_NONE; } -GLint GetSwizzleSource(SwizzleSource source) { +GLenum TextureMode(PixelFormat format, bool is_first) { + switch (format) { + case PixelFormat::D24_UNORM_S8_UINT: + case PixelFormat::D32_FLOAT_S8_UINT: + return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX; + case PixelFormat::S8_UINT_D24_UNORM: + return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT; + default: + UNREACHABLE(); + return GL_DEPTH_COMPONENT; + } +} + +GLint Swizzle(SwizzleSource source) { switch (source) { case SwizzleSource::Zero: return GL_ZERO; @@ -184,529 +248,844 @@ GLint GetSwizzleSource(SwizzleSource source) { case SwizzleSource::OneFloat: return GL_ONE; } - UNREACHABLE(); + UNREACHABLE_MSG("Invalid swizzle source={}", source); return GL_NONE; } -GLenum GetComponent(PixelFormat format, bool is_first) { - switch (format) { - case PixelFormat::D24_UNORM_S8_UINT: - case PixelFormat::D32_FLOAT_S8_UINT: - return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX; - case PixelFormat::S8_UINT_D24_UNORM: - return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT; +GLenum AttachmentType(PixelFormat format) { + switch (const SurfaceType type = VideoCore::Surface::GetFormatType(format); type) { + case SurfaceType::Depth: + return GL_DEPTH_ATTACHMENT; + case SurfaceType::DepthStencil: + return GL_DEPTH_STENCIL_ATTACHMENT; default: - UNREACHABLE(); - return GL_DEPTH_COMPONENT; + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return GL_NONE; } } -void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) { - if (params.IsBuffer()) { - return; +[[nodiscard]] bool IsConverted(const Device& device, PixelFormat format, ImageType type) { + if (!device.HasASTC() && IsPixelFormatASTC(format)) { + return true; } - glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(params.num_levels - 1)); - if (params.num_levels == 1) { - glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f); + switch (format) { + case PixelFormat::BC4_UNORM: + case PixelFormat::BC5_UNORM: + return type == ImageType::e3D; + default: + break; } + return false; } -OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format, - OGLBuffer& texture_buffer) { - OGLTexture texture; - texture.Create(target); +[[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) { + switch (value) { + case SwizzleSource::G: + return SwizzleSource::R; + default: + return value; + } +} - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureStorage1D(texture.handle, params.emulated_levels, internal_format, params.width); - break; - case SurfaceTarget::TextureBuffer: - texture_buffer.Create(); - glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(), - nullptr, GL_DYNAMIC_STORAGE_BIT); - glTextureBuffer(texture.handle, internal_format, texture_buffer.handle); +void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4> swizzle) { + switch (format) { + case PixelFormat::D24_UNORM_S8_UINT: + case PixelFormat::D32_FLOAT_S8_UINT: + case PixelFormat::S8_UINT_D24_UNORM: + UNIMPLEMENTED_IF(swizzle[0] != SwizzleSource::R && swizzle[0] != SwizzleSource::G); + glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE, + TextureMode(format, swizzle[0] == SwizzleSource::R)); + std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); break; - case SurfaceTarget::Texture2D: - case SurfaceTarget::TextureCubemap: - glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width, - params.height); + default: break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureStorage3D(texture.handle, params.emulated_levels, internal_format, params.width, - params.height, params.depth); + } + std::array<GLint, 4> gl_swizzle; + std::ranges::transform(swizzle, gl_swizzle.begin(), Swizzle); + glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); +} + +[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime, + const VideoCommon::ImageInfo& info) { + // Disable accelerated uploads for now as they don't implement swizzled uploads + return false; + switch (info.type) { + case ImageType::e2D: + case ImageType::e3D: + case ImageType::Linear: break; default: - UNREACHABLE(); + return false; + } + const GLenum internal_format = GetFormatTuple(info.format).internal_format; + const auto& format_info = runtime.FormatInfo(info.type, internal_format); + if (format_info.is_compressed) { + return false; + } + if (std::ranges::find(ACCELERATED_FORMATS, internal_format) == ACCELERATED_FORMATS.end()) { + return false; } + if (format_info.compatibility_by_size) { + return true; + } + const GLenum store_format = StoreFormat(BytesPerBlock(info.format)); + const GLenum store_class = runtime.FormatInfo(info.type, store_format).compatibility_class; + return format_info.compatibility_class == store_class; +} - ApplyTextureDefaults(params, texture.handle); +[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset, + VideoCommon::SubresourceLayers subresource, GLenum target) { + switch (target) { + case GL_TEXTURE_2D_ARRAY: + case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: + return CopyOrigin{ + .level = static_cast<GLint>(subresource.base_level), + .x = static_cast<GLint>(offset.x), + .y = static_cast<GLint>(offset.y), + .z = static_cast<GLint>(subresource.base_layer), + }; + case GL_TEXTURE_3D: + return CopyOrigin{ + .level = static_cast<GLint>(subresource.base_level), + .x = static_cast<GLint>(offset.x), + .y = static_cast<GLint>(offset.y), + .z = static_cast<GLint>(offset.z), + }; + default: + UNIMPLEMENTED_MSG("Unimplemented copy target={}", target); + return CopyOrigin{.level = 0, .x = 0, .y = 0, .z = 0}; + } +} - return texture; +[[nodiscard]] CopyRegion MakeCopyRegion(VideoCommon::Extent3D extent, + VideoCommon::SubresourceLayers dst_subresource, + GLenum target) { + switch (target) { + case GL_TEXTURE_2D_ARRAY: + case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: + return CopyRegion{ + .width = static_cast<GLsizei>(extent.width), + .height = static_cast<GLsizei>(extent.height), + .depth = static_cast<GLsizei>(dst_subresource.num_layers), + }; + case GL_TEXTURE_3D: + return CopyRegion{ + .width = static_cast<GLsizei>(extent.width), + .height = static_cast<GLsizei>(extent.height), + .depth = static_cast<GLsizei>(extent.depth), + }; + default: + UNIMPLEMENTED_MSG("Unimplemented copy target={}", target); + return CopyRegion{.width = 0, .height = 0, .depth = 0}; + } } -constexpr u32 EncodeSwizzle(SwizzleSource x_source, SwizzleSource y_source, SwizzleSource z_source, - SwizzleSource w_source) { - return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) | - (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source); +void AttachTexture(GLuint fbo, GLenum attachment, const ImageView* image_view) { + if (False(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) { + const GLuint texture = image_view->DefaultHandle(); + glNamedFramebufferTexture(fbo, attachment, texture, 0); + return; + } + const GLuint texture = image_view->Handle(ImageViewType::e3D); + if (image_view->range.extent.layers > 1) { + // TODO: OpenGL doesn't support rendering to a fixed number of slices + glNamedFramebufferTexture(fbo, attachment, texture, 0); + } else { + const u32 slice = image_view->range.base.layer; + glNamedFramebufferTextureLayer(fbo, attachment, texture, 0, slice); + } } } // Anonymous namespace -CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params, - bool is_astc_supported) - : VideoCommon::SurfaceBase<View>(gpu_addr, params, is_astc_supported) { - if (is_converted) { - internal_format = params.srgb_conversion ? GL_SRGB8_ALPHA8 : GL_RGBA8; - format = GL_RGBA; - type = GL_UNSIGNED_BYTE; - } else { - const auto& tuple{GetFormatTuple(params.pixel_format)}; - internal_format = tuple.internal_format; - format = tuple.format; - type = tuple.type; - is_compressed = params.IsCompressed(); +ImageBufferMap::~ImageBufferMap() { + if (sync) { + sync->Create(); } - target = GetTextureTarget(params.target); - texture = CreateTexture(params, target, internal_format, texture_buffer); - DecorateSurfaceName(); +} - u32 num_layers = 1; - if (params.is_layered || params.target == SurfaceTarget::Texture3D) { - num_layers = params.depth; +TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager, + StateTracker& state_tracker_) + : device{device_}, state_tracker{state_tracker_}, util_shaders(program_manager) { + static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D}; + for (size_t i = 0; i < TARGETS.size(); ++i) { + const GLenum target = TARGETS[i]; + for (const FormatTuple& tuple : FORMAT_TABLE) { + const GLenum format = tuple.internal_format; + GLint compat_class; + GLint compat_type; + GLint is_compressed; + glGetInternalformativ(target, format, GL_IMAGE_COMPATIBILITY_CLASS, 1, &compat_class); + glGetInternalformativ(target, format, GL_IMAGE_FORMAT_COMPATIBILITY_TYPE, 1, + &compat_type); + glGetInternalformativ(target, format, GL_TEXTURE_COMPRESSED, 1, &is_compressed); + const FormatProperties properties{ + .compatibility_class = static_cast<GLenum>(compat_class), + .compatibility_by_size = compat_type == GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE, + .is_compressed = is_compressed == GL_TRUE, + }; + format_properties[i].emplace(format, properties); + } } - - main_view = - CreateViewInner(ViewParams(params.target, 0, num_layers, 0, params.num_levels), true); + has_broken_texture_view_formats = device.HasBrokenTextureViewFormats(); + + null_image_1d_array.Create(GL_TEXTURE_1D_ARRAY); + null_image_cube_array.Create(GL_TEXTURE_CUBE_MAP_ARRAY); + null_image_3d.Create(GL_TEXTURE_3D); + null_image_rect.Create(GL_TEXTURE_RECTANGLE); + glTextureStorage2D(null_image_1d_array.handle, 1, GL_R8, 1, 1); + glTextureStorage3D(null_image_cube_array.handle, 1, GL_R8, 1, 1, 6); + glTextureStorage3D(null_image_3d.handle, 1, GL_R8, 1, 1, 1); + glTextureStorage2D(null_image_rect.handle, 1, GL_R8, 1, 1); + + std::array<GLuint, 4> new_handles; + glGenTextures(static_cast<GLsizei>(new_handles.size()), new_handles.data()); + null_image_view_1d.handle = new_handles[0]; + null_image_view_2d.handle = new_handles[1]; + null_image_view_2d_array.handle = new_handles[2]; + null_image_view_cube.handle = new_handles[3]; + glTextureView(null_image_view_1d.handle, GL_TEXTURE_1D, null_image_1d_array.handle, GL_R8, 0, 1, + 0, 1); + glTextureView(null_image_view_2d.handle, GL_TEXTURE_2D, null_image_cube_array.handle, GL_R8, 0, + 1, 0, 1); + glTextureView(null_image_view_2d_array.handle, GL_TEXTURE_2D_ARRAY, + null_image_cube_array.handle, GL_R8, 0, 1, 0, 1); + glTextureView(null_image_view_cube.handle, GL_TEXTURE_CUBE_MAP, null_image_cube_array.handle, + GL_R8, 0, 1, 0, 6); + const std::array texture_handles{ + null_image_1d_array.handle, null_image_cube_array.handle, null_image_3d.handle, + null_image_rect.handle, null_image_view_1d.handle, null_image_view_2d.handle, + null_image_view_2d_array.handle, null_image_view_cube.handle, + }; + for (const GLuint handle : texture_handles) { + static constexpr std::array NULL_SWIZZLE{GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO}; + glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, NULL_SWIZZLE.data()); + } + const auto set_view = [this](ImageViewType type, GLuint handle) { + if (device.HasDebuggingToolAttached()) { + const std::string name = fmt::format("NullImage {}", type); + glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data()); + } + null_image_views[static_cast<size_t>(type)] = handle; + }; + set_view(ImageViewType::e1D, null_image_view_1d.handle); + set_view(ImageViewType::e2D, null_image_view_2d.handle); + set_view(ImageViewType::Cube, null_image_view_cube.handle); + set_view(ImageViewType::e3D, null_image_3d.handle); + set_view(ImageViewType::e1DArray, null_image_1d_array.handle); + set_view(ImageViewType::e2DArray, null_image_view_2d_array.handle); + set_view(ImageViewType::CubeArray, null_image_cube_array.handle); + set_view(ImageViewType::Rect, null_image_rect.handle); } -CachedSurface::~CachedSurface() = default; - -void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { - MICROPROFILE_SCOPE(OpenGL_Texture_Download); +TextureCacheRuntime::~TextureCacheRuntime() = default; - if (params.IsBuffer()) { - glGetNamedBufferSubData(texture_buffer.handle, 0, - static_cast<GLsizeiptr>(params.GetHostSizeInBytes(false)), - staging_buffer.data()); - return; - } +void TextureCacheRuntime::Finish() { + glFinish(); +} - SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); }); +ImageBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) { + return upload_buffers.RequestMap(size, true); +} - for (u32 level = 0; level < params.emulated_levels; ++level) { - glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level, is_converted))); - glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level))); - const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level, is_converted); +ImageBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) { + return download_buffers.RequestMap(size, false); +} - u8* const mip_data = staging_buffer.data() + mip_offset; - const GLsizei size = static_cast<GLsizei>(params.GetHostMipmapSize(level)); - if (is_compressed) { - glGetCompressedTextureImage(texture.handle, level, size, mip_data); - } else { - glGetTextureImage(texture.handle, level, format, type, size, mip_data); - } +void TextureCacheRuntime::CopyImage(Image& dst_image, Image& src_image, + std::span<const ImageCopy> copies) { + const GLuint dst_name = dst_image.Handle(); + const GLuint src_name = src_image.Handle(); + const GLenum dst_target = ImageTarget(dst_image.info); + const GLenum src_target = ImageTarget(src_image.info); + for (const ImageCopy& copy : copies) { + const auto src_origin = MakeCopyOrigin(copy.src_offset, copy.src_subresource, src_target); + const auto dst_origin = MakeCopyOrigin(copy.dst_offset, copy.dst_subresource, dst_target); + const auto region = MakeCopyRegion(copy.extent, copy.dst_subresource, dst_target); + glCopyImageSubData(src_name, src_target, src_origin.level, src_origin.x, src_origin.y, + src_origin.z, dst_name, dst_target, dst_origin.level, dst_origin.x, + dst_origin.y, dst_origin.z, region.width, region.height, region.depth); } } -void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) { - MICROPROFILE_SCOPE(OpenGL_Texture_Upload); - SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); }); - for (u32 level = 0; level < params.emulated_levels; ++level) { - UploadTextureMipmap(level, staging_buffer); +bool TextureCacheRuntime::CanImageBeCopied(const Image& dst, const Image& src) { + if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) { + return false; } + return true; } -void CachedSurface::UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer) { - glPixelStorei(GL_UNPACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level, is_converted))); - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level))); - - const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level, is_converted); - const u8* buffer{staging_buffer.data() + mip_offset}; - if (is_compressed) { - const auto image_size{static_cast<GLsizei>(params.GetHostMipmapSize(level))}; - switch (params.target) { - case SurfaceTarget::Texture2D: - glCompressedTextureSubImage2D(texture.handle, level, 0, 0, - static_cast<GLsizei>(params.GetMipWidth(level)), - static_cast<GLsizei>(params.GetMipHeight(level)), - internal_format, image_size, buffer); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glCompressedTextureSubImage3D(texture.handle, level, 0, 0, 0, - static_cast<GLsizei>(params.GetMipWidth(level)), - static_cast<GLsizei>(params.GetMipHeight(level)), - static_cast<GLsizei>(params.GetMipDepth(level)), - internal_format, image_size, buffer); - break; - case SurfaceTarget::TextureCubemap: { - const std::size_t layer_size{params.GetHostLayerSize(level)}; - for (std::size_t face = 0; face < params.depth; ++face) { - glCompressedTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face), - static_cast<GLsizei>(params.GetMipWidth(level)), - static_cast<GLsizei>(params.GetMipHeight(level)), 1, - internal_format, static_cast<GLsizei>(layer_size), - buffer); - buffer += layer_size; - } - break; - } - default: - UNREACHABLE(); - } +void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src, + std::span<const ImageCopy> copies) { + if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) { + ASSERT(src.info.type == ImageType::e3D); + util_shaders.CopyBC4(dst, src, copies); } else { - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(texture.handle, level, 0, params.GetMipWidth(level), format, type, - buffer); - break; - case SurfaceTarget::TextureBuffer: - ASSERT(level == 0); - glNamedBufferSubData(texture_buffer.handle, 0, - params.GetMipWidth(level) * params.GetBytesPerPixel(), buffer); - break; - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2D: - glTextureSubImage2D(texture.handle, level, 0, 0, params.GetMipWidth(level), - params.GetMipHeight(level), format, type, buffer); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D( - texture.handle, level, 0, 0, 0, static_cast<GLsizei>(params.GetMipWidth(level)), - static_cast<GLsizei>(params.GetMipHeight(level)), - static_cast<GLsizei>(params.GetMipDepth(level)), format, type, buffer); - break; - case SurfaceTarget::TextureCubemap: - for (std::size_t face = 0; face < params.depth; ++face) { - glTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face), - params.GetMipWidth(level), params.GetMipHeight(level), 1, - format, type, buffer); - buffer += params.GetHostLayerSize(level); - } - break; - default: - UNREACHABLE(); - } + UNREACHABLE(); } } -void CachedSurface::DecorateSurfaceName() { - LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName()); -} +void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) { + state_tracker.NotifyScissor0(); + state_tracker.NotifyRasterizeEnable(); + state_tracker.NotifyFramebufferSRGB(); -void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) { - LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix); + ASSERT(dst->BufferBits() == src->BufferBits()); + + glEnable(GL_FRAMEBUFFER_SRGB); + glDisable(GL_RASTERIZER_DISCARD); + glDisablei(GL_SCISSOR_TEST, 0); + + const GLbitfield buffer_bits = dst->BufferBits(); + const bool has_depth = (buffer_bits & ~GL_COLOR_BUFFER_BIT) != 0; + const bool is_linear = !has_depth && filter == Tegra::Engines::Fermi2D::Filter::Bilinear; + glBlitNamedFramebuffer(src->Handle(), dst->Handle(), src_region[0].x, src_region[0].y, + src_region[1].x, src_region[1].y, dst_region[0].x, dst_region[0].y, + dst_region[1].x, dst_region[1].y, buffer_bits, + is_linear ? GL_LINEAR : GL_NEAREST); } -View CachedSurface::CreateView(const ViewParams& view_key) { - return CreateViewInner(view_key, false); +void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferMap& map, + std::span<const SwizzleParameters> swizzles) { + switch (image.info.type) { + case ImageType::e2D: + return util_shaders.BlockLinearUpload2D(image, map, swizzles); + case ImageType::e3D: + return util_shaders.BlockLinearUpload3D(image, map, swizzles); + case ImageType::Linear: + return util_shaders.PitchUpload(image, map, swizzles); + default: + UNREACHABLE(); + break; + } } -View CachedSurface::CreateViewInner(const ViewParams& view_key, const bool is_proxy) { - auto view = std::make_shared<CachedSurfaceView>(*this, view_key, is_proxy); - views[view_key] = view; - if (!is_proxy) - view->DecorateViewName(gpu_addr, params.TargetName() + "V:" + std::to_string(view_count++)); - return view; +void TextureCacheRuntime::InsertUploadMemoryBarrier() { + glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); } -CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& params, - bool is_proxy) - : VideoCommon::ViewBase(params), surface{surface}, format{surface.internal_format}, - target{GetTextureTarget(params.target)}, is_proxy{is_proxy} { - if (!is_proxy) { - main_view = CreateTextureView(); +FormatProperties TextureCacheRuntime::FormatInfo(ImageType type, GLenum internal_format) const { + switch (type) { + case ImageType::e1D: + return format_properties[0].at(internal_format); + case ImageType::e2D: + case ImageType::Linear: + return format_properties[1].at(internal_format); + case ImageType::e3D: + return format_properties[2].at(internal_format); + default: + UNREACHABLE(); + return FormatProperties{}; } } -CachedSurfaceView::~CachedSurfaceView() = default; +TextureCacheRuntime::StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_) + : storage_flags{storage_flags_}, map_flags{map_flags_} {} -void CachedSurfaceView::Attach(GLenum attachment, GLenum fb_target) const { - ASSERT(params.num_levels == 1); +TextureCacheRuntime::StagingBuffers::~StagingBuffers() = default; - if (params.target == SurfaceTarget::Texture3D) { - if (params.num_layers > 1) { - ASSERT(params.base_layer == 0); - glFramebufferTexture(fb_target, attachment, surface.texture.handle, params.base_level); - } else { - glFramebufferTexture3D(fb_target, attachment, target, surface.texture.handle, - params.base_level, params.base_layer); - } - return; +ImageBufferMap TextureCacheRuntime::StagingBuffers::RequestMap(size_t requested_size, + bool insert_fence) { + const size_t index = RequestBuffer(requested_size); + OGLSync* const sync = insert_fence ? &syncs[index] : nullptr; + return ImageBufferMap{ + .mapped_span = std::span(maps[index], requested_size), + .sync = sync, + .buffer = buffers[index].handle, + }; +} + +size_t TextureCacheRuntime::StagingBuffers::RequestBuffer(size_t requested_size) { + if (const std::optional<size_t> index = FindBuffer(requested_size); index) { + return *index; } - if (params.num_layers > 1) { - UNIMPLEMENTED_IF(params.base_layer != 0); - glFramebufferTexture(fb_target, attachment, GetTexture(), 0); - return; + OGLBuffer& buffer = buffers.emplace_back(); + buffer.Create(); + glNamedBufferStorage(buffer.handle, requested_size, nullptr, + storage_flags | GL_MAP_PERSISTENT_BIT); + maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, requested_size, + map_flags | GL_MAP_PERSISTENT_BIT))); + + syncs.emplace_back(); + sizes.push_back(requested_size); + + ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() && + maps.size() == sizes.size()); + + return buffers.size() - 1; +} + +std::optional<size_t> TextureCacheRuntime::StagingBuffers::FindBuffer(size_t requested_size) { + size_t smallest_buffer = std::numeric_limits<size_t>::max(); + std::optional<size_t> found; + const size_t num_buffers = sizes.size(); + for (size_t index = 0; index < num_buffers; ++index) { + const size_t buffer_size = sizes[index]; + if (buffer_size < requested_size || buffer_size >= smallest_buffer) { + continue; + } + if (syncs[index].handle != 0) { + GLint status; + glGetSynciv(syncs[index].handle, GL_SYNC_STATUS, 1, nullptr, &status); + if (status != GL_SIGNALED) { + continue; + } + syncs[index].Release(); + } + smallest_buffer = buffer_size; + found = index; } + return found; +} - const GLenum view_target = surface.GetTarget(); - const GLuint texture = surface.GetTexture(); - switch (surface.GetSurfaceParams().target) { - case SurfaceTarget::Texture1D: - glFramebufferTexture1D(fb_target, attachment, view_target, texture, params.base_level); +Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_, + VAddr cpu_addr_) + : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_) { + if (CanBeAccelerated(runtime, info)) { + flags |= ImageFlagBits::AcceleratedUpload; + } + if (IsConverted(runtime.device, info.format, info.type)) { + flags |= ImageFlagBits::Converted; + gl_internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8; + gl_format = GL_RGBA; + gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + } else { + const auto& tuple = GetFormatTuple(info.format); + gl_internal_format = tuple.internal_format; + gl_format = tuple.format; + gl_type = tuple.type; + } + const GLenum target = ImageTarget(info); + const GLsizei width = info.size.width; + const GLsizei height = info.size.height; + const GLsizei depth = info.size.depth; + const int max_host_mip_levels = std::bit_width(info.size.width); + const GLsizei num_levels = std::min(info.resources.levels, max_host_mip_levels); + const GLsizei num_layers = info.resources.layers; + const GLsizei num_samples = info.num_samples; + + GLuint handle = 0; + if (target != GL_TEXTURE_BUFFER) { + texture.Create(target); + handle = texture.handle; + } + switch (target) { + case GL_TEXTURE_1D_ARRAY: + glTextureStorage2D(handle, num_levels, gl_internal_format, width, num_layers); + break; + case GL_TEXTURE_2D_ARRAY: + glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, num_layers); break; - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(fb_target, attachment, view_target, texture, params.base_level); + case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: { + // TODO: Where should 'fixedsamplelocations' come from? + const auto [samples_x, samples_y] = SamplesLog2(info.num_samples); + glTextureStorage3DMultisample(handle, num_samples, gl_internal_format, width >> samples_x, + height >> samples_y, num_layers, GL_FALSE); + break; + } + case GL_TEXTURE_RECTANGLE: + glTextureStorage2D(handle, num_levels, gl_internal_format, width, height); break; - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: - glFramebufferTextureLayer(fb_target, attachment, texture, params.base_level, - params.base_layer); + case GL_TEXTURE_3D: + glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, depth); + break; + case GL_TEXTURE_BUFFER: + buffer.Create(); + glNamedBufferStorage(buffer.handle, guest_size_bytes, nullptr, 0); break; default: - UNIMPLEMENTED(); + UNREACHABLE_MSG("Invalid target=0x{:x}", target); + break; + } + if (runtime.device.HasDebuggingToolAttached()) { + const std::string name = VideoCommon::Name(*this); + glObjectLabel(target == GL_TEXTURE_BUFFER ? GL_BUFFER : GL_TEXTURE, handle, + static_cast<GLsizei>(name.size()), name.data()); } } -GLuint CachedSurfaceView::GetTexture(SwizzleSource x_source, SwizzleSource y_source, - SwizzleSource z_source, SwizzleSource w_source) { - if (GetSurfaceParams().IsBuffer()) { - return GetTexture(); - } - const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source); - if (current_swizzle == new_swizzle) { - return current_view; - } - current_swizzle = new_swizzle; +void Image::UploadMemory(const ImageBufferMap& map, + std::span<const VideoCommon::BufferImageCopy> copies) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer); + glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, map.offset, unswizzled_size_bytes); - const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle); - OGLTextureView& view = entry->second; - if (!is_cache_miss) { - current_view = view.handle; - return view.handle; - } - view = CreateTextureView(); - current_view = view.handle; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - std::array swizzle{x_source, y_source, z_source, w_source}; + u32 current_row_length = std::numeric_limits<u32>::max(); + u32 current_image_height = std::numeric_limits<u32>::max(); - switch (const PixelFormat format = GetSurfaceParams().pixel_format) { - case PixelFormat::D24_UNORM_S8_UINT: - case PixelFormat::D32_FLOAT_S8_UINT: - case PixelFormat::S8_UINT_D24_UNORM: - UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G); - glTextureParameteri(view.handle, GL_DEPTH_STENCIL_TEXTURE_MODE, - GetComponent(format, x_source == SwizzleSource::R)); - - // Make sure we sample the first component - std::transform(swizzle.begin(), swizzle.end(), swizzle.begin(), [](SwizzleSource value) { - return value == SwizzleSource::G ? SwizzleSource::R : value; - }); - [[fallthrough]]; - default: { - const std::array gl_swizzle = {GetSwizzleSource(swizzle[0]), GetSwizzleSource(swizzle[1]), - GetSwizzleSource(swizzle[2]), GetSwizzleSource(swizzle[3])}; - glTextureParameteriv(view.handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); - break; - } + for (const VideoCommon::BufferImageCopy& copy : copies) { + if (current_row_length != copy.buffer_row_length) { + current_row_length = copy.buffer_row_length; + glPixelStorei(GL_UNPACK_ROW_LENGTH, current_row_length); + } + if (current_image_height != copy.buffer_image_height) { + current_image_height = copy.buffer_image_height; + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, current_image_height); + } + CopyBufferToImage(copy, map.offset); } - return view.handle; } -OGLTextureView CachedSurfaceView::CreateTextureView() const { - OGLTextureView texture_view; - texture_view.Create(); - - if (target == GL_TEXTURE_3D) { - glTextureView(texture_view.handle, target, surface.texture.handle, format, - params.base_level, params.num_levels, 0, 1); - } else { - glTextureView(texture_view.handle, target, surface.texture.handle, format, - params.base_level, params.num_levels, params.base_layer, params.num_layers); +void Image::UploadMemory(const ImageBufferMap& map, + std::span<const VideoCommon::BufferCopy> copies) { + for (const VideoCommon::BufferCopy& copy : copies) { + glCopyNamedBufferSubData(map.buffer, buffer.handle, copy.src_offset + map.offset, + copy.dst_offset, copy.size); } - ApplyTextureDefaults(surface.GetSurfaceParams(), texture_view.handle); - - return texture_view; } -TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system, - VideoCore::RasterizerInterface& rasterizer, - const Device& device, StateTracker& state_tracker) - : TextureCacheBase{system, rasterizer, device.HasASTC()}, state_tracker{state_tracker} { - src_framebuffer.Create(); - dst_framebuffer.Create(); -} +void Image::DownloadMemory(ImageBufferMap& map, + std::span<const VideoCommon::BufferImageCopy> copies) { + glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); // TODO: Move this to its own API -TextureCacheOpenGL::~TextureCacheOpenGL() = default; + glBindBuffer(GL_PIXEL_PACK_BUFFER, map.buffer); + glPixelStorei(GL_PACK_ALIGNMENT, 1); -Surface TextureCacheOpenGL::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) { - return std::make_shared<CachedSurface>(gpu_addr, params, is_astc_supported); -} + u32 current_row_length = std::numeric_limits<u32>::max(); + u32 current_image_height = std::numeric_limits<u32>::max(); -void TextureCacheOpenGL::ImageCopy(Surface& src_surface, Surface& dst_surface, - const VideoCommon::CopyParams& copy_params) { - const auto& src_params = src_surface->GetSurfaceParams(); - const auto& dst_params = dst_surface->GetSurfaceParams(); - if (src_params.type != dst_params.type) { - // A fallback is needed - return; + for (const VideoCommon::BufferImageCopy& copy : copies) { + if (current_row_length != copy.buffer_row_length) { + current_row_length = copy.buffer_row_length; + glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length); + } + if (current_image_height != copy.buffer_image_height) { + current_image_height = copy.buffer_image_height; + glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height); + } + CopyImageToBuffer(copy, map.offset); } - const auto src_handle = src_surface->GetTexture(); - const auto src_target = src_surface->GetTarget(); - const auto dst_handle = dst_surface->GetTexture(); - const auto dst_target = dst_surface->GetTarget(); - glCopyImageSubData(src_handle, src_target, copy_params.source_level, copy_params.source_x, - copy_params.source_y, copy_params.source_z, dst_handle, dst_target, - copy_params.dest_level, copy_params.dest_x, copy_params.dest_y, - copy_params.dest_z, copy_params.width, copy_params.height, - copy_params.depth); } -void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view, - const Tegra::Engines::Fermi2D::Config& copy_config) { - const auto& src_params{src_view->GetSurfaceParams()}; - const auto& dst_params{dst_view->GetSurfaceParams()}; - UNIMPLEMENTED_IF(src_params.depth != 1); - UNIMPLEMENTED_IF(dst_params.depth != 1); - - state_tracker.NotifyScissor0(); - state_tracker.NotifyFramebuffer(); - state_tracker.NotifyRasterizeEnable(); - state_tracker.NotifyFramebufferSRGB(); - - if (dst_params.srgb_conversion) { - glEnable(GL_FRAMEBUFFER_SRGB); - } else { - glDisable(GL_FRAMEBUFFER_SRGB); +GLuint Image::StorageHandle() noexcept { + switch (info.format) { + case PixelFormat::A8B8G8R8_SRGB: + case PixelFormat::B8G8R8A8_SRGB: + case PixelFormat::BC1_RGBA_SRGB: + case PixelFormat::BC2_SRGB: + case PixelFormat::BC3_SRGB: + case PixelFormat::BC7_SRGB: + case PixelFormat::ASTC_2D_4X4_SRGB: + case PixelFormat::ASTC_2D_8X8_SRGB: + case PixelFormat::ASTC_2D_8X5_SRGB: + case PixelFormat::ASTC_2D_5X4_SRGB: + case PixelFormat::ASTC_2D_5X5_SRGB: + case PixelFormat::ASTC_2D_10X8_SRGB: + case PixelFormat::ASTC_2D_6X6_SRGB: + case PixelFormat::ASTC_2D_10X10_SRGB: + case PixelFormat::ASTC_2D_12X12_SRGB: + case PixelFormat::ASTC_2D_8X6_SRGB: + case PixelFormat::ASTC_2D_6X5_SRGB: + if (store_view.handle != 0) { + return store_view.handle; + } + store_view.Create(); + glTextureView(store_view.handle, ImageTarget(info), texture.handle, GL_RGBA8, 0, + info.resources.levels, 0, info.resources.layers); + return store_view.handle; + default: + return texture.handle; } - glDisable(GL_RASTERIZER_DISCARD); - glDisablei(GL_SCISSOR_TEST, 0); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, src_framebuffer.handle); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_framebuffer.handle); - - GLenum buffers = 0; - if (src_params.type == SurfaceType::ColorTexture) { - src_view->Attach(GL_COLOR_ATTACHMENT0, GL_READ_FRAMEBUFFER); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); +} - dst_view->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); +void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) { + // Compressed formats don't have a pixel format or type + const bool is_compressed = gl_format == GL_NONE; + const void* const offset = reinterpret_cast<const void*>(copy.buffer_offset + buffer_offset); - buffers = GL_COLOR_BUFFER_BIT; - } else if (src_params.type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - src_view->Attach(GL_DEPTH_ATTACHMENT, GL_READ_FRAMEBUFFER); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + switch (info.type) { + case ImageType::e1D: + if (is_compressed) { + glCompressedTextureSubImage2D(texture.handle, copy.image_subresource.base_level, + copy.image_offset.x, copy.image_subresource.base_layer, + copy.image_extent.width, + copy.image_subresource.num_layers, gl_internal_format, + static_cast<GLsizei>(copy.buffer_size), offset); + } else { + glTextureSubImage2D(texture.handle, copy.image_subresource.base_level, + copy.image_offset.x, copy.image_subresource.base_layer, + copy.image_extent.width, copy.image_subresource.num_layers, + gl_format, gl_type, offset); + } + break; + case ImageType::e2D: + case ImageType::Linear: + if (is_compressed) { + glCompressedTextureSubImage3D( + texture.handle, copy.image_subresource.base_level, copy.image_offset.x, + copy.image_offset.y, copy.image_subresource.base_layer, copy.image_extent.width, + copy.image_extent.height, copy.image_subresource.num_layers, gl_internal_format, + static_cast<GLsizei>(copy.buffer_size), offset); + } else { + glTextureSubImage3D(texture.handle, copy.image_subresource.base_level, + copy.image_offset.x, copy.image_offset.y, + copy.image_subresource.base_layer, copy.image_extent.width, + copy.image_extent.height, copy.image_subresource.num_layers, + gl_format, gl_type, offset); + } + break; + case ImageType::e3D: + if (is_compressed) { + glCompressedTextureSubImage3D( + texture.handle, copy.image_subresource.base_level, copy.image_offset.x, + copy.image_offset.y, copy.image_offset.z, copy.image_extent.width, + copy.image_extent.height, copy.image_extent.depth, gl_internal_format, + static_cast<GLsizei>(copy.buffer_size), offset); + } else { + glTextureSubImage3D(texture.handle, copy.image_subresource.base_level, + copy.image_offset.x, copy.image_offset.y, copy.image_offset.z, + copy.image_extent.width, copy.image_extent.height, + copy.image_extent.depth, gl_format, gl_type, offset); + } + break; + default: + UNREACHABLE(); + } +} - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - dst_view->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); +void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) { + const GLint x_offset = copy.image_offset.x; + const GLsizei width = copy.image_extent.width; - buffers = GL_DEPTH_BUFFER_BIT; - } else if (src_params.type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - src_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_READ_FRAMEBUFFER); + const GLint level = copy.image_subresource.base_level; + const GLsizei buffer_size = static_cast<GLsizei>(copy.buffer_size); + void* const offset = reinterpret_cast<void*>(copy.buffer_offset + buffer_offset); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - dst_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + GLint y_offset = 0; + GLint z_offset = 0; + GLsizei height = 1; + GLsizei depth = 1; - buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + switch (info.type) { + case ImageType::e1D: + y_offset = copy.image_subresource.base_layer; + height = copy.image_subresource.num_layers; + break; + case ImageType::e2D: + case ImageType::Linear: + y_offset = copy.image_offset.y; + z_offset = copy.image_subresource.base_layer; + height = copy.image_extent.height; + depth = copy.image_subresource.num_layers; + break; + case ImageType::e3D: + y_offset = copy.image_offset.y; + z_offset = copy.image_offset.z; + height = copy.image_extent.height; + depth = copy.image_extent.depth; + break; + default: + UNREACHABLE(); + } + // Compressed formats don't have a pixel format or type + const bool is_compressed = gl_format == GL_NONE; + if (is_compressed) { + glGetCompressedTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width, + height, depth, buffer_size, offset); + } else { + glGetTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width, height, + depth, gl_format, gl_type, buffer_size, offset); } - - const Common::Rectangle<u32>& src_rect = copy_config.src_rect; - const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect; - const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; - - glBlitFramebuffer(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.top), - static_cast<GLint>(src_rect.right), static_cast<GLint>(src_rect.bottom), - static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.top), - static_cast<GLint>(dst_rect.right), static_cast<GLint>(dst_rect.bottom), - buffers, - is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST); } -void TextureCacheOpenGL::BufferCopy(Surface& src_surface, Surface& dst_surface) { - MICROPROFILE_SCOPE(OpenGL_Texture_Buffer_Copy); - const auto& src_params = src_surface->GetSurfaceParams(); - const auto& dst_params = dst_surface->GetSurfaceParams(); - UNIMPLEMENTED_IF(src_params.num_levels > 1 || dst_params.num_levels > 1); +ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, + ImageId image_id_, Image& image) + : VideoCommon::ImageViewBase{info, image.info, image_id_}, views{runtime.null_image_views} { + const Device& device = runtime.device; + if (True(image.flags & ImageFlagBits::Converted)) { + internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8; + } else { + internal_format = GetFormatTuple(format).internal_format; + } + VideoCommon::SubresourceRange flatten_range = info.range; + std::array<GLuint, 2> handles; + stored_views.reserve(2); - const auto source_format = GetFormatTuple(src_params.pixel_format); - const auto dest_format = GetFormatTuple(dst_params.pixel_format); + switch (info.type) { + case ImageViewType::e1DArray: + flatten_range.extent.layers = 1; + [[fallthrough]]; + case ImageViewType::e1D: + glGenTextures(2, handles.data()); + SetupView(device, image, ImageViewType::e1D, handles[0], info, flatten_range); + SetupView(device, image, ImageViewType::e1DArray, handles[1], info, info.range); + break; + case ImageViewType::e2DArray: + flatten_range.extent.layers = 1; + [[fallthrough]]; + case ImageViewType::e2D: + if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) { + // 2D and 2D array views on a 3D textures are used exclusively for render targets + ASSERT(info.range.extent.levels == 1); + const VideoCommon::SubresourceRange slice_range{ + .base = {.level = info.range.base.level, .layer = 0}, + .extent = {.levels = 1, .layers = 1}, + }; + glGenTextures(1, handles.data()); + SetupView(device, image, ImageViewType::e3D, handles[0], info, slice_range); + break; + } + glGenTextures(2, handles.data()); + SetupView(device, image, ImageViewType::e2D, handles[0], info, flatten_range); + SetupView(device, image, ImageViewType::e2DArray, handles[1], info, info.range); + break; + case ImageViewType::e3D: + glGenTextures(1, handles.data()); + SetupView(device, image, ImageViewType::e3D, handles[0], info, info.range); + break; + case ImageViewType::CubeArray: + flatten_range.extent.layers = 6; + [[fallthrough]]; + case ImageViewType::Cube: + glGenTextures(2, handles.data()); + SetupView(device, image, ImageViewType::Cube, handles[0], info, flatten_range); + SetupView(device, image, ImageViewType::CubeArray, handles[1], info, info.range); + break; + case ImageViewType::Rect: + glGenTextures(1, handles.data()); + SetupView(device, image, ImageViewType::Rect, handles[0], info, info.range); + break; + case ImageViewType::Buffer: + glCreateTextures(GL_TEXTURE_BUFFER, 1, handles.data()); + SetupView(device, image, ImageViewType::Buffer, handles[0], info, info.range); + break; + } + default_handle = Handle(info.type); +} - const std::size_t source_size = src_surface->GetHostSizeInBytes(); - const std::size_t dest_size = dst_surface->GetHostSizeInBytes(); +ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageParams& params) + : VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {} - const std::size_t buffer_size = std::max(source_size, dest_size); +void ImageView::SetupView(const Device& device, Image& image, ImageViewType view_type, + GLuint handle, const VideoCommon::ImageViewInfo& info, + VideoCommon::SubresourceRange view_range) { + if (info.type == ImageViewType::Buffer) { + // TODO: Take offset from buffer cache + glTextureBufferRange(handle, internal_format, image.buffer.handle, 0, + image.guest_size_bytes); + } else { + const GLuint parent = image.texture.handle; + const GLenum target = ImageTarget(view_type, image.info.num_samples); + glTextureView(handle, target, parent, internal_format, view_range.base.level, + view_range.extent.levels, view_range.base.layer, view_range.extent.layers); + if (!info.IsRenderTarget()) { + ApplySwizzle(handle, format, info.Swizzle()); + } + } + if (device.HasDebuggingToolAttached()) { + const std::string name = VideoCommon::Name(*this, view_type); + glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data()); + } + stored_views.emplace_back().handle = handle; + views[static_cast<size_t>(view_type)] = handle; +} - GLuint copy_pbo_handle = FetchPBO(buffer_size); +Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) { + const GLenum compare_mode = config.depth_compare_enabled ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE; + const GLenum compare_func = MaxwellToGL::DepthCompareFunc(config.depth_compare_func); + const GLenum mag = MaxwellToGL::TextureFilterMode(config.mag_filter, TextureMipmapFilter::None); + const GLenum min = MaxwellToGL::TextureFilterMode(config.min_filter, config.mipmap_filter); + const GLenum reduction_filter = MaxwellToGL::ReductionFilter(config.reduction_filter); + const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE; + + UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); + UNIMPLEMENTED_IF(config.float_coord_normalization != 0); + + sampler.Create(); + const GLuint handle = sampler.handle; + glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); + glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); + glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); + glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); + glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); + glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); + glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); + glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); + glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); + glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); + glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); + + if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { + glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, config.MaxAnisotropy()); + } else { + LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); + } + if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { + glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); + } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { + LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); + } + if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { + glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); + } else if (seamless == GL_FALSE) { + // We default to false because it's more common + LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); + } +} - glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); +Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, + ImageView* depth_buffer, const VideoCommon::RenderTargets& key) { + // Bind to READ_FRAMEBUFFER to stop Nvidia's driver from creating an EXT_framebuffer instead of + // a core framebuffer. EXT framebuffer attachments have to match in size and can be shared + // across contexts. yuzu doesn't share framebuffers across contexts and we need attachments with + // mismatching size, this is why core framebuffers are preferred. + GLuint handle; + glGenFramebuffers(1, &handle); + glBindFramebuffer(GL_READ_FRAMEBUFFER, handle); + + GLsizei num_buffers = 0; + std::array<GLenum, NUM_RT> gl_draw_buffers; + gl_draw_buffers.fill(GL_NONE); + + for (size_t index = 0; index < color_buffers.size(); ++index) { + const ImageView* const image_view = color_buffers[index]; + if (!image_view) { + continue; + } + buffer_bits |= GL_COLOR_BUFFER_BIT; + gl_draw_buffers[index] = GL_COLOR_ATTACHMENT0 + key.draw_buffers[index]; + num_buffers = static_cast<GLsizei>(index + 1); - if (src_surface->IsCompressed()) { - glGetCompressedTextureImage(src_surface->GetTexture(), 0, static_cast<GLsizei>(source_size), - nullptr); - } else { - glGetTextureImage(src_surface->GetTexture(), 0, source_format.format, source_format.type, - static_cast<GLsizei>(source_size), nullptr); + const GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index); + AttachTexture(handle, attachment, image_view); } - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle); + if (const ImageView* const image_view = depth_buffer; image_view) { + if (GetFormatType(image_view->format) == SurfaceType::DepthStencil) { + buffer_bits |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } else { + buffer_bits |= GL_DEPTH_BUFFER_BIT; + } + const GLenum attachment = AttachmentType(image_view->format); + AttachTexture(handle, attachment, image_view); + } - const GLsizei width = static_cast<GLsizei>(dst_params.width); - const GLsizei height = static_cast<GLsizei>(dst_params.height); - const GLsizei depth = static_cast<GLsizei>(dst_params.depth); - if (dst_surface->IsCompressed()) { - LOG_CRITICAL(HW_GPU, "Compressed buffer copy is unimplemented!"); - UNREACHABLE(); + if (num_buffers > 1) { + glNamedFramebufferDrawBuffers(handle, num_buffers, gl_draw_buffers.data()); + } else if (num_buffers > 0) { + glNamedFramebufferDrawBuffer(handle, gl_draw_buffers[0]); } else { - switch (dst_params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(dst_surface->GetTexture(), 0, 0, width, dest_format.format, - dest_format.type, nullptr); - break; - case SurfaceTarget::Texture2D: - glTextureSubImage2D(dst_surface->GetTexture(), 0, 0, 0, width, height, - dest_format.format, dest_format.type, nullptr); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, - dest_format.format, dest_format.type, nullptr); - break; - case SurfaceTarget::TextureCubemap: - glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, - dest_format.format, dest_format.type, nullptr); - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(dst_params.target)); - UNREACHABLE(); - } + glNamedFramebufferDrawBuffer(handle, GL_NONE); } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glTextureBarrier(); -} + glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_WIDTH, key.size.width); + glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_HEIGHT, key.size.height); + // TODO + // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_LAYERS, ...); + // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_SAMPLES, ...); + // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS, ...); -GLuint TextureCacheOpenGL::FetchPBO(std::size_t buffer_size) { - ASSERT_OR_EXECUTE(buffer_size > 0, { return 0; }); - const u32 l2 = Common::Log2Ceil64(static_cast<u64>(buffer_size)); - OGLBuffer& cp = copy_pbo_cache[l2]; - if (cp.handle == 0) { - const std::size_t ceil_size = 1ULL << l2; - cp.Create(); - cp.MakeStreamCopy(ceil_size); + if (runtime.device.HasDebuggingToolAttached()) { + const std::string name = VideoCommon::Name(key); + glObjectLabel(GL_FRAMEBUFFER, handle, static_cast<GLsizei>(name.size()), name.data()); } - return cp.handle; + framebuffer.handle = handle; } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index de8f18489..a6172f009 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -4,154 +4,241 @@ #pragma once -#include <array> -#include <functional> #include <memory> -#include <unordered_map> -#include <utility> -#include <vector> +#include <span> #include <glad/glad.h> -#include "common/common_types.h" -#include "video_core/engines/shader_bytecode.h" -#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/util_shaders.h" #include "video_core/texture_cache/texture_cache.h" namespace OpenGL { -using VideoCommon::SurfaceParams; -using VideoCommon::ViewParams; - -class CachedSurfaceView; -class CachedSurface; -class TextureCacheOpenGL; +class Device; +class ProgramManager; class StateTracker; -using Surface = std::shared_ptr<CachedSurface>; -using View = std::shared_ptr<CachedSurfaceView>; -using TextureCacheBase = VideoCommon::TextureCache<Surface, View>; +class Framebuffer; +class Image; +class ImageView; +class Sampler; + +using VideoCommon::ImageId; +using VideoCommon::ImageViewId; +using VideoCommon::ImageViewType; +using VideoCommon::NUM_RT; +using VideoCommon::Offset2D; +using VideoCommon::RenderTargets; + +struct ImageBufferMap { + ~ImageBufferMap(); + + std::span<u8> mapped_span; + size_t offset = 0; + OGLSync* sync; + GLuint buffer; +}; + +struct FormatProperties { + GLenum compatibility_class; + bool compatibility_by_size; + bool is_compressed; +}; -class CachedSurface final : public VideoCommon::SurfaceBase<View> { - friend CachedSurfaceView; +class TextureCacheRuntime { + friend Framebuffer; + friend Image; + friend ImageView; + friend Sampler; public: - explicit CachedSurface(GPUVAddr gpu_addr, const SurfaceParams& params, bool is_astc_supported); - ~CachedSurface(); + explicit TextureCacheRuntime(const Device& device, ProgramManager& program_manager, + StateTracker& state_tracker); + ~TextureCacheRuntime(); - void UploadTexture(const std::vector<u8>& staging_buffer) override; - void DownloadTexture(std::vector<u8>& staging_buffer) override; + void Finish(); - GLenum GetTarget() const { - return target; - } + ImageBufferMap UploadStagingBuffer(size_t size); - GLuint GetTexture() const { - return texture.handle; - } + ImageBufferMap DownloadStagingBuffer(size_t size); + + void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies); - bool IsCompressed() const { - return is_compressed; + void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) { + UNIMPLEMENTED(); } -protected: - void DecorateSurfaceName() override; + bool CanImageBeCopied(const Image& dst, const Image& src); + + void EmulateCopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies); + + void BlitFramebuffer(Framebuffer* dst, Framebuffer* src, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation); + + void AccelerateImageUpload(Image& image, const ImageBufferMap& map, + std::span<const VideoCommon::SwizzleParameters> swizzles); + + void InsertUploadMemoryBarrier(); - View CreateView(const ViewParams& view_key) override; - View CreateViewInner(const ViewParams& view_key, bool is_proxy); + FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; + + bool HasBrokenTextureViewFormats() const noexcept { + return has_broken_texture_view_formats; + } private: - void UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer); + struct StagingBuffers { + explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_); + ~StagingBuffers(); - GLenum internal_format{}; - GLenum format{}; - GLenum type{}; - bool is_compressed{}; - GLenum target{}; - u32 view_count{}; + ImageBufferMap RequestMap(size_t requested_size, bool insert_fence); - OGLTexture texture; - OGLBuffer texture_buffer; + size_t RequestBuffer(size_t requested_size); + + std::optional<size_t> FindBuffer(size_t requested_size); + + std::vector<OGLSync> syncs; + std::vector<OGLBuffer> buffers; + std::vector<u8*> maps; + std::vector<size_t> sizes; + GLenum storage_flags; + GLenum map_flags; + }; + + const Device& device; + StateTracker& state_tracker; + UtilShaders util_shaders; + + std::array<std::unordered_map<GLenum, FormatProperties>, 3> format_properties; + bool has_broken_texture_view_formats = false; + + StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT}; + StagingBuffers download_buffers{GL_MAP_READ_BIT, GL_MAP_READ_BIT}; + + OGLTexture null_image_1d_array; + OGLTexture null_image_cube_array; + OGLTexture null_image_3d; + OGLTexture null_image_rect; + OGLTextureView null_image_view_1d; + OGLTextureView null_image_view_2d; + OGLTextureView null_image_view_2d_array; + OGLTextureView null_image_view_cube; + + std::array<GLuint, VideoCommon::NUM_IMAGE_VIEW_TYPES> null_image_views; }; -class CachedSurfaceView final : public VideoCommon::ViewBase { +class Image : public VideoCommon::ImageBase { + friend ImageView; + public: - explicit CachedSurfaceView(CachedSurface& surface, const ViewParams& params, bool is_proxy); - ~CachedSurfaceView(); + explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, + VAddr cpu_addr); - /// @brief Attaches this texture view to the currently bound fb_target framebuffer - /// @param attachment Attachment to bind textures to - /// @param fb_target Framebuffer target to attach to (e.g. DRAW_FRAMEBUFFER) - void Attach(GLenum attachment, GLenum fb_target) const; + void UploadMemory(const ImageBufferMap& map, + std::span<const VideoCommon::BufferImageCopy> copies); - GLuint GetTexture(Tegra::Texture::SwizzleSource x_source, - Tegra::Texture::SwizzleSource y_source, - Tegra::Texture::SwizzleSource z_source, - Tegra::Texture::SwizzleSource w_source); + void UploadMemory(const ImageBufferMap& map, std::span<const VideoCommon::BufferCopy> copies); - void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix); + void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); - void MarkAsModified(u64 tick) { - surface.MarkAsModified(true, tick); + GLuint StorageHandle() noexcept; + + GLuint Handle() const noexcept { + return texture.handle; } - GLuint GetTexture() const { - if (is_proxy) { - return surface.GetTexture(); - } - return main_view.handle; +private: + void CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); + + void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); + + OGLTexture texture; + OGLBuffer buffer; + OGLTextureView store_view; + GLenum gl_internal_format = GL_NONE; + GLenum gl_format = GL_NONE; + GLenum gl_type = GL_NONE; +}; + +class ImageView : public VideoCommon::ImageViewBase { + friend Image; + +public: + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&); + + [[nodiscard]] GLuint Handle(ImageViewType query_type) const noexcept { + return views[static_cast<size_t>(query_type)]; } - GLenum GetFormat() const { - return format; + [[nodiscard]] GLuint DefaultHandle() const noexcept { + return default_handle; } - const SurfaceParams& GetSurfaceParams() const { - return surface.GetSurfaceParams(); + [[nodiscard]] GLenum Format() const noexcept { + return internal_format; } private: - OGLTextureView CreateTextureView() const; + void SetupView(const Device& device, Image& image, ImageViewType view_type, GLuint handle, + const VideoCommon::ImageViewInfo& info, + VideoCommon::SubresourceRange view_range); + + std::array<GLuint, VideoCommon::NUM_IMAGE_VIEW_TYPES> views{}; + std::vector<OGLTextureView> stored_views; + GLuint default_handle = 0; + GLenum internal_format = GL_NONE; +}; - CachedSurface& surface; - const GLenum format; - const GLenum target; - const bool is_proxy; +class ImageAlloc : public VideoCommon::ImageAllocBase {}; - std::unordered_map<u32, OGLTextureView> view_cache; - OGLTextureView main_view; +class Sampler { +public: + explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); + + GLuint Handle() const noexcept { + return sampler.handle; + } - // Use an invalid default so it always fails the comparison test - u32 current_swizzle = 0xffffffff; - GLuint current_view = 0; +private: + OGLSampler sampler; }; -class TextureCacheOpenGL final : public TextureCacheBase { +class Framebuffer { public: - explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const Device& device, StateTracker& state_tracker); - ~TextureCacheOpenGL(); - -protected: - Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override; - - void ImageCopy(Surface& src_surface, Surface& dst_surface, - const VideoCommon::CopyParams& copy_params) override; + explicit Framebuffer(TextureCacheRuntime&, std::span<ImageView*, NUM_RT> color_buffers, + ImageView* depth_buffer, const VideoCommon::RenderTargets& key); - void ImageBlit(View& src_view, View& dst_view, - const Tegra::Engines::Fermi2D::Config& copy_config) override; + [[nodiscard]] GLuint Handle() const noexcept { + return framebuffer.handle; + } - void BufferCopy(Surface& src_surface, Surface& dst_surface) override; + [[nodiscard]] GLbitfield BufferBits() const noexcept { + return buffer_bits; + } private: - GLuint FetchPBO(std::size_t buffer_size); - - StateTracker& state_tracker; + OGLFramebuffer framebuffer; + GLbitfield buffer_bits = GL_NONE; +}; - OGLFramebuffer src_framebuffer; - OGLFramebuffer dst_framebuffer; - std::unordered_map<u32, OGLBuffer> copy_pbo_cache; +struct TextureCacheParams { + static constexpr bool ENABLE_VALIDATION = true; + static constexpr bool FRAMEBUFFER_BLITS = true; + static constexpr bool HAS_EMULATED_COPIES = true; + + using Runtime = OpenGL::TextureCacheRuntime; + using Image = OpenGL::Image; + using ImageAlloc = OpenGL::ImageAlloc; + using ImageView = OpenGL::ImageView; + using Sampler = OpenGL::Sampler; + using Framebuffer = OpenGL::Framebuffer; }; +using TextureCache = VideoCommon::TextureCache<TextureCacheParams>; + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index fe9bd4b5a..f7ad8f370 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -4,23 +4,10 @@ #pragma once -#include <array> #include <glad/glad.h> -#include "common/common_types.h" -#include "common/logging/log.h" #include "video_core/engines/maxwell_3d.h" -namespace OpenGL { - -using GLvec2 = std::array<GLfloat, 2>; -using GLvec3 = std::array<GLfloat, 3>; -using GLvec4 = std::array<GLfloat, 4>; - -using GLuvec2 = std::array<GLuint, 2>; -using GLuvec3 = std::array<GLuint, 3>; -using GLuvec4 = std::array<GLuint, 4>; - -namespace MaxwellToGL { +namespace OpenGL::MaxwellToGL { using Maxwell = Tegra::Engines::Maxwell3D::Regs; @@ -47,6 +34,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) { return GL_UNSIGNED_INT; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return GL_UNSIGNED_INT_2_10_10_10_REV; + default: + break; } break; case Maxwell::VertexAttribute::Type::SignedNorm: @@ -70,6 +59,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) { return GL_INT; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return GL_INT_2_10_10_10_REV; + default: + break; } break; case Maxwell::VertexAttribute::Type::Float: @@ -84,6 +75,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) { case Maxwell::VertexAttribute::Size::Size_32_32_32: case Maxwell::VertexAttribute::Size::Size_32_32_32_32: return GL_FLOAT; + default: + break; } break; } @@ -101,7 +94,7 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) { case Maxwell::IndexFormat::UnsignedInt: return GL_UNSIGNED_INT; } - UNREACHABLE_MSG("Invalid index_format={}", static_cast<u32>(index_format)); + UNREACHABLE_MSG("Invalid index_format={}", index_format); return {}; } @@ -138,7 +131,7 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { case Maxwell::PrimitiveTopology::Patches: return GL_PATCHES; } - UNREACHABLE_MSG("Invalid topology={}", static_cast<int>(topology)); + UNREACHABLE_MSG("Invalid topology={}", topology); return GL_POINTS; } @@ -166,8 +159,8 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, } break; } - UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}", - static_cast<u32>(filter_mode), static_cast<u32>(mipmap_filter_mode)); + UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}", filter_mode, + mipmap_filter_mode); return GL_NEAREST; } @@ -198,7 +191,7 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { return GL_MIRROR_CLAMP_TO_EDGE; } } - UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); + UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", wrap_mode); return GL_REPEAT; } @@ -221,7 +214,7 @@ inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) { case Tegra::Texture::DepthCompareFunc::Always: return GL_ALWAYS; } - UNIMPLEMENTED_MSG("Unimplemented texture depth compare function={}", static_cast<u32>(func)); + UNIMPLEMENTED_MSG("Unimplemented texture depth compare function={}", func); return GL_GREATER; } @@ -243,7 +236,7 @@ inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { case Maxwell::Blend::Equation::MaxGL: return GL_MAX; } - UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation)); + UNIMPLEMENTED_MSG("Unimplemented blend equation={}", equation); return GL_FUNC_ADD; } @@ -307,27 +300,7 @@ inline GLenum BlendFunc(Maxwell::Blend::Factor factor) { case Maxwell::Blend::Factor::OneMinusConstantAlphaGL: return GL_ONE_MINUS_CONSTANT_ALPHA; } - UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor)); - return GL_ZERO; -} - -inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) { - switch (source) { - case Tegra::Texture::SwizzleSource::Zero: - return GL_ZERO; - case Tegra::Texture::SwizzleSource::R: - return GL_RED; - case Tegra::Texture::SwizzleSource::G: - return GL_GREEN; - case Tegra::Texture::SwizzleSource::B: - return GL_BLUE; - case Tegra::Texture::SwizzleSource::A: - return GL_ALPHA; - case Tegra::Texture::SwizzleSource::OneInt: - case Tegra::Texture::SwizzleSource::OneFloat: - return GL_ONE; - } - UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(source)); + UNIMPLEMENTED_MSG("Unimplemented blend factor={}", factor); return GL_ZERO; } @@ -358,7 +331,7 @@ inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) { case Maxwell::ComparisonOp::AlwaysOld: return GL_ALWAYS; } - UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison)); + UNIMPLEMENTED_MSG("Unimplemented comparison op={}", comparison); return GL_ALWAYS; } @@ -389,7 +362,7 @@ inline GLenum StencilOp(Maxwell::StencilOp stencil) { case Maxwell::StencilOp::DecrWrapOGL: return GL_DECR_WRAP; } - UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil)); + UNIMPLEMENTED_MSG("Unimplemented stencil op={}", stencil); return GL_KEEP; } @@ -400,7 +373,7 @@ inline GLenum FrontFace(Maxwell::FrontFace front_face) { case Maxwell::FrontFace::CounterClockWise: return GL_CCW; } - UNIMPLEMENTED_MSG("Unimplemented front face cull={}", static_cast<u32>(front_face)); + UNIMPLEMENTED_MSG("Unimplemented front face cull={}", front_face); return GL_CCW; } @@ -413,7 +386,7 @@ inline GLenum CullFace(Maxwell::CullFace cull_face) { case Maxwell::CullFace::FrontAndBack: return GL_FRONT_AND_BACK; } - UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face)); + UNIMPLEMENTED_MSG("Unimplemented cull face={}", cull_face); return GL_BACK; } @@ -452,7 +425,7 @@ inline GLenum LogicOp(Maxwell::LogicOperation operation) { case Maxwell::LogicOperation::Set: return GL_SET; } - UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(operation)); + UNIMPLEMENTED_MSG("Unimplemented logic operation={}", operation); return GL_COPY; } @@ -465,14 +438,26 @@ inline GLenum PolygonMode(Maxwell::PolygonMode polygon_mode) { case Maxwell::PolygonMode::Fill: return GL_FILL; } - UNREACHABLE_MSG("Invalid polygon mode={}", static_cast<int>(polygon_mode)); + UNREACHABLE_MSG("Invalid polygon mode={}", polygon_mode); return GL_FILL; } +inline GLenum ReductionFilter(Tegra::Texture::SamplerReduction filter) { + switch (filter) { + case Tegra::Texture::SamplerReduction::WeightedAverage: + return GL_WEIGHTED_AVERAGE_ARB; + case Tegra::Texture::SamplerReduction::Min: + return GL_MIN; + case Tegra::Texture::SamplerReduction::Max: + return GL_MAX; + } + UNREACHABLE_MSG("Invalid reduction filter={}", static_cast<int>(filter)); + return GL_WEIGHTED_AVERAGE_ARB; +} + inline GLenum ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) { // Enumeration order matches register order. We can convert it arithmetically. return GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV + static_cast<GLenum>(swizzle); } -} // namespace MaxwellToGL -} // namespace OpenGL +} // namespace OpenGL::MaxwellToGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index b759c2dba..9d2acd4d9 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -23,29 +23,13 @@ #include "core/telemetry_session.h" #include "video_core/host_shaders/opengl_present_frag.h" #include "video_core/host_shaders/opengl_present_vert.h" -#include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/textures/decoders.h" namespace OpenGL { - namespace { - -constexpr std::size_t SWAP_CHAIN_SIZE = 3; - -struct Frame { - u32 width{}; /// Width of the frame (to detect resize) - u32 height{}; /// Height of the frame - bool color_reloaded{}; /// Texture attachment was recreated (ie: resized) - OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO - OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread - OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread - GLsync render_fence{}; /// Fence created on the render thread - GLsync present_fence{}; /// Fence created on the presentation thread - bool is_srgb{}; /// Framebuffer is sRGB or RGB -}; - constexpr GLint PositionLocation = 0; constexpr GLint TexCoordLocation = 1; constexpr GLint ModelViewMatrixLocation = 0; @@ -58,24 +42,6 @@ struct ScreenRectVertex { std::array<GLfloat, 2> tex_coord; }; -/// Returns true if any debug tool is attached -bool HasDebugTool() { - const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED"); - if (nsight) { - return true; - } - - GLint num_extensions; - glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); - for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) { - const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index)); - if (!std::strcmp(name, "GL_EXT_debug_tool")) { - return true; - } - } - return false; -} - /** * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left * corner and (width, height) on the lower-bottom. @@ -156,217 +122,62 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit break; } } - } // Anonymous namespace -/** - * For smooth Vsync rendering, we want to always present the latest frame that the core generates, - * but also make sure that rendering happens at the pace that the frontend dictates. This is a - * helper class that the renderer uses to sync frames between the render thread and the presentation - * thread - */ -class FrameMailbox { -public: - std::mutex swap_chain_lock; - std::condition_variable present_cv; - std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{}; - std::queue<Frame*> free_queue; - std::deque<Frame*> present_queue; - Frame* previous_frame{}; - - FrameMailbox() { - for (auto& frame : swap_chain) { - free_queue.push(&frame); - } - } - - ~FrameMailbox() { - // lock the mutex and clear out the present and free_queues and notify any people who are - // blocked to prevent deadlock on shutdown - std::scoped_lock lock{swap_chain_lock}; - std::queue<Frame*>().swap(free_queue); - present_queue.clear(); - present_cv.notify_all(); - } - - void ReloadPresentFrame(Frame* frame, u32 height, u32 width) { - frame->present.Release(); - frame->present.Create(); - GLint previous_draw_fbo{}; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - frame->color.handle); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); - frame->color_reloaded = false; - } - - void ReloadRenderFrame(Frame* frame, u32 width, u32 height) { - // Recreate the color texture attachment - frame->color.Release(); - frame->color.Create(); - const GLenum internal_format = frame->is_srgb ? GL_SRGB8 : GL_RGB8; - glNamedRenderbufferStorage(frame->color.handle, internal_format, width, height); - - // Recreate the FBO for the render target - frame->render.Release(); - frame->render.Create(); - glBindFramebuffer(GL_FRAMEBUFFER, frame->render.handle); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - frame->color.handle); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); - } - - frame->width = width; - frame->height = height; - frame->color_reloaded = true; - } - - Frame* GetRenderFrame() { - std::unique_lock lock{swap_chain_lock}; - - // If theres no free frames, we will reuse the oldest render frame - if (free_queue.empty()) { - auto frame = present_queue.back(); - present_queue.pop_back(); - return frame; - } - - Frame* frame = free_queue.front(); - free_queue.pop(); - return frame; - } - - void ReleaseRenderFrame(Frame* frame) { - std::unique_lock lock{swap_chain_lock}; - present_queue.push_front(frame); - present_cv.notify_one(); - } - - Frame* TryGetPresentFrame(int timeout_ms) { - std::unique_lock lock{swap_chain_lock}; - // wait for new entries in the present_queue - present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), - [&] { return !present_queue.empty(); }); - if (present_queue.empty()) { - // timed out waiting for a frame to draw so return the previous frame - return previous_frame; - } - - // free the previous frame and add it back to the free queue - if (previous_frame) { - free_queue.push(previous_frame); - } - - // the newest entries are pushed to the front of the queue - Frame* frame = present_queue.front(); - present_queue.pop_front(); - // remove all old entries from the present queue and move them back to the free_queue - for (auto f : present_queue) { - free_queue.push(f); - } - present_queue.clear(); - previous_frame = frame; - return frame; - } -}; - -RendererOpenGL::RendererOpenGL(Core::System& system_, Core::Frontend::EmuWindow& emu_window_, - Tegra::GPU& gpu_, +RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_, + Core::Frontend::EmuWindow& emu_window_, + Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, std::unique_ptr<Core::Frontend::GraphicsContext> context_) - : RendererBase{emu_window_, std::move(context_)}, system{system_}, - emu_window{emu_window_}, gpu{gpu_}, program_manager{device}, has_debug_tool{HasDebugTool()} {} + : RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_}, + emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, state_tracker{gpu}, + program_manager{device}, + rasterizer(emu_window, gpu, cpu_memory, device, screen_info, program_manager, state_tracker) { + if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) { + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(DebugHandler, nullptr); + } + AddTelemetryFields(); + InitOpenGLObjects(); +} RendererOpenGL::~RendererOpenGL() = default; -MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64)); -MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128)); - void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { if (!framebuffer) { return; } - PrepareRendertarget(framebuffer); RenderScreenshot(); - Frame* frame; - { - MICROPROFILE_SCOPE(OpenGL_WaitPresent); - - frame = frame_mailbox->GetRenderFrame(); + state_tracker.BindFramebuffer(0); + DrawScreen(emu_window.GetFramebufferLayout()); - // Clean up sync objects before drawing + ++m_current_frame; - // INTEL driver workaround. We can't delete the previous render sync object until we are - // sure that the presentation is done - if (frame->present_fence) { - glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); - } - - // delete the draw fence if the frame wasn't presented - if (frame->render_fence) { - glDeleteSync(frame->render_fence); - frame->render_fence = 0; - } + rasterizer.TickFrame(); - // wait for the presentation to be done - if (frame->present_fence) { - glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(frame->present_fence); - frame->present_fence = 0; - } - } - - { - MICROPROFILE_SCOPE(OpenGL_RenderFrame); - const auto& layout = render_window.GetFramebufferLayout(); - - // Recreate the frame if the size of the window has changed - if (layout.width != frame->width || layout.height != frame->height || - screen_info.display_srgb != frame->is_srgb) { - LOG_DEBUG(Render_OpenGL, "Reloading render frame"); - frame->is_srgb = screen_info.display_srgb; - frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height); - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame->render.handle); - DrawScreen(layout); - // Create a fence for the frontend to wait on and swap this frame to OffTex - frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - frame_mailbox->ReleaseRenderFrame(frame); - m_current_frame++; - rasterizer->TickFrame(); - } - - render_window.PollEvents(); - if (has_debug_tool) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - Present(0); - context->SwapBuffers(); - } + context->SwapBuffers(); + render_window.OnFrameDisplayed(); } void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { - if (framebuffer) { - // If framebuffer is provided, reload it from memory to a texture - if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || - screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || - screen_info.texture.pixel_format != framebuffer->pixel_format || - gl_framebuffer_data.empty()) { - // Reallocate texture if the framebuffer size has changed. - // This is expected to not happen very often and hence should not be a - // performance problem. - ConfigureFramebufferTexture(screen_info.texture, *framebuffer); - } - - // Load the framebuffer from memory, draw it to the screen, and swap buffers - LoadFBToScreenInfo(*framebuffer); + if (!framebuffer) { + return; + } + // If framebuffer is provided, reload it from memory to a texture + if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || + screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || + screen_info.texture.pixel_format != framebuffer->pixel_format || + gl_framebuffer_data.empty()) { + // Reallocate texture if the framebuffer size has changed. + // This is expected to not happen very often and hence should not be a + // performance problem. + ConfigureFramebufferTexture(screen_info.texture, *framebuffer); } + + // Load the framebuffer from memory, draw it to the screen, and swap buffers + LoadFBToScreenInfo(*framebuffer); } void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { @@ -375,26 +186,27 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf framebuffer_crop_rect = framebuffer.crop_rect; const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; - if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { + if (rasterizer.AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { return; } // Reset the screen info's display texture to its own permanent texture screen_info.display_texture = screen_info.texture.resource.handle; - const auto pixel_format{ - VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; - const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; - const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; - u8* const host_ptr{system.Memory().GetPointer(framebuffer_addr)}; - rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes); - // TODO(Rodrigo): Read this from HLE constexpr u32 block_height_log2 = 4; - VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format, - framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1, - gl_framebuffer_data.data(), host_ptr); - + const auto pixel_format{ + VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; + const u32 bytes_per_pixel{VideoCore::Surface::BytesPerBlock(pixel_format)}; + const u64 size_in_bytes{Tegra::Texture::CalculateSize( + true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)}; + const u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)}; + const std::span<const u8> input_data(host_ptr, size_in_bytes); + Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel, + framebuffer.width, framebuffer.height, 1, block_height_log2, + 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); // Update existing texture @@ -416,8 +228,6 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color } void RendererOpenGL::InitOpenGLObjects() { - frame_mailbox = std::make_unique<FrameMailbox>(); - glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), Settings::values.bg_blue.GetValue(), 0.0f); @@ -435,6 +245,10 @@ void RendererOpenGL::InitOpenGLObjects() { glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex_program.handle); glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment_program.handle); + // Generate presentation sampler + present_sampler.Create(); + glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + // Generate VBO handle for drawing vertex_buffer.Create(); @@ -452,9 +266,15 @@ void RendererOpenGL::InitOpenGLObjects() { // Clear screen to black LoadColorToActiveGLTexture(0, 0, 0, 0, screen_info.texture); + // Enable seamless cubemaps when per texture parameters are not available + if (!GLAD_GL_ARB_seamless_cubemap_per_texture && !GLAD_GL_AMD_seamless_cubemap_per_texture) { + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + } + // Enable unified vertex attributes and query vertex buffer address when the driver supports it if (device.HasVertexBufferUnifiedMemory()) { glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV); + glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV); glMakeNamedBufferResidentNV(vertex_buffer.handle, GL_READ_ONLY); glGetNamedBufferParameterui64vNV(vertex_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, @@ -471,19 +291,10 @@ void RendererOpenGL::AddTelemetryFields() { LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor); LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); - auto& telemetry_session = system.TelemetrySession(); constexpr auto user_system = Common::Telemetry::FieldType::UserSystem; - telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor); - telemetry_session.AddField(user_system, "GPU_Model", gpu_model); - telemetry_session.AddField(user_system, "GPU_OpenGL_Version", gl_version); -} - -void RendererOpenGL::CreateRasterizer() { - if (rasterizer) { - return; - } - rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, device, screen_info, - program_manager, state_tracker); + telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor)); + telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model)); + telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version)); } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, @@ -494,7 +305,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, const auto pixel_format{ VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; - const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; + const u32 bytes_per_pixel{VideoCore::Surface::BytesPerBlock(pixel_format)}; gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel); GLint internal_format; @@ -513,8 +324,8 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, internal_format = GL_RGBA8; texture.gl_format = GL_RGBA; texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; - UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", - static_cast<u32>(framebuffer.pixel_format)); + // UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", + // static_cast<u32>(framebuffer.pixel_format)); } texture.resource.Release(); @@ -546,7 +357,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { } else { // Other transformations are unsupported LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}", - static_cast<u32>(framebuffer_transform_flags)); + framebuffer_transform_flags); UNIMPLEMENTED(); } } @@ -580,7 +391,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { state_tracker.NotifyPolygonModes(); state_tracker.NotifyViewport0(); state_tracker.NotifyScissor0(); - state_tracker.NotifyColorMask0(); + state_tracker.NotifyColorMask(0); state_tracker.NotifyBlend0(); state_tracker.NotifyFramebuffer(); state_tracker.NotifyFrontFace(); @@ -596,6 +407,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { program_manager.BindHostPipeline(pipeline.handle); + state_tracker.ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); glEnable(GL_CULL_FACE); if (screen_info.display_srgb) { glEnable(GL_FRAMEBUFFER_SRGB); @@ -614,7 +426,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { glCullFace(GL_BACK); glFrontFace(GL_CW); glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width), static_cast<GLfloat>(layout.height)); glDepthRangeIndexed(0, 0.0, 0.0); @@ -638,7 +449,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { } glBindTextureUnit(0, screen_info.display_texture); - glBindSampler(0, 0); + glBindSampler(0, present_sampler.handle); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -646,51 +457,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { program_manager.RestoreGuestPipeline(); } -bool RendererOpenGL::TryPresent(int timeout_ms) { - if (has_debug_tool) { - LOG_DEBUG(Render_OpenGL, - "Skipping presentation because we are presenting on the main context"); - return false; - } - return Present(timeout_ms); -} - -bool RendererOpenGL::Present(int timeout_ms) { - const auto& layout = render_window.GetFramebufferLayout(); - auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms); - if (!frame) { - LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); - return false; - } - - // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a - // readback since we won't be doing any blending - glClear(GL_COLOR_BUFFER_BIT); - - // Recreate the presentation FBO if the color attachment was changed - if (frame->color_reloaded) { - LOG_DEBUG(Render_OpenGL, "Reloading present frame"); - frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height); - } - glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); - // INTEL workaround. - // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete - // it on the emulation thread without too much penalty - // glDeleteSync(frame.render_sync); - // frame.render_sync = 0; - - glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); - glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - - // Insert fence for the main thread to block on - frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - return true; -} - void RendererOpenGL::RenderScreenshot() { if (!renderer_settings.screenshot_requested) { return; @@ -705,7 +471,7 @@ void RendererOpenGL::RenderScreenshot() { screenshot_framebuffer.Create(); glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle); - Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; + const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; GLuint renderbuffer; glGenRenderbuffers(1, &renderbuffer); @@ -716,6 +482,8 @@ void RendererOpenGL::RenderScreenshot() { DrawScreen(layout); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, renderer_settings.screenshot_bits); @@ -729,25 +497,4 @@ void RendererOpenGL::RenderScreenshot() { renderer_settings.screenshot_requested = false; } -bool RendererOpenGL::Init() { - if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) { - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(DebugHandler, nullptr); - } - - AddTelemetryFields(); - - if (!GLAD_GL_VERSION_4_3) { - return false; - } - - InitOpenGLObjects(); - CreateRasterizer(); - - return true; -} - -void RendererOpenGL::ShutDown() {} - } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 52ea76b7d..cc19a110f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -10,22 +10,32 @@ #include "common/math_util.h" #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state_tracker.h" namespace Core { class System; -} +class TelemetrySession; +} // namespace Core namespace Core::Frontend { class EmuWindow; } +namespace Core::Memory { +class Memory; +} + namespace Layout { struct FramebufferLayout; } +namespace Tegra { +class GPU; +} + namespace OpenGL { /// Structure used for storing information about the textures for the Switch screen @@ -46,25 +56,19 @@ struct ScreenInfo { TextureInfo texture; }; -struct PresentationTexture { - u32 width = 0; - u32 height = 0; - OGLTexture texture; -}; - -class FrameMailbox; - class RendererOpenGL final : public VideoCore::RendererBase { public: - explicit RendererOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, - Tegra::GPU& gpu, - std::unique_ptr<Core::Frontend::GraphicsContext> context); + explicit RendererOpenGL(Core::TelemetrySession& telemetry_session_, + Core::Frontend::EmuWindow& emu_window_, + Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, + std::unique_ptr<Core::Frontend::GraphicsContext> context_); ~RendererOpenGL() override; - bool Init() override; - void ShutDown() override; void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - bool TryPresent(int timeout_ms) override; + + VideoCore::RasterizerInterface* ReadRasterizer() override { + return &rasterizer; + } private: /// Initializes the OpenGL state and creates persistent objects. @@ -72,8 +76,6 @@ private: void AddTelemetryFields(); - void CreateRasterizer(); - void ConfigureFramebufferTexture(TextureInfo& texture, const Tegra::FramebufferConfig& framebuffer); @@ -92,16 +94,18 @@ private: void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); - bool Present(int timeout_ms); - - Core::System& system; + Core::TelemetrySession& telemetry_session; Core::Frontend::EmuWindow& emu_window; + Core::Memory::Memory& cpu_memory; Tegra::GPU& gpu; - const Device device; - StateTracker state_tracker{system}; + Device device; + StateTracker state_tracker; + ProgramManager program_manager; + RasterizerOpenGL rasterizer; // OpenGL object IDs + OGLSampler present_sampler; OGLBuffer vertex_buffer; OGLProgram vertex_program; OGLProgram fragment_program; @@ -114,20 +118,12 @@ private: /// Display information for Switch screen ScreenInfo screen_info; - /// Global dummy shader pipeline - ProgramManager program_manager; - /// OpenGL framebuffer data std::vector<u8> gl_framebuffer_data; /// Used for transforming the framebuffer orientation Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{}; Common::Rectangle<int> framebuffer_crop_rect; - - /// Frame presentation mailbox - std::unique_ptr<FrameMailbox> frame_mailbox; - - bool has_debug_tool = false; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp new file mode 100644 index 000000000..31ec68505 --- /dev/null +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -0,0 +1,225 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <bit> +#include <span> +#include <string_view> + +#include <glad/glad.h> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "video_core/host_shaders/block_linear_unswizzle_2d_comp.h" +#include "video_core/host_shaders/block_linear_unswizzle_3d_comp.h" +#include "video_core/host_shaders/opengl_copy_bc4_comp.h" +#include "video_core/host_shaders/pitch_unswizzle_comp.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" +#include "video_core/renderer_opengl/gl_texture_cache.h" +#include "video_core/renderer_opengl/util_shaders.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/accelerated_swizzle.h" +#include "video_core/texture_cache/types.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/decoders.h" + +namespace OpenGL { + +using namespace HostShaders; + +using VideoCommon::Extent3D; +using VideoCommon::ImageCopy; +using VideoCommon::ImageType; +using VideoCommon::SwizzleParameters; +using VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams; +using VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams; +using VideoCore::Surface::BytesPerBlock; + +namespace { + +OGLProgram MakeProgram(std::string_view source) { + OGLShader shader; + shader.Create(source, GL_COMPUTE_SHADER); + + OGLProgram program; + program.Create(true, false, shader.handle); + return program; +} + +} // Anonymous namespace + +UtilShaders::UtilShaders(ProgramManager& program_manager_) + : program_manager{program_manager_}, + block_linear_unswizzle_2d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_2D_COMP)), + block_linear_unswizzle_3d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_3D_COMP)), + pitch_unswizzle_program(MakeProgram(PITCH_UNSWIZZLE_COMP)), + copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) { + const auto swizzle_table = Tegra::Texture::MakeSwizzleTable(); + swizzle_table_buffer.Create(); + glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0); +} + +UtilShaders::~UtilShaders() = default; + +void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map, + std::span<const SwizzleParameters> swizzles) { + static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; + static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; + static constexpr GLuint BINDING_INPUT_BUFFER = 1; + static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; + + program_manager.BindHostCompute(block_linear_unswizzle_2d_program.handle); + glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); + + const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); + for (const SwizzleParameters& swizzle : swizzles) { + const Extent3D num_tiles = swizzle.num_tiles; + const size_t input_offset = swizzle.buffer_offset + map.offset; + + const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width); + const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height); + + const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + glUniform3uiv(0, 1, params.origin.data()); + glUniform3iv(1, 1, params.destination.data()); + glUniform1ui(2, params.bytes_per_block_log2); + glUniform1ui(3, params.layer_stride); + glUniform1ui(4, params.block_size); + glUniform1ui(5, params.x_shift); + glUniform1ui(6, params.block_height); + glUniform1ui(7, params.block_height_mask); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, + GL_WRITE_ONLY, store_format); + glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers); + } + program_manager.RestoreGuestCompute(); +} + +void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map, + std::span<const SwizzleParameters> swizzles) { + static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8}; + + static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; + static constexpr GLuint BINDING_INPUT_BUFFER = 1; + static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; + + glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); + program_manager.BindHostCompute(block_linear_unswizzle_3d_program.handle); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); + + const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); + for (const SwizzleParameters& swizzle : swizzles) { + const Extent3D num_tiles = swizzle.num_tiles; + const size_t input_offset = swizzle.buffer_offset + map.offset; + + const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width); + const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height); + const u32 num_dispatches_z = Common::DivCeil(num_tiles.depth, WORKGROUP_SIZE.depth); + + const auto params = MakeBlockLinearSwizzle3DParams(swizzle, image.info); + glUniform3uiv(0, 1, params.origin.data()); + glUniform3iv(1, 1, params.destination.data()); + glUniform1ui(2, params.bytes_per_block_log2); + glUniform1ui(3, params.slice_size); + glUniform1ui(4, params.block_size); + glUniform1ui(5, params.x_shift); + glUniform1ui(6, params.block_height); + glUniform1ui(7, params.block_height_mask); + glUniform1ui(8, params.block_depth); + glUniform1ui(9, params.block_depth_mask); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, + GL_WRITE_ONLY, store_format); + glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z); + } + program_manager.RestoreGuestCompute(); +} + +void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map, + std::span<const SwizzleParameters> swizzles) { + static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; + static constexpr GLuint BINDING_INPUT_BUFFER = 0; + static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; + static constexpr GLuint LOC_ORIGIN = 0; + static constexpr GLuint LOC_DESTINATION = 1; + static constexpr GLuint LOC_BYTES_PER_BLOCK = 2; + static constexpr GLuint LOC_PITCH = 3; + + const u32 bytes_per_block = BytesPerBlock(image.info.format); + const GLenum format = StoreFormat(bytes_per_block); + const u32 pitch = image.info.pitch; + + UNIMPLEMENTED_IF_MSG(!std::has_single_bit(bytes_per_block), + "Non-power of two images are not implemented"); + + program_manager.BindHostCompute(pitch_unswizzle_program.handle); + glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); + glUniform2ui(LOC_ORIGIN, 0, 0); + glUniform2i(LOC_DESTINATION, 0, 0); + glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block); + glUniform1ui(LOC_PITCH, pitch); + glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, + format); + for (const SwizzleParameters& swizzle : swizzles) { + const Extent3D num_tiles = swizzle.num_tiles; + const size_t input_offset = swizzle.buffer_offset + map.offset; + + const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width); + const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height); + + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + glDispatchCompute(num_dispatches_x, num_dispatches_y, 1); + } + program_manager.RestoreGuestCompute(); +} + +void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const ImageCopy> copies) { + static constexpr GLuint BINDING_INPUT_IMAGE = 0; + static constexpr GLuint BINDING_OUTPUT_IMAGE = 1; + static constexpr GLuint LOC_SRC_OFFSET = 0; + static constexpr GLuint LOC_DST_OFFSET = 1; + + program_manager.BindHostCompute(copy_bc4_program.handle); + + for (const ImageCopy& copy : copies) { + ASSERT(copy.src_subresource.base_layer == 0); + ASSERT(copy.src_subresource.num_layers == 1); + ASSERT(copy.dst_subresource.base_layer == 0); + ASSERT(copy.dst_subresource.num_layers == 1); + + glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z); + glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z); + glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(), + copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); + glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(), + copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI); + glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth); + } + program_manager.RestoreGuestCompute(); +} + +GLenum StoreFormat(u32 bytes_per_block) { + switch (bytes_per_block) { + case 1: + return GL_R8UI; + case 2: + return GL_R16UI; + case 4: + return GL_R32UI; + case 8: + return GL_RG32UI; + case 16: + return GL_RGBA32UI; + } + UNREACHABLE(); + return GL_R8UI; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h new file mode 100644 index 000000000..7b1d16b09 --- /dev/null +++ b/src/video_core/renderer_opengl/util_shaders.h @@ -0,0 +1,52 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <span> + +#include <glad/glad.h> + +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/texture_cache/types.h" + +namespace OpenGL { + +class Image; +class ProgramManager; + +struct ImageBufferMap; + +class UtilShaders { +public: + explicit UtilShaders(ProgramManager& program_manager); + ~UtilShaders(); + + void BlockLinearUpload2D(Image& image, const ImageBufferMap& map, + std::span<const VideoCommon::SwizzleParameters> swizzles); + + void BlockLinearUpload3D(Image& image, const ImageBufferMap& map, + std::span<const VideoCommon::SwizzleParameters> swizzles); + + void PitchUpload(Image& image, const ImageBufferMap& map, + std::span<const VideoCommon::SwizzleParameters> swizzles); + + void CopyBC4(Image& dst_image, Image& src_image, + std::span<const VideoCommon::ImageCopy> copies); + +private: + ProgramManager& program_manager; + + OGLBuffer swizzle_table_buffer; + + OGLProgram block_linear_unswizzle_2d_program; + OGLProgram block_linear_unswizzle_3d_program; + OGLProgram pitch_unswizzle_program; + OGLProgram copy_bc4_program; +}; + +GLenum StoreFormat(u32 bytes_per_block); + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp deleted file mode 100644 index 6d7bb16b2..000000000 --- a/src/video_core/renderer_opengl/utils.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <string> -#include <vector> - -#include <fmt/format.h> -#include <glad/glad.h> - -#include "common/common_types.h" -#include "video_core/renderer_opengl/gl_state_tracker.h" -#include "video_core/renderer_opengl/utils.h" - -namespace OpenGL { - -void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info) { - if (!GLAD_GL_KHR_debug) { - // We don't need to throw an error as this is just for debugging - return; - } - - std::string object_label; - if (extra_info.empty()) { - switch (identifier) { - case GL_TEXTURE: - object_label = fmt::format("Texture@0x{:016X}", addr); - break; - case GL_PROGRAM: - object_label = fmt::format("Shader@0x{:016X}", addr); - break; - default: - object_label = fmt::format("Object(0x{:X})@0x{:016X}", identifier, addr); - break; - } - } else { - object_label = fmt::format("{}@0x{:016X}", extra_info, addr); - } - glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str())); -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h deleted file mode 100644 index 9c09ee12c..000000000 --- a/src/video_core/renderer_opengl/utils.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string_view> -#include <vector> -#include <glad/glad.h> -#include "common/common_types.h" - -namespace OpenGL { - -void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {}); - -} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp new file mode 100644 index 000000000..1f6a169ae --- /dev/null +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -0,0 +1,624 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" +#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" +#include "video_core/host_shaders/full_screen_triangle_vert_spv.h" +#include "video_core/host_shaders/vulkan_blit_color_float_frag_spv.h" +#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" +#include "video_core/renderer_vulkan/blit_image.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/renderer_vulkan/vk_state_tracker.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/surface.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +using VideoCommon::ImageViewType; + +namespace { +struct PushConstants { + std::array<float, 2> tex_scale; + std::array<float, 2> tex_offset; +}; + +template <u32 binding> +inline constexpr VkDescriptorSetLayoutBinding TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING{ + .binding = binding, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = nullptr, +}; +constexpr std::array TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{ + TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>, + TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>, +}; +constexpr VkDescriptorSetLayoutCreateInfo ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = 1, + .pBindings = &TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>, +}; +constexpr VkDescriptorSetLayoutCreateInfo TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = static_cast<u32>(TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()), + .pBindings = TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(), +}; +constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{ + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + .size = sizeof(PushConstants), +}; +constexpr VkPipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, +}; +constexpr VkPipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, +}; +constexpr VkPipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .viewportCount = 1, + .pViewports = nullptr, + .scissorCount = 1, + .pScissors = nullptr, +}; +constexpr VkPipelineRasterizationStateCreateInfo PIPELINE_RASTERIZATION_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, +}; +constexpr VkPipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, +}; +constexpr std::array DYNAMIC_STATES{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, +}; +constexpr VkPipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()), + .pDynamicStates = DYNAMIC_STATES.data(), +}; +constexpr VkPipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 0, + .pAttachments = nullptr, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, +}; +constexpr VkPipelineColorBlendAttachmentState PIPELINE_COLOR_BLEND_ATTACHMENT_STATE{ + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, +}; +constexpr VkPipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 1, + .pAttachments = &PIPELINE_COLOR_BLEND_ATTACHMENT_STATE, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, +}; +constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE, + .front = VkStencilOpState{}, + .back = VkStencilOpState{}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, +}; + +template <VkFilter filter> +inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = filter, + .minFilter = filter, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 0.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + .unnormalizedCoordinates = VK_TRUE, +}; + +constexpr VkPipelineLayoutCreateInfo PipelineLayoutCreateInfo( + const VkDescriptorSetLayout* set_layout) { + return VkPipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &PUSH_CONSTANT_RANGE, + }; +} + +constexpr VkPipelineShaderStageCreateInfo PipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, + VkShaderModule shader) { + return VkPipelineShaderStageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = stage, + .module = shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }; +} + +constexpr std::array<VkPipelineShaderStageCreateInfo, 2> MakeStages( + VkShaderModule vertex_shader, VkShaderModule fragment_shader) { + return std::array{ + PipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader), + PipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader), + }; +} + +void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descriptor_set, + VkSampler sampler, VkImageView image_view) { + const VkDescriptorImageInfo image_info{ + .sampler = sampler, + .imageView = image_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + const VkWriteDescriptorSet write_descriptor_set{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }; + device.GetLogical().UpdateDescriptorSets(write_descriptor_set, nullptr); +} + +void UpdateTwoTexturesDescriptorSet(const Device& device, VkDescriptorSet descriptor_set, + VkSampler sampler, VkImageView image_view_0, + VkImageView image_view_1) { + const VkDescriptorImageInfo image_info_0{ + .sampler = sampler, + .imageView = image_view_0, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + const VkDescriptorImageInfo image_info_1{ + .sampler = sampler, + .imageView = image_view_1, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + const std::array write_descriptor_sets{ + VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info_0, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }, + VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = descriptor_set, + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info_1, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }, + }; + device.GetLogical().UpdateDescriptorSets(write_descriptor_sets, nullptr); +} + +void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region) { + const VkOffset2D offset{ + .x = std::min(dst_region[0].x, dst_region[1].x), + .y = std::min(dst_region[0].y, dst_region[1].y), + }; + const VkExtent2D extent{ + .width = static_cast<u32>(std::abs(dst_region[1].x - dst_region[0].x)), + .height = static_cast<u32>(std::abs(dst_region[1].y - dst_region[0].y)), + }; + const VkViewport viewport{ + .x = static_cast<float>(offset.x), + .y = static_cast<float>(offset.y), + .width = static_cast<float>(extent.width), + .height = static_cast<float>(extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + // TODO: Support scissored blits + const VkRect2D scissor{ + .offset = offset, + .extent = extent, + }; + const float scale_x = static_cast<float>(src_region[1].x - src_region[0].x); + const float scale_y = static_cast<float>(src_region[1].y - src_region[0].y); + const PushConstants push_constants{ + .tex_scale = {scale_x, scale_y}, + .tex_offset = {static_cast<float>(src_region[0].x), static_cast<float>(src_region[0].y)}, + }; + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants); +} + +} // Anonymous namespace + +BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_, + StateTracker& state_tracker_, VKDescriptorPool& descriptor_pool) + : device{device_}, scheduler{scheduler_}, state_tracker{state_tracker_}, + one_texture_set_layout(device.GetLogical().CreateDescriptorSetLayout( + ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), + two_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout( + TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), + one_texture_descriptor_allocator(descriptor_pool, *one_texture_set_layout), + two_textures_descriptor_allocator(descriptor_pool, *two_textures_set_layout), + one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout( + PipelineLayoutCreateInfo(one_texture_set_layout.address()))), + two_textures_pipeline_layout(device.GetLogical().CreatePipelineLayout( + PipelineLayoutCreateInfo(two_textures_set_layout.address()))), + full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), + blit_color_to_color_frag(BuildShader(device, VULKAN_BLIT_COLOR_FLOAT_FRAG_SPV)), + convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), + convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), + linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), + nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) { + if (device.IsExtShaderStencilExportSupported()) { + blit_depth_stencil_frag = BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV); + } +} + +BlitImageHelper::~BlitImageHelper() = default; + +void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) { + const bool is_linear = filter == Tegra::Engines::Fermi2D::Filter::Bilinear; + const BlitImagePipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .operation = operation, + }; + const VkPipelineLayout layout = *one_texture_pipeline_layout; + const VkImageView src_view = src_image_view.Handle(ImageViewType::e2D); + const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler; + const VkPipeline pipeline = FindOrEmplacePipeline(key); + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_view, descriptor_set, + &device = device](vk::CommandBuffer cmdbuf) { + // TODO: Barriers + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, + VkImageView src_depth_view, VkImageView src_stencil_view, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) { + ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point); + ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy); + + const VkPipelineLayout layout = *two_textures_pipeline_layout; + const VkSampler sampler = *nearest_sampler; + const VkPipeline pipeline = BlitDepthStencilPipeline(dst_framebuffer->RenderPass()); + const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit(); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view, + src_stencil_view, descriptor_set, + &device = device](vk::CommandBuffer cmdbuf) { + // TODO: Barriers + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view, + src_stencil_view); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass()); + Convert(*convert_d32_to_r32_pipeline, dst_framebuffer, src_image_view); +} + +void BlitImageHelper::ConvertR32ToD32(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + + ConvertColorToDepthPipeline(convert_r32_to_d32_pipeline, dst_framebuffer->RenderPass()); + Convert(*convert_r32_to_d32_pipeline, dst_framebuffer, src_image_view); +} + +void BlitImageHelper::ConvertD16ToR16(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + ConvertDepthToColorPipeline(convert_d16_to_r16_pipeline, dst_framebuffer->RenderPass()); + Convert(*convert_d16_to_r16_pipeline, dst_framebuffer, src_image_view); +} + +void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + ConvertColorToDepthPipeline(convert_r16_to_d16_pipeline, dst_framebuffer->RenderPass()); + Convert(*convert_r16_to_d16_pipeline, dst_framebuffer, src_image_view); +} + +void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + const VkPipelineLayout layout = *one_texture_pipeline_layout; + const VkImageView src_view = src_image_view.Handle(ImageViewType::e2D); + const VkSampler sampler = *nearest_sampler; + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + const VkExtent2D extent{ + .width = src_image_view.size.width, + .height = src_image_view.size.height, + }; + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([pipeline, layout, sampler, src_view, descriptor_set, extent, + &device = device](vk::CommandBuffer cmdbuf) { + const VkOffset2D offset{ + .x = 0, + .y = 0, + }; + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast<float>(extent.width), + .height = static_cast<float>(extent.height), + .minDepth = 0.0f, + .maxDepth = 0.0f, + }; + const VkRect2D scissor{ + .offset = offset, + .extent = extent, + }; + const PushConstants push_constants{ + .tex_scale = {viewport.width, viewport.height}, + .tex_offset = {0.0f, 0.0f}, + }; + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); + + // TODO: Barriers + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +VkPipeline BlitImageHelper::FindOrEmplacePipeline(const BlitImagePipelineKey& key) { + const auto it = std::ranges::find(blit_color_keys, key); + if (it != blit_color_keys.end()) { + return *blit_color_pipelines[std::distance(blit_color_keys.begin(), it)]; + } + blit_color_keys.push_back(key); + + const std::array stages = MakeStages(*full_screen_vert, *blit_color_to_color_frag); + const VkPipelineColorBlendAttachmentState blend_attachment{ + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + // TODO: programmable blending + const VkPipelineColorBlendStateCreateInfo color_blend_create_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 1, + .pAttachments = &blend_attachment, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + blit_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend_create_info, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *blit_color_pipelines.back(); +} + +VkPipeline BlitImageHelper::BlitDepthStencilPipeline(VkRenderPass renderpass) { + if (blit_depth_stencil_pipeline) { + return *blit_depth_stencil_pipeline; + } + const std::array stages = MakeStages(*full_screen_vert, *blit_depth_stencil_frag); + blit_depth_stencil_pipeline = device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *two_textures_pipeline_layout, + .renderPass = renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }); + return *blit_depth_stencil_pipeline; +} + +void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + if (pipeline) { + return; + } + const std::array stages = MakeStages(*full_screen_vert, *convert_depth_to_float_frag); + pipeline = device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }); +} + +void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + if (pipeline) { + return; + } + const std::array stages = MakeStages(*full_screen_vert, *convert_float_to_depth_frag); + pipeline = device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h new file mode 100644 index 000000000..43fd3d737 --- /dev/null +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -0,0 +1,96 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <compare> + +#include "video_core/engines/fermi_2d.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/texture_cache/types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +using VideoCommon::Offset2D; + +class Device; +class Framebuffer; +class ImageView; +class StateTracker; +class VKScheduler; + +struct BlitImagePipelineKey { + constexpr auto operator<=>(const BlitImagePipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + Tegra::Engines::Fermi2D::Operation operation; +}; + +class BlitImageHelper { +public: + explicit BlitImageHelper(const Device& device, VKScheduler& scheduler, + StateTracker& state_tracker, VKDescriptorPool& descriptor_pool); + ~BlitImageHelper(); + + void BlitColor(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation); + + void BlitDepthStencil(const Framebuffer* dst_framebuffer, VkImageView src_depth_view, + VkImageView src_stencil_view, const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation); + + void ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + + void ConvertR32ToD32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + + void ConvertD16ToR16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + + void ConvertR16ToD16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + +private: + void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, + const ImageView& src_image_view); + + [[nodiscard]] VkPipeline FindOrEmplacePipeline(const BlitImagePipelineKey& key); + + [[nodiscard]] VkPipeline BlitDepthStencilPipeline(VkRenderPass renderpass); + + void ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); + + void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); + + const Device& device; + VKScheduler& scheduler; + StateTracker& state_tracker; + + vk::DescriptorSetLayout one_texture_set_layout; + vk::DescriptorSetLayout two_textures_set_layout; + DescriptorAllocator one_texture_descriptor_allocator; + DescriptorAllocator two_textures_descriptor_allocator; + vk::PipelineLayout one_texture_pipeline_layout; + vk::PipelineLayout two_textures_pipeline_layout; + vk::ShaderModule full_screen_vert; + vk::ShaderModule blit_color_to_color_frag; + vk::ShaderModule blit_depth_stencil_frag; + vk::ShaderModule convert_depth_to_float_frag; + vk::ShaderModule convert_float_to_depth_frag; + vk::Sampler linear_sampler; + vk::Sampler nearest_sampler; + + std::vector<BlitImagePipelineKey> blit_color_keys; + std::vector<vk::Pipeline> blit_color_pipelines; + vk::Pipeline blit_depth_stencil_pipeline; + vk::Pipeline convert_d32_to_r32_pipeline; + vk::Pipeline convert_r32_to_d32_pipeline; + vk::Pipeline convert_d16_to_r16_pipeline; + vk::Pipeline convert_r16_to_d16_pipeline; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 81a39a3b8..362278f01 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -8,17 +8,19 @@ #include <boost/functional/hash.hpp> +#include "common/bit_cast.h" #include "common/cityhash.h" #include "common/common_types.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/vk_state_tracker.h" namespace Vulkan { namespace { -constexpr std::size_t POINT = 0; -constexpr std::size_t LINE = 1; -constexpr std::size_t POLYGON = 2; +constexpr size_t POINT = 0; +constexpr size_t LINE = 1; +constexpr size_t POLYGON = 2; constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { POINT, // Points LINE, // Lines @@ -39,13 +41,17 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { } // Anonymous namespace -void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { - const std::array enabled_lut = {regs.polygon_offset_point_enable, - regs.polygon_offset_line_enable, - regs.polygon_offset_fill_enable}; +void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, + bool has_extended_dynamic_state) { + const Maxwell& regs = maxwell3d.regs; + const std::array enabled_lut{ + regs.polygon_offset_point_enable, + regs.polygon_offset_line_enable, + regs.polygon_offset_fill_enable, + }; const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); - raw = 0; + raw1 = 0; primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0); depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0); depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value()); @@ -58,40 +64,58 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0); logic_op.Assign(PackLogicOp(regs.logic_op.operation)); rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0); + topology.Assign(regs.draw.topology); + msaa_mode.Assign(regs.multisample_mode); - std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast - - for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { - binding_divisors[index] = - regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0; + raw2 = 0; + const auto test_func = + regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; + alpha_test_func.Assign(PackComparisonOp(test_func)); + early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0); + + alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref); + point_size = Common::BitCast<u32>(regs.point_size); + + if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) { + maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false; + for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { + const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index); + binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0; + } } - - for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { - const auto& input = regs.vertex_attrib_format[index]; - auto& attribute = attributes[index]; - attribute.raw = 0; - attribute.enabled.Assign(input.IsConstant() ? 0 : 1); - attribute.buffer.Assign(input.buffer); - attribute.offset.Assign(input.offset); - attribute.type.Assign(static_cast<u32>(input.type.Value())); - attribute.size.Assign(static_cast<u32>(input.size.Value())); + if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) { + maxwell3d.dirty.flags[Dirty::VertexAttributes] = false; + for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { + const auto& input = regs.vertex_attrib_format[index]; + auto& attribute = attributes[index]; + attribute.raw = 0; + attribute.enabled.Assign(input.IsConstant() ? 0 : 1); + attribute.buffer.Assign(input.buffer); + attribute.offset.Assign(input.offset); + attribute.type.Assign(static_cast<u32>(input.type.Value())); + attribute.size.Assign(static_cast<u32>(input.size.Value())); + } } - - for (std::size_t index = 0; index < std::size(attachments); ++index) { - attachments[index].Fill(regs, index); + if (maxwell3d.dirty.flags[Dirty::Blending]) { + maxwell3d.dirty.flags[Dirty::Blending] = false; + for (size_t index = 0; index < attachments.size(); ++index) { + attachments[index].Refresh(regs, index); + } + } + if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) { + maxwell3d.dirty.flags[Dirty::ViewportSwizzles] = false; + const auto& transform = regs.viewport_transform; + std::ranges::transform(transform, viewport_swizzles.begin(), [](const auto& viewport) { + return static_cast<u16>(viewport.swizzle.raw); + }); } - - const auto& transform = regs.viewport_transform; - std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(), - [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); }); - if (!has_extended_dynamic_state) { no_extended_dynamic_state.Assign(1); - dynamic_state.Fill(regs); + dynamic_state.Refresh(regs); } } -void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { +void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) { const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; raw = 0; @@ -130,8 +154,7 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size enable.Assign(1); } -void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { - const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); +void FixedPipelineState::DynamicState::Refresh(const Maxwell& regs) { u32 packed_front_face = PackFrontFace(regs.front_face); if (regs.screen_y_control.triangle_rast_flip != 0) { // Flip front face @@ -161,22 +184,16 @@ void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { depth_test_enable.Assign(regs.depth_test_enable); front_face.Assign(packed_front_face); depth_test_func.Assign(PackComparisonOp(regs.depth_test_func)); - topology.Assign(topology_index); cull_face.Assign(PackCullFace(regs.cull_face)); cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0); - - for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { - const auto& input = regs.vertex_array[index]; - VertexBinding& binding = vertex_bindings[index]; - binding.raw = 0; - binding.enabled.Assign(input.IsEnabled() ? 1 : 0); - binding.stride.Assign(static_cast<u16>(input.stride.Value())); - } + std::ranges::transform(regs.vertex_array, vertex_strides.begin(), [](const auto& array) { + return static_cast<u16>(array.stride.Value()); + }); } -std::size_t FixedPipelineState::Hash() const noexcept { +size_t FixedPipelineState::Hash() const noexcept { const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); - return static_cast<std::size_t>(hash); + return static_cast<size_t>(hash); } bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index cdcbb65f5..a0eb83a68 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -58,7 +58,7 @@ struct FixedPipelineState { BitField<30, 1, u32> enable; }; - void Fill(const Maxwell& regs, std::size_t index); + void Refresh(const Maxwell& regs, size_t index); constexpr std::array<bool, 4> Mask() const noexcept { return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; @@ -106,7 +106,7 @@ struct FixedPipelineState { } }; - template <std::size_t Position> + template <size_t Position> union StencilFace { BitField<Position + 0, 3, u32> action_stencil_fail; BitField<Position + 3, 3, u32> action_depth_fail; @@ -130,12 +130,6 @@ struct FixedPipelineState { } }; - union VertexBinding { - u16 raw; - BitField<0, 12, u16> stride; - BitField<12, 1, u16> enabled; - }; - struct DynamicState { union { u32 raw1; @@ -150,13 +144,13 @@ struct FixedPipelineState { }; union { u32 raw2; - BitField<0, 4, u32> topology; - BitField<4, 2, u32> cull_face; - BitField<6, 1, u32> cull_enable; + BitField<0, 2, u32> cull_face; + BitField<2, 1, u32> cull_enable; }; - std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings; + // Vertex stride is a 12 bits value, we have 4 bits to spare per element + std::array<u16, Maxwell::NumVertexArrays> vertex_strides; - void Fill(const Maxwell& regs); + void Refresh(const Maxwell& regs); Maxwell::ComparisonOp DepthTestFunc() const noexcept { return UnpackComparisonOp(depth_test_func); @@ -169,14 +163,10 @@ struct FixedPipelineState { Maxwell::FrontFace FrontFace() const noexcept { return UnpackFrontFace(front_face.Value()); } - - constexpr Maxwell::PrimitiveTopology Topology() const noexcept { - return static_cast<Maxwell::PrimitiveTopology>(topology.Value()); - } }; union { - u32 raw; + u32 raw1; BitField<0, 1, u32> no_extended_dynamic_state; BitField<2, 1, u32> primitive_restart_enable; BitField<3, 1, u32> depth_bias_enable; @@ -190,7 +180,16 @@ struct FixedPipelineState { BitField<18, 1, u32> logic_op_enable; BitField<19, 4, u32> logic_op; BitField<23, 1, u32> rasterize_enable; + BitField<24, 4, Maxwell::PrimitiveTopology> topology; + BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; + }; + union { + u32 raw2; + BitField<0, 3, u32> alpha_test_func; + BitField<3, 1, u32> early_z; }; + + u32 alpha_test_ref; u32 point_size; std::array<u32, Maxwell::NumVertexArrays> binding_divisors; std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes; @@ -198,9 +197,9 @@ struct FixedPipelineState { std::array<u16, Maxwell::NumViewports> viewport_swizzles; DynamicState dynamic_state; - void Fill(const Maxwell& regs, bool has_extended_dynamic_state); + void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state); - std::size_t Hash() const noexcept; + size_t Hash() const noexcept; bool operator==(const FixedPipelineState& rhs) const noexcept; @@ -208,8 +207,8 @@ struct FixedPipelineState { return !operator==(rhs); } - std::size_t Size() const noexcept { - const std::size_t total_size = sizeof *this; + size_t Size() const noexcept { + const size_t total_size = sizeof *this; return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState)); } }; @@ -223,7 +222,7 @@ namespace std { template <> struct hash<Vulkan::FixedPipelineState> { - std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { + size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { return k.Hash(); } }; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index f8c77f4fa..19aaf034f 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -9,9 +9,9 @@ #include "common/logging/log.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/surface.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan::MaxwellToVK { @@ -26,7 +26,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter) { case Tegra::Texture::TextureFilter::Linear: return VK_FILTER_LINEAR; } - UNREACHABLE_MSG("Invalid sampler filter={}", static_cast<u32>(filter)); + UNREACHABLE_MSG("Invalid sampler filter={}", filter); return {}; } @@ -43,11 +43,11 @@ VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter case Tegra::Texture::TextureMipmapFilter::Linear: return VK_SAMPLER_MIPMAP_MODE_LINEAR; } - UNREACHABLE_MSG("Invalid sampler mipmap mode={}", static_cast<u32>(mipmap_filter)); + UNREACHABLE_MSG("Invalid sampler mipmap mode={}", mipmap_filter); return {}; } -VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode, +VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode, Tegra::Texture::TextureFilter filter) { switch (wrap_mode) { case Tegra::Texture::WrapMode::Wrap: @@ -78,9 +78,10 @@ VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode w case Tegra::Texture::WrapMode::MirrorOnceBorder: UNIMPLEMENTED(); return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + default: + UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", wrap_mode); + return {}; } - UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode)); - return {}; } VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) { @@ -102,16 +103,15 @@ VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_ case Tegra::Texture::DepthCompareFunc::Always: return VK_COMPARE_OP_ALWAYS; } - UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}", - static_cast<u32>(depth_compare_func)); + UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}", depth_compare_func); return {}; } } // namespace Sampler namespace { - -enum : u32 { Attachable = 1, Storage = 2 }; +constexpr u32 Attachable = 1 << 0; +constexpr u32 Storage = 1 << 1; struct FormatTuple { VkFormat format; ///< Vulkan format @@ -122,7 +122,7 @@ struct FormatTuple { {VK_FORMAT_A8B8G8R8_SINT_PACK32, Attachable | Storage}, // A8B8G8R8_SINT {VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage}, // A8B8G8R8_UINT {VK_FORMAT_R5G6B5_UNORM_PACK16, Attachable}, // R5G6B5_UNORM - {VK_FORMAT_B5G6R5_UNORM_PACK16, Attachable}, // B5G6R5_UNORM + {VK_FORMAT_B5G6R5_UNORM_PACK16}, // B5G6R5_UNORM {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT @@ -163,7 +163,7 @@ struct FormatTuple { {VK_FORMAT_R16G16_UNORM, Attachable | Storage}, // R16G16_UNORM {VK_FORMAT_R16G16_SFLOAT, Attachable | Storage}, // R16G16_FLOAT {VK_FORMAT_UNDEFINED}, // R16G16_UINT - {VK_FORMAT_UNDEFINED}, // R16G16_SINT + {VK_FORMAT_R16G16_SINT, Attachable | Storage}, // R16G16_SINT {VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // R16G16_SNORM {VK_FORMAT_UNDEFINED}, // R32G32B32_FLOAT {VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // A8B8G8R8_SRGB @@ -222,30 +222,36 @@ constexpr bool IsZetaFormat(PixelFormat pixel_format) { } // Anonymous namespace -FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFormat pixel_format) { - ASSERT(static_cast<std::size_t>(pixel_format) < std::size(tex_format_tuples)); - - auto tuple = tex_format_tuples[static_cast<std::size_t>(pixel_format)]; +FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb, + PixelFormat pixel_format) { + ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples)); + FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)]; if (tuple.format == VK_FORMAT_UNDEFINED) { - UNIMPLEMENTED_MSG("Unimplemented texture format with pixel format={}", - static_cast<u32>(pixel_format)); - return {VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true}; + UNIMPLEMENTED_MSG("Unimplemented texture format with pixel format={}", pixel_format); + return FormatInfo{VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true}; } // Use A8B8G8R8_UNORM on hardware that doesn't support ASTC natively if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) { - tuple.format = VideoCore::Surface::IsPixelFormatSRGB(pixel_format) - ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 - : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format); + if (is_srgb) { + tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32; + } else { + tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; + tuple.usage |= Storage; + } } - const bool attachable = tuple.usage & Attachable; - const bool storage = tuple.usage & Storage; + const bool attachable = (tuple.usage & Attachable) != 0; + const bool storage = (tuple.usage & Storage) != 0; - VkFormatFeatureFlags usage; - if (format_type == FormatType::Buffer) { + VkFormatFeatureFlags usage{}; + switch (format_type) { + case FormatType::Buffer: usage = VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT; - } else { + break; + case FormatType::Linear: + case FormatType::Optimal: usage = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT; if (attachable) { @@ -255,6 +261,7 @@ FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFo if (storage) { usage |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; } + break; } return {device.GetSupportedFormat(tuple.format, usage, format_type), attachable, storage}; } @@ -274,11 +281,11 @@ VkShaderStageFlagBits ShaderStage(Tegra::Engines::ShaderType stage) { case Tegra::Engines::ShaderType::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; } - UNIMPLEMENTED_MSG("Unimplemented shader stage={}", static_cast<u32>(stage)); + UNIMPLEMENTED_MSG("Unimplemented shader stage={}", stage); return {}; } -VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device, +VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device, Maxwell::PrimitiveTopology topology) { switch (topology) { case Maxwell::PrimitiveTopology::Points: @@ -298,9 +305,10 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device, return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; case Maxwell::PrimitiveTopology::Patches: return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + default: + UNIMPLEMENTED_MSG("Unimplemented topology={}", topology); + return {}; } - UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology)); - return {}; } VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { @@ -325,6 +333,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R16G16B16A16_UNORM; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::SignedNorm: @@ -347,6 +357,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R16G16B16A16_SNORM; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_SNORM_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::UnsignedScaled: @@ -369,6 +381,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R16G16B16A16_USCALED; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_USCALED_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::SignedScaled: @@ -391,6 +405,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R16G16B16A16_SSCALED; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_SSCALED_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::UnsignedInt: @@ -421,6 +437,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R32G32B32A32_UINT; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_UINT_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::SignedInt: @@ -451,6 +469,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R32G32B32A32_SINT; case Maxwell::VertexAttribute::Size::Size_10_10_10_2: return VK_FORMAT_A2B10G10R10_SINT_PACK32; + default: + break; } break; case Maxwell::VertexAttribute::Type::Float: @@ -471,11 +491,12 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib return VK_FORMAT_R32G32B32_SFLOAT; case Maxwell::VertexAttribute::Size::Size_32_32_32_32: return VK_FORMAT_R32G32B32A32_SFLOAT; + default: + break; } break; } - UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", static_cast<u32>(type), - static_cast<u32>(size)); + UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size); return {}; } @@ -506,24 +527,20 @@ VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) { case Maxwell::ComparisonOp::AlwaysOld: return VK_COMPARE_OP_ALWAYS; } - UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison)); + UNIMPLEMENTED_MSG("Unimplemented comparison op={}", comparison); return {}; } -VkIndexType IndexFormat(const VKDevice& device, Maxwell::IndexFormat index_format) { +VkIndexType IndexFormat(Maxwell::IndexFormat index_format) { switch (index_format) { case Maxwell::IndexFormat::UnsignedByte: - if (!device.IsExtIndexTypeUint8Supported()) { - UNIMPLEMENTED_MSG("Native uint8 indices are not supported on this device"); - return VK_INDEX_TYPE_UINT16; - } return VK_INDEX_TYPE_UINT8_EXT; case Maxwell::IndexFormat::UnsignedShort: return VK_INDEX_TYPE_UINT16; case Maxwell::IndexFormat::UnsignedInt: return VK_INDEX_TYPE_UINT32; } - UNIMPLEMENTED_MSG("Unimplemented index_format={}", static_cast<u32>(index_format)); + UNIMPLEMENTED_MSG("Unimplemented index_format={}", index_format); return {}; } @@ -554,7 +571,7 @@ VkStencilOp StencilOp(Maxwell::StencilOp stencil_op) { case Maxwell::StencilOp::DecrWrapOGL: return VK_STENCIL_OP_DECREMENT_AND_WRAP; } - UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil_op)); + UNIMPLEMENTED_MSG("Unimplemented stencil op={}", stencil_op); return {}; } @@ -576,7 +593,7 @@ VkBlendOp BlendEquation(Maxwell::Blend::Equation equation) { case Maxwell::Blend::Equation::MaxGL: return VK_BLEND_OP_MAX; } - UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation)); + UNIMPLEMENTED_MSG("Unimplemented blend equation={}", equation); return {}; } @@ -640,7 +657,7 @@ VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor) { case Maxwell::Blend::Factor::OneMinusConstantAlphaGL: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; } - UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor)); + UNIMPLEMENTED_MSG("Unimplemented blend factor={}", factor); return {}; } @@ -651,11 +668,11 @@ VkFrontFace FrontFace(Maxwell::FrontFace front_face) { case Maxwell::FrontFace::CounterClockWise: return VK_FRONT_FACE_COUNTER_CLOCKWISE; } - UNIMPLEMENTED_MSG("Unimplemented front face={}", static_cast<u32>(front_face)); + UNIMPLEMENTED_MSG("Unimplemented front face={}", front_face); return {}; } -VkCullModeFlags CullFace(Maxwell::CullFace cull_face) { +VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face) { switch (cull_face) { case Maxwell::CullFace::Front: return VK_CULL_MODE_FRONT_BIT; @@ -664,7 +681,7 @@ VkCullModeFlags CullFace(Maxwell::CullFace cull_face) { case Maxwell::CullFace::FrontAndBack: return VK_CULL_MODE_FRONT_AND_BACK; } - UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face)); + UNIMPLEMENTED_MSG("Unimplemented cull face={}", cull_face); return {}; } @@ -684,7 +701,7 @@ VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) { case Tegra::Texture::SwizzleSource::OneFloat: return VK_COMPONENT_SWIZZLE_ONE; } - UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(swizzle)); + UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", swizzle); return {}; } @@ -707,8 +724,21 @@ VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) case Maxwell::ViewportSwizzle::NegativeW: return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV; } - UNREACHABLE_MSG("Invalid swizzle={}", static_cast<int>(swizzle)); + UNREACHABLE_MSG("Invalid swizzle={}", swizzle); return {}; } +VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction) { + switch (reduction) { + case Tegra::Texture::SamplerReduction::WeightedAverage: + return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; + case Tegra::Texture::SamplerReduction::Min: + return VK_SAMPLER_REDUCTION_MODE_MIN_EXT; + case Tegra::Texture::SamplerReduction::Max: + return VK_SAMPLER_REDUCTION_MODE_MAX_EXT; + } + UNREACHABLE_MSG("Invalid sampler mode={}", static_cast<int>(reduction)); + return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; +} + } // namespace Vulkan::MaxwellToVK diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h index 7e213452f..e3e06ba38 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.h +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h @@ -6,10 +6,10 @@ #include "common/common_types.h" #include "video_core/engines/maxwell_3d.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/surface.h" #include "video_core/textures/texture.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan::MaxwellToVK { @@ -22,7 +22,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter); VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter); -VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode, +VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode, Tegra::Texture::TextureFilter filter); VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func); @@ -35,17 +35,25 @@ struct FormatInfo { bool storage; }; -FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFormat pixel_format); +/** + * Returns format properties supported in the host + * @param device Host device + * @param format_type Type of image the buffer will use + * @param with_srgb True when the format can be sRGB when converted to another format (ASTC) + * @param pixel_format Guest pixel format to describe + */ +[[nodiscard]] FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb, + PixelFormat pixel_format); VkShaderStageFlagBits ShaderStage(Tegra::Engines::ShaderType stage); -VkPrimitiveTopology PrimitiveTopology(const VKDevice& device, Maxwell::PrimitiveTopology topology); +VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology); VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size); VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison); -VkIndexType IndexFormat(const VKDevice& device, Maxwell::IndexFormat index_format); +VkIndexType IndexFormat(Maxwell::IndexFormat index_format); VkStencilOp StencilOp(Maxwell::StencilOp stencil_op); @@ -55,10 +63,12 @@ VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor); VkFrontFace FrontFace(Maxwell::FrontFace front_face); -VkCullModeFlags CullFace(Maxwell::CullFace cull_face); +VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face); VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle); VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle); +VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction); + } // namespace Vulkan::MaxwellToVK diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index ae46e0444..1cc720ddd 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -12,8 +12,6 @@ #include <fmt/format.h> -#include "common/dynamic_library.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "common/telemetry.h" #include "core/core.h" @@ -24,179 +22,27 @@ #include "video_core/gpu.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_blit_screen.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" #include "video_core/renderer_vulkan/vk_swapchain.h" -#include "video_core/renderer_vulkan/wrapper.h" - -// Include these late to avoid polluting previous headers -#ifdef _WIN32 -#include <windows.h> -// ensure include order -#include <vulkan/vulkan_win32.h> -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) -#include <X11/Xlib.h> -#include <vulkan/vulkan_wayland.h> -#include <vulkan/vulkan_xlib.h> -#endif +#include "video_core/vulkan_common/vulkan_debug_callback.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_library.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_surface.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { - namespace { - -using Core::Frontend::WindowSystemType; - -VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, - VkDebugUtilsMessageTypeFlagsEXT type, - const VkDebugUtilsMessengerCallbackDataEXT* data, - [[maybe_unused]] void* user_data) { - const char* message{data->pMessage}; - - if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { - LOG_CRITICAL(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { - LOG_WARNING(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { - LOG_INFO(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { - LOG_DEBUG(Render_Vulkan, "{}", message); - } - return VK_FALSE; -} - -Common::DynamicLibrary OpenVulkanLibrary() { - Common::DynamicLibrary library; -#ifdef __APPLE__ - // Check if a path to a specific Vulkan library has been specified. - char* libvulkan_env = getenv("LIBVULKAN_PATH"); - if (!libvulkan_env || !library.Open(libvulkan_env)) { - // Use the libvulkan.dylib from the application bundle. - const std::string filename = - Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; - library.Open(filename.c_str()); - } -#else - std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); - if (!library.Open(filename.c_str())) { - // Android devices may not have libvulkan.so.1, only libvulkan.so. - filename = Common::DynamicLibrary::GetVersionedFilename("vulkan"); - library.Open(filename.c_str()); - } -#endif - return library; -} - -vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatch& dld, - WindowSystemType window_type = WindowSystemType::Headless, - bool enable_layers = false) { - if (!library.IsOpen()) { - LOG_ERROR(Render_Vulkan, "Vulkan library not available"); - return {}; - } - if (!library.GetSymbol("vkGetInstanceProcAddr", &dld.vkGetInstanceProcAddr)) { - LOG_ERROR(Render_Vulkan, "vkGetInstanceProcAddr not present in Vulkan"); - return {}; - } - if (!vk::Load(dld)) { - LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers"); - return {}; - } - - std::vector<const char*> extensions; - extensions.reserve(6); - switch (window_type) { - case Core::Frontend::WindowSystemType::Headless: - break; -#ifdef _WIN32 - case Core::Frontend::WindowSystemType::Windows: - extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); - break; -#endif -#if !defined(_WIN32) && !defined(__APPLE__) - case Core::Frontend::WindowSystemType::X11: - extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); - break; - case Core::Frontend::WindowSystemType::Wayland: - extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); - break; -#endif - default: - LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); - break; - } - if (window_type != Core::Frontend::WindowSystemType::Headless) { - extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - } - if (enable_layers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - - const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld); - if (!properties) { - LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); - return {}; - } - - for (const char* extension : extensions) { - const auto it = - std::find_if(properties->begin(), properties->end(), [extension](const auto& prop) { - return !std::strcmp(extension, prop.extensionName); - }); - if (it == properties->end()) { - LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension); - return {}; - } - } - - std::vector<const char*> layers; - layers.reserve(1); - if (enable_layers) { - layers.push_back("VK_LAYER_KHRONOS_validation"); - } - - const std::optional layer_properties = vk::EnumerateInstanceLayerProperties(dld); - if (!layer_properties) { - LOG_ERROR(Render_Vulkan, "Failed to query layer properties, disabling layers"); - layers.clear(); - } - - for (auto layer_it = layers.begin(); layer_it != layers.end();) { - const char* const layer = *layer_it; - const auto it = std::find_if( - layer_properties->begin(), layer_properties->end(), - [layer](const VkLayerProperties& prop) { return !std::strcmp(layer, prop.layerName); }); - if (it == layer_properties->end()) { - LOG_ERROR(Render_Vulkan, "Layer {} not available, removing it", layer); - layer_it = layers.erase(layer_it); - } else { - ++layer_it; - } - } - - vk::Instance instance = vk::Instance::Create(layers, extensions, dld); - if (!instance) { - LOG_ERROR(Render_Vulkan, "Failed to create Vulkan instance"); - return {}; - } - if (!vk::Load(*instance, dld)) { - LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers"); - } - return instance; -} - std::string GetReadableVersion(u32 version) { return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), VK_VERSION_PATCH(version)); } -std::string GetDriverVersion(const VKDevice& device) { +std::string GetDriverVersion(const Device& device) { // Extracted from // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314 const u32 version = device.GetDriverVersion(); @@ -213,7 +59,6 @@ std::string GetDriverVersion(const VKDevice& device) { const u32 minor = version & 0x3fff; return fmt::format("{}.{}", major, minor); } - return GetReadableVersion(version); } @@ -235,213 +80,98 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext return separated_extensions; } +Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, + VkSurfaceKHR surface) { + const std::vector<VkPhysicalDevice> devices = instance.EnumeratePhysicalDevices(); + const s32 device_index = Settings::values.vulkan_device.GetValue(); + if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) { + LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + const vk::PhysicalDevice physical_device(devices[device_index], dld); + return Device(*instance, physical_device, surface, dld); +} } // Anonymous namespace -RendererVulkan::RendererVulkan(Core::System& system_, Core::Frontend::EmuWindow& emu_window, - Tegra::GPU& gpu_, - std::unique_ptr<Core::Frontend::GraphicsContext> context) - : RendererBase{emu_window, std::move(context)}, system{system_}, gpu{gpu_} {} +RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, + Core::Frontend::EmuWindow& emu_window, + Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, + std::unique_ptr<Core::Frontend::GraphicsContext> context_) try + : RendererBase(emu_window, std::move(context_)), + telemetry_session(telemetry_session_), + cpu_memory(cpu_memory_), + gpu(gpu_), + library(OpenLibrary()), + instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, + true, Settings::values.renderer_debug)), + debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), + surface(CreateSurface(instance, render_window)), + device(CreateDevice(instance, dld, *surface)), + memory_allocator(device, false), + state_tracker(gpu), + scheduler(device, state_tracker), + swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, + render_window.GetFramebufferLayout().height, false), + blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, + screen_info), + rasterizer(render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, device, + memory_allocator, state_tracker, scheduler) { + Report(); +} catch (const vk::Exception& exception) { + LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what()); + throw std::runtime_error{fmt::format("Vulkan initialization error {}", exception.what())}; +} RendererVulkan::~RendererVulkan() { - ShutDown(); + void(device.GetLogical().WaitIdle()); } void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { - render_window.PollEvents(); - if (!framebuffer) { return; } - const auto& layout = render_window.GetFramebufferLayout(); if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) { const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; const bool use_accelerated = - rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); + rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); const bool is_srgb = use_accelerated && screen_info.is_srgb; - if (swapchain->HasFramebufferChanged(layout) || swapchain->GetSrgbState() != is_srgb) { - swapchain->Create(layout.width, layout.height, is_srgb); - blit_screen->Recreate(); - } - - scheduler->WaitWorker(); - - swapchain->AcquireNextImage(); - const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated); - - scheduler->Flush(false, render_semaphore); - - if (swapchain->Present(render_semaphore, fence)) { - blit_screen->Recreate(); + if (swapchain.HasFramebufferChanged(layout) || swapchain.GetSrgbState() != is_srgb) { + swapchain.Create(layout.width, layout.height, is_srgb); + blit_screen.Recreate(); } - rasterizer->TickFrame(); - } - - render_window.PollEvents(); -} - -bool RendererVulkan::TryPresent(int /*timeout_ms*/) { - // TODO (bunnei): ImplementMe - return true; -} - -bool RendererVulkan::Init() { - library = OpenVulkanLibrary(); - instance = CreateInstance(library, dld, render_window.GetWindowInfo().type, - Settings::values.renderer_debug); - if (!instance || !CreateDebugCallback() || !CreateSurface() || !PickDevices()) { - return false; - } - - Report(); - - memory_manager = std::make_unique<VKMemoryManager>(*device); - - resource_manager = std::make_unique<VKResourceManager>(*device); - - const auto& framebuffer = render_window.GetFramebufferLayout(); - swapchain = std::make_unique<VKSwapchain>(*surface, *device); - swapchain->Create(framebuffer.width, framebuffer.height, false); - - state_tracker = std::make_unique<StateTracker>(system); - - scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker); - - rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device, - *resource_manager, *memory_manager, - *state_tracker, *scheduler); - - blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device, - *resource_manager, *memory_manager, *swapchain, - *scheduler, screen_info); - - return true; -} - -void RendererVulkan::ShutDown() { - if (!device) { - return; - } - if (const auto& dev = device->GetLogical()) { - dev.WaitIdle(); - } - - rasterizer.reset(); - blit_screen.reset(); - scheduler.reset(); - swapchain.reset(); - memory_manager.reset(); - resource_manager.reset(); - device.reset(); -} + scheduler.WaitWorker(); -bool RendererVulkan::CreateDebugCallback() { - if (!Settings::values.renderer_debug) { - return true; - } - debug_callback = instance.TryCreateDebugCallback(DebugCallback); - if (!debug_callback) { - LOG_ERROR(Render_Vulkan, "Failed to create debug callback"); - return false; - } - return true; -} + swapchain.AcquireNextImage(); + const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); -bool RendererVulkan::CreateSurface() { - [[maybe_unused]] const auto& window_info = render_window.GetWindowInfo(); - VkSurfaceKHR unsafe_surface = nullptr; + scheduler.Flush(render_semaphore); -#ifdef _WIN32 - if (window_info.type == Core::Frontend::WindowSystemType::Windows) { - const HWND hWnd = static_cast<HWND>(window_info.render_surface); - const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, - nullptr, 0, nullptr, hWnd}; - const auto vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>( - dld.vkGetInstanceProcAddr(*instance, "vkCreateWin32SurfaceKHR")); - if (!vkCreateWin32SurfaceKHR || - vkCreateWin32SurfaceKHR(*instance, &win32_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { - LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface"); - return false; + if (swapchain.Present(render_semaphore)) { + blit_screen.Recreate(); } - } -#endif -#if !defined(_WIN32) && !defined(__APPLE__) - if (window_info.type == Core::Frontend::WindowSystemType::X11) { - const VkXlibSurfaceCreateInfoKHR xlib_ci{ - VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0, - static_cast<Display*>(window_info.display_connection), - reinterpret_cast<Window>(window_info.render_surface)}; - const auto vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>( - dld.vkGetInstanceProcAddr(*instance, "vkCreateXlibSurfaceKHR")); - if (!vkCreateXlibSurfaceKHR || - vkCreateXlibSurfaceKHR(*instance, &xlib_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { - LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface"); - return false; - } - } - if (window_info.type == Core::Frontend::WindowSystemType::Wayland) { - const VkWaylandSurfaceCreateInfoKHR wayland_ci{ - VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, - static_cast<wl_display*>(window_info.display_connection), - static_cast<wl_surface*>(window_info.render_surface)}; - const auto vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>( - dld.vkGetInstanceProcAddr(*instance, "vkCreateWaylandSurfaceKHR")); - if (!vkCreateWaylandSurfaceKHR || - vkCreateWaylandSurfaceKHR(*instance, &wayland_ci, nullptr, &unsafe_surface) != - VK_SUCCESS) { - LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface"); - return false; - } - } -#endif - if (!unsafe_surface) { - LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); - return false; + rasterizer.TickFrame(); } - surface = vk::SurfaceKHR(unsafe_surface, *instance, dld); - return true; -} - -bool RendererVulkan::PickDevices() { - const auto devices = instance.EnumeratePhysicalDevices(); - if (!devices) { - LOG_ERROR(Render_Vulkan, "Failed to enumerate physical devices"); - return false; - } - - const s32 device_index = Settings::values.vulkan_device.GetValue(); - if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) { - LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index); - return false; - } - const vk::PhysicalDevice physical_device((*devices)[static_cast<std::size_t>(device_index)], - dld); - if (!VKDevice::IsSuitable(physical_device, *surface)) { - return false; - } - - device = std::make_unique<VKDevice>(*instance, physical_device, *surface, dld); - return device->Create(); + render_window.OnFrameDisplayed(); } void RendererVulkan::Report() const { - const std::string vendor_name{device->GetVendorName()}; - const std::string model_name{device->GetModelName()}; - const std::string driver_version = GetDriverVersion(*device); + const std::string vendor_name{device.GetVendorName()}; + const std::string model_name{device.GetModelName()}; + const std::string driver_version = GetDriverVersion(device); const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version); - const std::string api_version = GetReadableVersion(device->GetApiVersion()); + const std::string api_version = GetReadableVersion(device.ApiVersion()); - const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions()); + const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions()); LOG_INFO(Render_Vulkan, "Driver: {}", driver_name); LOG_INFO(Render_Vulkan, "Device: {}", model_name); LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version); - auto& telemetry_session = system.TelemetrySession(); - constexpr auto field = Common::Telemetry::FieldType::UserSystem; + static constexpr auto field = Common::Telemetry::FieldType::UserSystem; telemetry_session.AddField(field, "GPU_Vendor", vendor_name); telemetry_session.AddField(field, "GPU_Model", model_name); telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name); @@ -449,25 +179,4 @@ void RendererVulkan::Report() const { telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); } -std::vector<std::string> RendererVulkan::EnumerateDevices() { - vk::InstanceDispatch dld; - Common::DynamicLibrary library = OpenVulkanLibrary(); - vk::Instance instance = CreateInstance(library, dld); - if (!instance) { - return {}; - } - - const std::optional physical_devices = instance.EnumeratePhysicalDevices(); - if (!physical_devices) { - return {}; - } - - std::vector<std::string> names; - names.reserve(physical_devices->size()); - for (const auto& device : *physical_devices) { - names.push_back(vk::PhysicalDevice(device, dld).GetProperties().deviceName); - } - return names; -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 13debbbc0..72071316c 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -9,75 +9,67 @@ #include <vector> #include "common/dynamic_library.h" - #include "video_core/renderer_base.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_blit_screen.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_state_tracker.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Core { -class System; +class TelemetrySession; } -namespace Vulkan { +namespace Core::Memory { +class Memory; +} -class StateTracker; -class VKBlitScreen; -class VKDevice; -class VKFence; -class VKMemoryManager; -class VKResourceManager; -class VKSwapchain; -class VKScheduler; -class VKImage; - -struct VKScreenInfo { - VKImage* image{}; - u32 width{}; - u32 height{}; - bool is_srgb{}; -}; +namespace Tegra { +class GPU; +} + +namespace Vulkan { class RendererVulkan final : public VideoCore::RendererBase { public: - explicit RendererVulkan(Core::System& system, Core::Frontend::EmuWindow& emu_window, - Tegra::GPU& gpu, - std::unique_ptr<Core::Frontend::GraphicsContext> context); + explicit RendererVulkan(Core::TelemetrySession& telemtry_session, + Core::Frontend::EmuWindow& emu_window, + Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, + std::unique_ptr<Core::Frontend::GraphicsContext> context_); ~RendererVulkan() override; - bool Init() override; - void ShutDown() override; void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - bool TryPresent(int timeout_ms) override; - static std::vector<std::string> EnumerateDevices(); + VideoCore::RasterizerInterface* ReadRasterizer() override { + return &rasterizer; + } private: - bool CreateDebugCallback(); - - bool CreateSurface(); - - bool PickDevices(); - void Report() const; - Core::System& system; + Core::TelemetrySession& telemetry_session; + Core::Memory::Memory& cpu_memory; Tegra::GPU& gpu; Common::DynamicLibrary library; vk::InstanceDispatch dld; vk::Instance instance; + vk::DebugUtilsMessenger debug_callback; vk::SurfaceKHR surface; VKScreenInfo screen_info; - vk::DebugCallback debug_callback; - std::unique_ptr<VKDevice> device; - std::unique_ptr<VKSwapchain> swapchain; - std::unique_ptr<VKMemoryManager> memory_manager; - std::unique_ptr<VKResourceManager> resource_manager; - std::unique_ptr<StateTracker> state_tracker; - std::unique_ptr<VKScheduler> scheduler; - std::unique_ptr<VKBlitScreen> blit_screen; + Device device; + MemoryAllocator memory_allocator; + StateTracker state_tracker; + VKScheduler scheduler; + VKSwapchain swapchain; + VKBlitScreen blit_screen; + RasterizerVulkan rasterizer; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/shaders/quad_array.comp b/src/video_core/renderer_vulkan/shaders/quad_array.comp deleted file mode 100644 index 5a5703308..000000000 --- a/src/video_core/renderer_vulkan/shaders/quad_array.comp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -/* - * Build instructions: - * $ glslangValidator -V $THIS_FILE -o output.spv - * $ spirv-opt -O --strip-debug output.spv -o optimized.spv - * $ xxd -i optimized.spv - * - * Then copy that bytecode to the C++ file - */ - -#version 460 core - -layout (local_size_x = 1024) in; - -layout (std430, set = 0, binding = 0) buffer OutputBuffer { - uint output_indexes[]; -}; - -layout (push_constant) uniform PushConstants { - uint first; -}; - -void main() { - uint primitive = gl_GlobalInvocationID.x; - if (primitive * 6 >= output_indexes.length()) { - return; - } - - const uint quad_map[6] = uint[](0, 1, 2, 0, 2, 3); - for (uint vertex = 0; vertex < 6; ++vertex) { - uint index = first + primitive * 4 + quad_map[vertex]; - output_indexes[primitive * 6 + vertex] = index; - } -} diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index a551e3de8..a1a32aabe 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -12,127 +12,28 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/math_util.h" - #include "core/core.h" #include "core/frontend/emu_window.h" #include "core/memory.h" - #include "video_core/gpu.h" -#include "video_core/morton.h" -#include "video_core/rasterizer_interface.h" +#include "video_core/host_shaders/vulkan_present_frag_spv.h" +#include "video_core/host_shaders/vulkan_present_vert_spv.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_blit_screen.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_image.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_swapchain.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/surface.h" +#include "video_core/textures/decoders.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { namespace { -// Generated from the "shaders/" directory, read the instructions there. -constexpr u8 blit_vertex_code[] = { - 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, - 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, - 0x38, 0x00, 0x01, 0x00}; - -constexpr u8 blit_fragment_code[] = { - 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; - struct ScreenRectVertex { ScreenRectVertex() = default; explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {} @@ -175,9 +76,9 @@ constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { // clang-format on } -std::size_t GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { +u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { using namespace VideoCore::Surface; - return GetBytesPerPixel(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); + return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); } std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { @@ -210,17 +111,14 @@ struct VKBlitScreen::BufferData { // Unaligned image data goes here }; -VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window, - VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - VKSwapchain& swapchain, VKScheduler& scheduler, - const VKScreenInfo& screen_info) - : system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device}, - resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain}, - scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} { - watches.resize(image_count); - std::generate(watches.begin(), watches.end(), - []() { return std::make_unique<VKFenceWatch>(); }); +VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_, + Core::Frontend::EmuWindow& render_window_, const Device& device_, + MemoryAllocator& memory_allocator_, VKSwapchain& swapchain_, + VKScheduler& scheduler_, const VKScreenInfo& screen_info_) + : cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_}, + memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_}, + image_count{swapchain.GetImageCount()}, screen_info{screen_info_} { + resource_ticks.resize(image_count); CreateStaticResources(); CreateDynamicResources(); @@ -232,44 +130,40 @@ void VKBlitScreen::Recreate() { CreateDynamicResources(); } -std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, - bool use_accelerated) { +VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) { RefreshResources(framebuffer); // Finish any pending renderpass scheduler.RequestOutsideRenderPassOperationContext(); const std::size_t image_index = swapchain.GetImageIndex(); - watches[image_index]->Watch(scheduler.GetFence()); - VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get(); + scheduler.Wait(resource_ticks[image_index]); + resource_ticks[image_index] = scheduler.CurrentTick(); - UpdateDescriptorSet(image_index, blit_image->GetPresentView()); + UpdateDescriptorSet(image_index, + use_accelerated ? screen_info.image_view : *raw_image_views[image_index]); BufferData data; SetUniformData(data, framebuffer); SetVertexData(data, framebuffer); - auto map = buffer_commit->Map(); - std::memcpy(map.GetAddress(), &data, sizeof(data)); + const std::span<u8> mapped_span = buffer_commit.Map(); + std::memcpy(mapped_span.data(), &data, sizeof(data)); if (!use_accelerated) { const u64 image_offset = GetRawImageOffset(framebuffer, image_index); - const auto pixel_format = - VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format); const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset; - const auto host_ptr = system.Memory().GetPointer(framebuffer_addr); - rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer)); + const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr); + const size_t size_bytes = GetSizeInBytes(framebuffer); // TODO(Rodrigo): Read this from HLE constexpr u32 block_height_log2 = 4; - VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format, - framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1, - map.GetAddress() + image_offset, host_ptr); - - blit_image->Transition(0, 1, 0, 1, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer); + Tegra::Texture::UnswizzleTexture( + mapped_span.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes), + bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0); const VkBufferImageCopy copy{ .bufferOffset = image_offset, @@ -291,15 +185,42 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon }, }; scheduler.Record( - [buffer = *buffer, image = *blit_image->GetHandle(), copy](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy); + [buffer = *buffer, image = *raw_images[image_index], copy](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier base_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + VkImageMemoryBarrier read_barrier = base_barrier; + read_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImageMemoryBarrier write_barrier = base_barrier; + write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, read_barrier); + cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_GENERAL, copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); }); } - map.Release(); - - blit_image->Transition(0, 1, 0, 1, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index], descriptor_set = descriptor_sets[image_index], buffer = *buffer, size = swapchain.GetSize(), pipeline = *pipeline, @@ -307,31 +228,31 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon const VkClearValue clear_color{ .color = {.float32 = {0.0f, 0.0f, 0.0f, 0.0f}}, }; - - VkRenderPassBeginInfo renderpass_bi; - renderpass_bi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderpass_bi.pNext = nullptr; - renderpass_bi.renderPass = renderpass; - renderpass_bi.framebuffer = framebuffer; - renderpass_bi.renderArea.offset.x = 0; - renderpass_bi.renderArea.offset.y = 0; - renderpass_bi.renderArea.extent = size; - renderpass_bi.clearValueCount = 1; - renderpass_bi.pClearValues = &clear_color; - - VkViewport viewport; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast<float>(size.width); - viewport.height = static_cast<float>(size.height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor; - scissor.offset.x = 0; - scissor.offset.y = 0; - scissor.extent = size; - + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = renderpass, + .framebuffer = framebuffer, + .renderArea = + { + .offset = {0, 0}, + .extent = size, + }, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast<float>(size.width), + .height = static_cast<float>(size.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const VkRect2D scissor{ + .offset = {0, 0}, + .extent = size, + }; cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); cmdbuf.SetViewport(0, viewport); @@ -342,8 +263,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon cmdbuf.Draw(4, 1, 0, 0); cmdbuf.EndRenderPass(); }); - - return {scheduler.GetFence(), *semaphores[image_index]}; + return *semaphores[image_index]; } void VKBlitScreen::CreateStaticResources() { @@ -375,8 +295,8 @@ void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) } void VKBlitScreen::CreateShaders() { - vertex_shader = BuildShader(device, sizeof(blit_vertex_code), blit_vertex_code); - fragment_shader = BuildShader(device, sizeof(blit_fragment_code), blit_fragment_code); + vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV); + fragment_shader = BuildShader(device, VULKAN_PRESENT_FRAG_SPV); } void VKBlitScreen::CreateSemaphores() { @@ -423,7 +343,7 @@ void VKBlitScreen::CreateRenderPass() { const VkAttachmentReference color_attachment_ref{ .attachment = 0, - .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .layout = VK_IMAGE_LAYOUT_GENERAL, }; const VkSubpassDescription subpass_description{ @@ -711,12 +631,12 @@ void VKBlitScreen::CreateFramebuffers() { void VKBlitScreen::ReleaseRawImages() { for (std::size_t i = 0; i < raw_images.size(); ++i) { - watches[i]->Wait(); + scheduler.Wait(resource_ticks.at(i)); } raw_images.clear(); raw_buffer_commits.clear(); buffer.reset(); - buffer_commit.reset(); + buffer_commit = MemoryCommit{}; } void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { @@ -733,39 +653,61 @@ void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuff }; buffer = device.GetLogical().CreateBuffer(ci); - buffer_commit = memory_manager.Commit(buffer, true); + buffer_commit = memory_allocator.Commit(buffer, MemoryUsage::Upload); } void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { raw_images.resize(image_count); + raw_image_views.resize(image_count); raw_buffer_commits.resize(image_count); - const VkImageCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .imageType = VK_IMAGE_TYPE_2D, - .format = GetFormat(framebuffer), - .extent = - { - .width = framebuffer.width, - .height = framebuffer.height, - .depth = 1, - }, - .mipLevels = 1, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_LINEAR, - .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - }; - - for (std::size_t i = 0; i < image_count; ++i) { - raw_images[i] = std::make_unique<VKImage>(device, scheduler, ci, VK_IMAGE_ASPECT_COLOR_BIT); - raw_buffer_commits[i] = memory_manager.Commit(raw_images[i]->GetHandle(), false); + for (size_t i = 0; i < image_count; ++i) { + raw_images[i] = device.GetLogical().CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = GetFormat(framebuffer), + .extent = + { + .width = framebuffer.width, + .height = framebuffer.height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_LINEAR, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + raw_buffer_commits[i] = memory_allocator.Commit(raw_images[i], MemoryUsage::DeviceLocal); + raw_image_views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *raw_images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = GetFormat(framebuffer), + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); } } @@ -792,7 +734,7 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag const VkDescriptorImageInfo image_info{ .sampler = *sampler, .imageView = image_view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; const VkWriteDescriptorSet sampler_write{ diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index 243640fab..5e3177685 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.h +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -5,16 +5,18 @@ #pragma once #include <memory> -#include <tuple> -#include "video_core/renderer_vulkan/vk_memory_manager.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Core { class System; } +namespace Core::Memory { +class Memory; +} + namespace Core::Frontend { class EmuWindow; } @@ -30,26 +32,31 @@ class RasterizerInterface; namespace Vulkan { struct ScreenInfo; + +class Device; class RasterizerVulkan; -class VKDevice; -class VKFence; -class VKImage; class VKScheduler; class VKSwapchain; -class VKBlitScreen final { +struct VKScreenInfo { + VkImageView image_view{}; + u32 width{}; + u32 height{}; + bool is_srgb{}; +}; + +class VKBlitScreen { public: - explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window, - VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - VKSwapchain& swapchain, VKScheduler& scheduler, - const VKScreenInfo& screen_info); + explicit VKBlitScreen(Core::Memory::Memory& cpu_memory, + Core::Frontend::EmuWindow& render_window, const Device& device, + MemoryAllocator& memory_manager, VKSwapchain& swapchain, + VKScheduler& scheduler, const VKScreenInfo& screen_info); ~VKBlitScreen(); void Recreate(); - std::tuple<VKFence&, VkSemaphore> Draw(const Tegra::FramebufferConfig& framebuffer, - bool use_accelerated); + [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated); private: struct BufferData; @@ -81,12 +88,10 @@ private: u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, std::size_t image_index) const; - Core::System& system; + Core::Memory::Memory& cpu_memory; Core::Frontend::EmuWindow& render_window; - VideoCore::RasterizerInterface& rasterizer; - const VKDevice& device; - VKResourceManager& resource_manager; - VKMemoryManager& memory_manager; + const Device& device; + MemoryAllocator& memory_allocator; VKSwapchain& swapchain; VKScheduler& scheduler; const std::size_t image_count; @@ -104,13 +109,14 @@ private: vk::Sampler sampler; vk::Buffer buffer; - VKMemoryCommit buffer_commit; + MemoryCommit buffer_commit; - std::vector<std::unique_ptr<VKFenceWatch>> watches; + std::vector<u64> resource_ticks; std::vector<vk::Semaphore> semaphores; - std::vector<std::unique_ptr<VKImage>> raw_images; - std::vector<VKMemoryCommit> raw_buffer_commits; + std::vector<vk::Image> raw_images; + std::vector<vk::ImageView> raw_image_views; + std::vector<MemoryCommit> raw_buffer_commits; u32 raw_width = 0; u32 raw_height = 0; }; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 1d2f8b557..848eedd66 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -3,172 +3,308 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <cstring> -#include <memory> +#include <span> +#include <vector> -#include "core/core.h" #include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { - namespace { +VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) { + return VkBufferCopy{ + .srcOffset = copy.src_offset, + .dstOffset = copy.dst_offset, + .size = copy.size, + }; +} -constexpr VkBufferUsageFlags BUFFER_USAGE = - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - -constexpr VkPipelineStageFlags UPLOAD_PIPELINE_STAGE = - VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - -constexpr VkAccessFlags UPLOAD_ACCESS_BARRIERS = - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | - VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT; +VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) { + if (num_elements <= 0xff && device.IsExtIndexTypeUint8Supported()) { + return VK_INDEX_TYPE_UINT8_EXT; + } + if (num_elements <= 0xffff) { + return VK_INDEX_TYPE_UINT16; + } + return VK_INDEX_TYPE_UINT32; +} -std::unique_ptr<VKStreamBuffer> CreateStreamBuffer(const VKDevice& device, VKScheduler& scheduler) { - return std::make_unique<VKStreamBuffer>(device, scheduler, BUFFER_USAGE); +size_t BytesPerIndex(VkIndexType index_type) { + switch (index_type) { + case VK_INDEX_TYPE_UINT8_EXT: + return 1; + case VK_INDEX_TYPE_UINT16: + return 2; + case VK_INDEX_TYPE_UINT32: + return 4; + default: + UNREACHABLE_MSG("Invalid index type={}", index_type); + return 1; + } } +template <typename T> +std::array<T, 6> MakeQuadIndices(u32 quad, u32 first) { + std::array<T, 6> indices{0, 1, 2, 0, 2, 3}; + std::ranges::transform(indices, indices.begin(), + [quad, first](u32 index) { return first + index + quad * 4; }); + return indices; +} } // Anonymous namespace -Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler_, - VKStagingBufferPool& staging_pool_, VAddr cpu_addr, std::size_t size) - : BufferBlock{cpu_addr, size}, scheduler{scheduler_}, staging_pool{staging_pool_} { - const VkBufferCreateInfo ci{ +Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) + : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} + +Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, + VAddr cpu_addr_, u64 size_bytes_) + : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_) { + buffer = runtime.device.GetLogical().CreateBuffer(VkBufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .size = static_cast<VkDeviceSize>(size), - .usage = BUFFER_USAGE | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .size = SizeBytes(), + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, - }; + }); + if (runtime.device.HasDebuggingToolAttached()) { + buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); + } + commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal); +} - buffer.handle = device.GetLogical().CreateBuffer(ci); - buffer.commit = memory_manager.Commit(buffer.handle, false); +BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_, + VKScheduler& scheduler_, StagingBufferPool& staging_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, + VKDescriptorPool& descriptor_pool) + : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, + staging_pool{staging_pool_}, update_descriptor_queue{update_descriptor_queue_}, + uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), + quad_index_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue) {} + +StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) { + return staging_pool.Request(size, MemoryUsage::Upload); } -Buffer::~Buffer() = default; +StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size) { + return staging_pool.Request(size, MemoryUsage::Download); +} -void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) { - const auto& staging = staging_pool.GetUnusedBuffer(size, true); - std::memcpy(staging.commit->Map(size), data, size); +void BufferCacheRuntime::Finish() { + scheduler.Finish(); +} +void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, + std::span<const VideoCommon::BufferCopy> copies) { + static constexpr VkMemoryBarrier READ_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + }; + static constexpr VkMemoryBarrier WRITE_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }; + // Measuring a popular game, this number never exceeds the specified size once data is warmed up + boost::container::small_vector<VkBufferCopy, 3> vk_copies(copies.size()); + std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy); scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) { + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, READ_BARRIER); + cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, WRITE_BARRIER); + }); +} - const VkBuffer handle = Handle(); - scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyBuffer(staging, handle, VkBufferCopy{0, offset, size}); - - const VkBufferMemoryBarrier barrier{ - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = UPLOAD_ACCESS_BARRIERS, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = handle, - .offset = offset, - .size = size, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, UPLOAD_PIPELINE_STAGE, 0, {}, - barrier, {}); +void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, + u32 base_vertex, u32 num_indices, VkBuffer buffer, + u32 offset, [[maybe_unused]] u32 size) { + VkIndexType vk_index_type = MaxwellToVK::IndexFormat(index_format); + VkDeviceSize vk_offset = offset; + VkBuffer vk_buffer = buffer; + if (topology == PrimitiveTopology::Quads) { + vk_index_type = VK_INDEX_TYPE_UINT32; + std::tie(vk_buffer, vk_offset) = + quad_index_pass.Assemble(index_format, num_indices, base_vertex, buffer, offset); + } else if (vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported()) { + vk_index_type = VK_INDEX_TYPE_UINT16; + std::tie(vk_buffer, vk_offset) = uint8_pass.Assemble(num_indices, buffer, offset); + } + if (vk_buffer == VK_NULL_HANDLE) { + // Vulkan doesn't support null index buffers. Replace it with our own null buffer. + ReserveNullIndexBuffer(); + vk_buffer = *null_index_buffer; + } + scheduler.Record([vk_buffer, vk_offset, vk_index_type](vk::CommandBuffer cmdbuf) { + cmdbuf.BindIndexBuffer(vk_buffer, vk_offset, vk_index_type); }); } -void Buffer::Download(std::size_t offset, std::size_t size, u8* data) { - const auto& staging = staging_pool.GetUnusedBuffer(size, true); - scheduler.RequestOutsideRenderPassOperationContext(); +void BufferCacheRuntime::BindQuadArrayIndexBuffer(u32 first, u32 count) { + ReserveQuadArrayLUT(first + count, true); - const VkBuffer handle = Handle(); - scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) { - const VkBufferMemoryBarrier barrier{ - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = handle, - .offset = offset, - .size = size, - }; - - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, barrier, {}); - cmdbuf.CopyBuffer(handle, staging, VkBufferCopy{offset, 0, size}); + // The LUT has the indices 0, 1, 2, and 3 copied as an array + // To apply these 'first' offsets we can apply an offset based on the modulus. + const VkIndexType index_type = quad_array_lut_index_type; + const size_t sub_first_offset = static_cast<size_t>(first % 4) * (current_num_indices / 4); + const size_t offset = (sub_first_offset + first / 4) * 6ULL * BytesPerIndex(index_type); + scheduler.Record([buffer = *quad_array_lut, index_type, offset](vk::CommandBuffer cmdbuf) { + cmdbuf.BindIndexBuffer(buffer, offset, index_type); }); - scheduler.Finish(); - - std::memcpy(data, staging.commit->Map(size), size); } -void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) { - scheduler.RequestOutsideRenderPassOperationContext(); +void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, + u32 stride) { + if (device.IsExtExtendedDynamicStateSupported()) { + scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) { + const VkDeviceSize vk_offset = offset; + const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE; + const VkDeviceSize vk_stride = stride; + cmdbuf.BindVertexBuffers2EXT(index, 1, &buffer, &vk_offset, &vk_size, &vk_stride); + }); + } else { + scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffer(index, buffer, offset); + }); + } +} - const VkBuffer dst_buffer = Handle(); - scheduler.Record([src_buffer = src.Handle(), dst_buffer, src_offset, dst_offset, - size](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyBuffer(src_buffer, dst_buffer, VkBufferCopy{src_offset, dst_offset, size}); - - std::array<VkBufferMemoryBarrier, 2> barriers; - barriers[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barriers[0].pNext = nullptr; - barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barriers[0].dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barriers[0].buffer = src_buffer; - barriers[0].offset = src_offset; - barriers[0].size = size; - barriers[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barriers[1].pNext = nullptr; - barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barriers[1].dstAccessMask = UPLOAD_ACCESS_BARRIERS; - barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barriers[1].buffer = dst_buffer; - barriers[1].offset = dst_offset; - barriers[1].size = size; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, UPLOAD_PIPELINE_STAGE, 0, {}, - barriers, {}); +void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, + u32 size) { + if (!device.IsExtTransformFeedbackSupported()) { + // Already logged in the rasterizer + return; + } + scheduler.Record([index, buffer, offset, size](vk::CommandBuffer cmdbuf) { + const VkDeviceSize vk_offset = offset; + const VkDeviceSize vk_size = size; + cmdbuf.BindTransformFeedbackBuffersEXT(index, 1, &buffer, &vk_offset, &vk_size); }); } -VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, - const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler, VKStagingBufferPool& staging_pool) - : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, system, - CreateStreamBuffer(device, - scheduler)}, - device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{ - staging_pool} {} +void BufferCacheRuntime::BindBuffer(VkBuffer buffer, u32 offset, u32 size) { + update_descriptor_queue.AddBuffer(buffer, offset, size); +} + +void BufferCacheRuntime::ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle) { + if (num_indices <= current_num_indices) { + return; + } + if (wait_for_idle) { + scheduler.Finish(); + } + current_num_indices = num_indices; + quad_array_lut_index_type = IndexTypeFromNumElements(device, num_indices); -VKBufferCache::~VKBufferCache() = default; + const u32 num_quads = num_indices / 4; + const u32 num_triangle_indices = num_quads * 6; + const u32 num_first_offset_copies = 4; + const size_t bytes_per_index = BytesPerIndex(quad_array_lut_index_type); + const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies; + quad_array_lut = device.GetLogical().CreateBuffer(VkBufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = size_bytes, + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }); + if (device.HasDebuggingToolAttached()) { + quad_array_lut.SetObjectNameEXT("Quad LUT"); + } + quad_array_lut_commit = memory_allocator.Commit(quad_array_lut, MemoryUsage::DeviceLocal); -std::shared_ptr<Buffer> VKBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) { - return std::make_shared<Buffer>(device, memory_manager, scheduler, staging_pool, cpu_addr, - size); + const StagingBufferRef staging = staging_pool.Request(size_bytes, MemoryUsage::Upload); + u8* staging_data = staging.mapped_span.data(); + const size_t quad_size = bytes_per_index * 6; + for (u32 first = 0; first < num_first_offset_copies; ++first) { + for (u32 quad = 0; quad < num_quads; ++quad) { + switch (quad_array_lut_index_type) { + case VK_INDEX_TYPE_UINT8_EXT: + std::memcpy(staging_data, MakeQuadIndices<u8>(quad, first).data(), quad_size); + break; + case VK_INDEX_TYPE_UINT16: + std::memcpy(staging_data, MakeQuadIndices<u16>(quad, first).data(), quad_size); + break; + case VK_INDEX_TYPE_UINT32: + std::memcpy(staging_data, MakeQuadIndices<u32>(quad, first).data(), quad_size); + break; + default: + UNREACHABLE(); + break; + } + staging_data += quad_size; + } + } + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset, + dst_buffer = *quad_array_lut, size_bytes](vk::CommandBuffer cmdbuf) { + const VkBufferCopy copy{ + .srcOffset = src_offset, + .dstOffset = 0, + .size = size_bytes, + }; + const VkBufferMemoryBarrier write_barrier{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_INDEX_READ_BIT, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .buffer = dst_buffer, + .offset = 0, + .size = size_bytes, + }; + cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + 0, write_barrier); + }); } -VKBufferCache::BufferInfo VKBufferCache::GetEmptyBuffer(std::size_t size) { - size = std::max(size, std::size_t(4)); - const auto& empty = staging_pool.GetUnusedBuffer(size, false); +void BufferCacheRuntime::ReserveNullIndexBuffer() { + if (null_index_buffer) { + return; + } + null_index_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = 4, + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }); + if (device.HasDebuggingToolAttached()) { + null_index_buffer.SetObjectNameEXT("Null index buffer"); + } + null_index_buffer_commit = memory_allocator.Commit(null_index_buffer, MemoryUsage::DeviceLocal); + scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf) { - cmdbuf.FillBuffer(buffer, 0, size, 0); + scheduler.Record([buffer = *null_index_buffer](vk::CommandBuffer cmdbuf) { + cmdbuf.FillBuffer(buffer, 0, VK_WHOLE_SIZE, 0); }); - return {*empty.handle, 0, 0}; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 991ee451c..041e6515c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -4,70 +4,124 @@ #pragma once -#include <memory> - -#include "common/common_types.h" #include "video_core/buffer_cache/buffer_cache.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" -#include "video_core/renderer_vulkan/vk_stream_buffer.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Core { -class System; -} +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; -class VKMemoryManager; +class Device; +class VKDescriptorPool; class VKScheduler; +class VKUpdateDescriptorQueue; -class Buffer final : public VideoCommon::BufferBlock { +class BufferCacheRuntime; + +class Buffer : public VideoCommon::BufferBase<VideoCore::RasterizerInterface> { public: - explicit Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler, - VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size); - ~Buffer(); + explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params); + explicit Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, + VAddr cpu_addr_, u64 size_bytes_); + + [[nodiscard]] VkBuffer Handle() const noexcept { + return *buffer; + } + + operator VkBuffer() const noexcept { + return *buffer; + } + +private: + vk::Buffer buffer; + MemoryCommit commit; +}; + +class BufferCacheRuntime { + friend Buffer; + + using PrimitiveTopology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology; + using IndexFormat = Tegra::Engines::Maxwell3D::Regs::IndexFormat; + +public: + explicit BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_manager_, + VKScheduler& scheduler_, StagingBufferPool& staging_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, + VKDescriptorPool& descriptor_pool); + + void Finish(); - void Upload(std::size_t offset, std::size_t size, const u8* data); + [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size); - void Download(std::size_t offset, std::size_t size, u8* data); + [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size); - void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size); + void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, + std::span<const VideoCommon::BufferCopy> copies); - VkBuffer Handle() const { - return *buffer.handle; + void BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 num_indices, + u32 base_vertex, VkBuffer buffer, u32 offset, u32 size); + + void BindQuadArrayIndexBuffer(u32 first, u32 count); + + void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride); + + void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); + + std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage, + [[maybe_unused]] u32 binding_index, u32 size) { + const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); + BindBuffer(ref.buffer, static_cast<u32>(ref.offset), size); + return ref.mapped_span; } - u64 Address() const { - return 0; + void BindUniformBuffer(VkBuffer buffer, u32 offset, u32 size) { + BindBuffer(buffer, offset, size); + } + + void BindStorageBuffer(VkBuffer buffer, u32 offset, u32 size, + [[maybe_unused]] bool is_written) { + BindBuffer(buffer, offset, size); } private: + void BindBuffer(VkBuffer buffer, u32 offset, u32 size); + + void ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle); + + void ReserveNullIndexBuffer(); + + const Device& device; + MemoryAllocator& memory_allocator; VKScheduler& scheduler; - VKStagingBufferPool& staging_pool; + StagingBufferPool& staging_pool; + VKUpdateDescriptorQueue& update_descriptor_queue; - VKBuffer buffer; -}; + vk::Buffer quad_array_lut; + MemoryCommit quad_array_lut_commit; + VkIndexType quad_array_lut_index_type{}; + u32 current_num_indices = 0; -class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> { -public: - explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, - const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler, VKStagingBufferPool& staging_pool); - ~VKBufferCache(); + vk::Buffer null_index_buffer; + MemoryCommit null_index_buffer_commit; - BufferInfo GetEmptyBuffer(std::size_t size) override; + Uint8Pass uint8_pass; + QuadIndexedPass quad_index_pass; +}; -protected: - std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override; +struct BufferCacheParams { + using Runtime = Vulkan::BufferCacheRuntime; + using Buffer = Vulkan::Buffer; -private: - const VKDevice& device; - VKMemoryManager& memory_manager; - VKScheduler& scheduler; - VKStagingBufferPool& staging_pool; + static constexpr bool IS_OPENGL = false; + static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = false; + static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = false; + static constexpr bool NEEDS_BIND_UNIFORM_INDEX = false; + static constexpr bool NEEDS_BIND_STORAGE_INDEX = false; + static constexpr bool USE_MEMORY_MAPS = true; }; +using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp new file mode 100644 index 000000000..a99df9323 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp @@ -0,0 +1,46 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstddef> + +#include "video_core/renderer_vulkan/vk_command_pool.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +constexpr size_t COMMAND_BUFFER_POOL_SIZE = 0x1000; + +struct CommandPool::Pool { + vk::CommandPool handle; + vk::CommandBuffers cmdbufs; +}; + +CommandPool::CommandPool(MasterSemaphore& master_semaphore_, const Device& device_) + : ResourcePool(master_semaphore_, COMMAND_BUFFER_POOL_SIZE), device{device_} {} + +CommandPool::~CommandPool() = default; + +void CommandPool::Allocate(size_t begin, size_t end) { + // Command buffers are going to be commited, recorded, executed every single usage cycle. + // They are also going to be reseted when commited. + Pool& pool = pools.emplace_back(); + pool.handle = device.GetLogical().CreateCommandPool({ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = device.GetGraphicsFamily(), + }); + pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE); +} + +VkCommandBuffer CommandPool::Commit() { + const size_t index = CommitResource(); + const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; + const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; + return pools[pool_index].cmdbufs[sub_index]; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h new file mode 100644 index 000000000..61c26a22a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_command_pool.h @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <vector> + +#include "video_core/renderer_vulkan/vk_resource_pool.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class MasterSemaphore; + +class CommandPool final : public ResourcePool { +public: + explicit CommandPool(MasterSemaphore& master_semaphore_, const Device& device_); + ~CommandPool() override; + + void Allocate(size_t begin, size_t end) override; + + VkCommandBuffer Commit(); + +private: + struct Pool; + + const Device& device; + std::vector<Pool> pools; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 182461ed9..2f9a7b028 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -10,131 +10,19 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/div_ceil.h" +#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" +#include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { - namespace { - -// Quad array SPIR-V module. Generated from the "shaders/" directory, read the instructions there. -constexpr u8 quad_array[] = { - 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x09, 0x00, 0x34, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, - 0x37, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, - 0x49, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x4b, 0x00, 0x00, 0x00, - 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, - 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0xae, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, - 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, - 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, - 0x27, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, - 0x27, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x2f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x32, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x03, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, - 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, - 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x3d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x45, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x03, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x23, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4e, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, - 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; - -VkDescriptorSetLayoutBinding BuildQuadArrayPassDescriptorSetLayoutBinding() { - return { - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }; -} - -VkDescriptorUpdateTemplateEntryKHR BuildQuadArrayPassDescriptorUpdateTemplateEntry() { - return { - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = 0, - .stride = sizeof(DescriptorUpdateEntry), - }; -} - VkPushConstantRange BuildComputePushConstantRange(std::size_t size) { return { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, @@ -143,206 +31,6 @@ VkPushConstantRange BuildComputePushConstantRange(std::size_t size) { }; } -// Uint8 SPIR-V module. Generated from the "shaders/" directory. -constexpr u8 uint8_pass[] = { - 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x2f, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, - 0x51, 0x11, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x61, 0x11, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00, - 0x53, 0x50, 0x56, 0x5f, 0x4b, 0x48, 0x52, 0x5f, 0x31, 0x36, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x53, 0x50, 0x56, 0x5f, - 0x4b, 0x48, 0x52, 0x5f, 0x38, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, - 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, - 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, - 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0xf7, 0x00, 0x03, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0x15, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x71, 0x00, 0x04, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; - -// Quad indexed SPIR-V module. Generated from the "shaders/" directory. -constexpr u8 QUAD_INDEXED_SPV[] = { - 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x7c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x56, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x3b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x3f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x43, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x09, 0x00, 0x41, 0x00, 0x00, 0x00, - 0x44, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, - 0x42, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, - 0x46, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00, - 0x56, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x57, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x46, 0x00, 0x00, 0x00, - 0x47, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x73, 0x00, 0x00, 0x00, - 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x44, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, - 0x19, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, - 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, - 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, - 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, - 0x2b, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, - 0xf5, 0x00, 0x07, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x1e, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0xf6, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xfa, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, - 0xf8, 0x00, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, - 0x47, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, - 0xc3, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0x00, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, - 0x4a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x54, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, - 0x5b, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, - 0x4e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, - 0x5c, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, - 0x5d, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, - 0x41, 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x42, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, - 0x6a, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x62, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x5b, 0x00, 0x00, 0x00, - 0x6d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, - 0x3e, 0x00, 0x03, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, 0x00, - 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00, - 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; - std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBindings() { return {{ { @@ -375,11 +63,11 @@ VkDescriptorUpdateTemplateEntryKHR BuildInputOutputDescriptorUpdateTemplate() { } // Anonymous namespace -VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool, +VKComputePass::VKComputePass(const Device& device, VKDescriptorPool& descriptor_pool, vk::Span<VkDescriptorSetLayoutBinding> bindings, vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates, - vk::Span<VkPushConstantRange> push_constants, std::size_t code_size, - const u8* code) { + vk::Span<VkPushConstantRange> push_constants, + std::span<const u32> code) { descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = nullptr, @@ -387,7 +75,6 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto .bindingCount = bindings.size(), .pBindings = bindings.data(), }); - layout = device.GetLogical().CreatePipelineLayout({ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, @@ -397,7 +84,6 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto .pushConstantRangeCount = push_constants.size(), .pPushConstantRanges = push_constants.data(), }); - if (!templates.empty()) { descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR({ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR, @@ -414,18 +100,13 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto descriptor_allocator.emplace(descriptor_pool, *descriptor_set_layout); } - - auto code_copy = std::make_unique<u32[]>(code_size / sizeof(u32) + 1); - std::memcpy(code_copy.get(), code, code_size); - module = device.GetLogical().CreateShaderModule({ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .codeSize = code_size, - .pCode = code_copy.get(), + .codeSize = static_cast<u32>(code.size_bytes()), + .pCode = code.data(), }); - pipeline = device.GetLogical().CreateComputePipeline({ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -448,126 +129,70 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto VKComputePass::~VKComputePass() = default; -VkDescriptorSet VKComputePass::CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue, - VKFence& fence) { +VkDescriptorSet VKComputePass::CommitDescriptorSet( + VKUpdateDescriptorQueue& update_descriptor_queue) { if (!descriptor_template) { return nullptr; } - const auto set = descriptor_allocator->Commit(fence); + const VkDescriptorSet set = descriptor_allocator->Commit(); update_descriptor_queue.Send(*descriptor_template, set); return set; } -QuadArrayPass::QuadArrayPass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue) - : VKComputePass(device, descriptor_pool, BuildQuadArrayPassDescriptorSetLayoutBinding(), - BuildQuadArrayPassDescriptorUpdateTemplateEntry(), - BuildComputePushConstantRange(sizeof(u32)), std::size(quad_array), quad_array), - scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool}, - update_descriptor_queue{update_descriptor_queue} {} - -QuadArrayPass::~QuadArrayPass() = default; - -std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) { - const u32 num_triangle_vertices = (num_vertices / 4) * 6; - const std::size_t staging_size = num_triangle_vertices * sizeof(u32); - auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); - - update_descriptor_queue.Acquire(); - update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size); - const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); - - scheduler.RequestOutsideRenderPassOperationContext(); - - ASSERT(num_vertices % 4 == 0); - const u32 num_quads = num_vertices / 4; - scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, num_quads, - first, set](vk::CommandBuffer cmdbuf) { - constexpr u32 dispatch_size = 1024; - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {}); - cmdbuf.PushConstants(layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(first), &first); - cmdbuf.Dispatch(Common::AlignUp(num_quads, dispatch_size) / dispatch_size, 1, 1); - - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = buffer; - barrier.offset = 0; - barrier.size = static_cast<VkDeviceSize>(num_quads) * 6 * sizeof(u32); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, {barrier}, {}); - }); - return {*buffer.handle, 0}; -} - -Uint8Pass::Uint8Pass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue) +Uint8Pass::Uint8Pass(const Device& device, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool, StagingBufferPool& staging_buffer_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_) : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(), - BuildInputOutputDescriptorUpdateTemplate(), {}, std::size(uint8_pass), - uint8_pass), - scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool}, - update_descriptor_queue{update_descriptor_queue} {} + BuildInputOutputDescriptorUpdateTemplate(), {}, VULKAN_UINT8_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + update_descriptor_queue{update_descriptor_queue_} {} Uint8Pass::~Uint8Pass() = default; -std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer, - u64 src_offset) { - const auto staging_size = static_cast<u32>(num_vertices * sizeof(u16)); - auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); +std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer, + u32 src_offset) { + const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16)); + const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); update_descriptor_queue.Acquire(); update_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices); - update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size); - const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); + update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); + const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue); scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set, + scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging.buffer, set, num_vertices](vk::CommandBuffer cmdbuf) { - constexpr u32 dispatch_size = 1024; + static constexpr u32 DISPATCH_SIZE = 1024; + static constexpr VkMemoryBarrier WRITE_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, + }; cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {}); - cmdbuf.Dispatch(Common::AlignUp(num_vertices, dispatch_size) / dispatch_size, 1, 1); - - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = buffer; - barrier.offset = 0; - barrier.size = static_cast<VkDeviceSize>(num_vertices * sizeof(u16)); + cmdbuf.Dispatch(Common::DivCeil(num_vertices, DISPATCH_SIZE), 1, 1); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {}); + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, WRITE_BARRIER); }); - return {*buffer.handle, 0}; + return {staging.buffer, staging.offset}; } -QuadIndexedPass::QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue) - : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(), +QuadIndexedPass::QuadIndexedPass(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_) + : VKComputePass(device_, descriptor_pool_, BuildInputOutputDescriptorSetBindings(), BuildInputOutputDescriptorUpdateTemplate(), - BuildComputePushConstantRange(sizeof(u32) * 2), std::size(QUAD_INDEXED_SPV), - QUAD_INDEXED_SPV), - scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool}, - update_descriptor_queue{update_descriptor_queue} {} + BuildComputePushConstantRange(sizeof(u32) * 2), VULKAN_QUAD_INDEXED_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + update_descriptor_queue{update_descriptor_queue_} {} QuadIndexedPass::~QuadIndexedPass() = default; -std::pair<VkBuffer, u64> QuadIndexedPass::Assemble( +std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble( Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices, u32 base_vertex, - VkBuffer src_buffer, u64 src_offset) { + VkBuffer src_buffer, u32 src_offset) { const u32 index_shift = [index_format] { switch (index_format) { case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedByte: @@ -584,38 +209,33 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble( const u32 num_tri_vertices = (num_vertices / 4) * 6; const std::size_t staging_size = num_tri_vertices * sizeof(u32); - auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); + const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); update_descriptor_queue.Acquire(); update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size); - update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size); - const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); + update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); + const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue); scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set, + scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging.buffer, set, num_tri_vertices, base_vertex, index_shift](vk::CommandBuffer cmdbuf) { - static constexpr u32 dispatch_size = 1024; + static constexpr u32 DISPATCH_SIZE = 1024; + static constexpr VkMemoryBarrier WRITE_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, + }; const std::array push_constants = {base_vertex, index_shift}; cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {}); cmdbuf.PushConstants(layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants), &push_constants); - cmdbuf.Dispatch(Common::AlignUp(num_tri_vertices, dispatch_size) / dispatch_size, 1, 1); - - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = buffer; - barrier.offset = 0; - barrier.size = static_cast<VkDeviceSize>(num_tri_vertices * sizeof(u32)); + cmdbuf.Dispatch(Common::DivCeil(num_tri_vertices, DISPATCH_SIZE), 1, 1); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {}); + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, WRITE_BARRIER); }); - return {*buffer.handle, 0}; + return {staging.buffer, staging.offset}; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 230b526bc..17d781d99 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -5,33 +5,31 @@ #pragma once #include <optional> +#include <span> #include <utility> #include "common/common_types.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; -class VKFence; +class Device; +class StagingBufferPool; class VKScheduler; -class VKStagingBufferPool; class VKUpdateDescriptorQueue; class VKComputePass { public: - explicit VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool, + explicit VKComputePass(const Device& device, VKDescriptorPool& descriptor_pool, vk::Span<VkDescriptorSetLayoutBinding> bindings, vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates, - vk::Span<VkPushConstantRange> push_constants, std::size_t code_size, - const u8* code); + vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code); ~VKComputePass(); protected: - VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue, - VKFence& fence); + VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue); vk::DescriptorUpdateTemplateKHR descriptor_template; vk::PipelineLayout layout; @@ -43,52 +41,39 @@ private: vk::ShaderModule module; }; -class QuadArrayPass final : public VKComputePass { -public: - explicit QuadArrayPass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue); - ~QuadArrayPass(); - - std::pair<VkBuffer, VkDeviceSize> Assemble(u32 num_vertices, u32 first); - -private: - VKScheduler& scheduler; - VKStagingBufferPool& staging_buffer_pool; - VKUpdateDescriptorQueue& update_descriptor_queue; -}; - class Uint8Pass final : public VKComputePass { public: - explicit Uint8Pass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue); + explicit Uint8Pass(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_); ~Uint8Pass(); - std::pair<VkBuffer, u64> Assemble(u32 num_vertices, VkBuffer src_buffer, u64 src_offset); + /// Assemble uint8 indices into an uint16 index buffer + /// Returns a pair with the staging buffer, and the offset where the assembled data is + std::pair<VkBuffer, VkDeviceSize> Assemble(u32 num_vertices, VkBuffer src_buffer, + u32 src_offset); private: VKScheduler& scheduler; - VKStagingBufferPool& staging_buffer_pool; + StagingBufferPool& staging_buffer_pool; VKUpdateDescriptorQueue& update_descriptor_queue; }; class QuadIndexedPass final : public VKComputePass { public: - explicit QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKStagingBufferPool& staging_buffer_pool, - VKUpdateDescriptorQueue& update_descriptor_queue); + explicit QuadIndexedPass(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_); ~QuadIndexedPass(); - std::pair<VkBuffer, u64> Assemble(Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, - u32 num_vertices, u32 base_vertex, VkBuffer src_buffer, - u64 src_offset); + std::pair<VkBuffer, VkDeviceSize> Assemble( + Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices, + u32 base_vertex, VkBuffer src_buffer, u32 src_offset); private: VKScheduler& scheduler; - VKStagingBufferPool& staging_buffer_pool; + StagingBufferPool& staging_buffer_pool; VKUpdateDescriptorQueue& update_descriptor_queue; }; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index ed9d2991c..3a48219b7 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -6,25 +6,25 @@ #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -VKComputePipeline::VKComputePipeline(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - const SPIRVShader& shader) - : device{device}, scheduler{scheduler}, entries{shader.entries}, +VKComputePipeline::VKComputePipeline(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, + const SPIRVShader& shader_) + : device{device_}, scheduler{scheduler_}, entries{shader_.entries}, descriptor_set_layout{CreateDescriptorSetLayout()}, - descriptor_allocator{descriptor_pool, *descriptor_set_layout}, - update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, + descriptor_allocator{descriptor_pool_, *descriptor_set_layout}, + update_descriptor_queue{update_descriptor_queue_}, layout{CreatePipelineLayout()}, descriptor_template{CreateDescriptorUpdateTemplate()}, - shader_module{CreateShaderModule(shader.code)}, pipeline{CreatePipeline()} {} + shader_module{CreateShaderModule(shader_.code)}, pipeline{CreatePipeline()} {} VKComputePipeline::~VKComputePipeline() = default; @@ -32,7 +32,7 @@ VkDescriptorSet VKComputePipeline::CommitDescriptorSet() { if (!descriptor_template) { return {}; } - const auto set = descriptor_allocator.Commit(scheduler.GetFence()); + const VkDescriptorSet set = descriptor_allocator.Commit(); update_descriptor_queue.Send(*descriptor_template, set); return set; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 6e2f22a4a..7e16575ac 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -7,20 +7,20 @@ #include "common/common_types.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; +class Device; class VKScheduler; class VKUpdateDescriptorQueue; class VKComputePipeline final { public: - explicit VKComputePipeline(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - const SPIRVShader& shader); + explicit VKComputePipeline(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, + const SPIRVShader& shader_); ~VKComputePipeline(); VkDescriptorSet CommitDescriptorSet(); @@ -48,7 +48,7 @@ private: vk::Pipeline CreatePipeline() const; - const VKDevice& device; + const Device& device; VKScheduler& scheduler; ShaderEntries entries; diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp index ac4a0884e..ef9fb5910 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -6,23 +6,25 @@ #include "common/common_types.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { // Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines. constexpr std::size_t SETS_GROW_RATE = 0x20; -DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool, - VkDescriptorSetLayout layout) - : VKFencedPool{SETS_GROW_RATE}, descriptor_pool{descriptor_pool}, layout{layout} {} +DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool_, + VkDescriptorSetLayout layout_) + : ResourcePool(descriptor_pool_.master_semaphore, SETS_GROW_RATE), + descriptor_pool{descriptor_pool_}, layout{layout_} {} DescriptorAllocator::~DescriptorAllocator() = default; -VkDescriptorSet DescriptorAllocator::Commit(VKFence& fence) { - const std::size_t index = CommitResource(fence); +VkDescriptorSet DescriptorAllocator::Commit() { + const std::size_t index = CommitResource(); return descriptors_allocations[index / SETS_GROW_RATE][index % SETS_GROW_RATE]; } @@ -30,8 +32,9 @@ void DescriptorAllocator::Allocate(std::size_t begin, std::size_t end) { descriptors_allocations.push_back(descriptor_pool.AllocateDescriptors(layout, end - begin)); } -VKDescriptorPool::VKDescriptorPool(const VKDevice& device) - : device{device}, active_pool{AllocateNewPool()} {} +VKDescriptorPool::VKDescriptorPool(const Device& device_, VKScheduler& scheduler) + : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()}, active_pool{ + AllocateNewPool()} {} VKDescriptorPool::~VKDescriptorPool() = default; diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h index 9efa66bef..f892be7be 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h @@ -6,21 +6,24 @@ #include <vector> -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { +class Device; class VKDescriptorPool; +class VKScheduler; -class DescriptorAllocator final : public VKFencedPool { +class DescriptorAllocator final : public ResourcePool { public: explicit DescriptorAllocator(VKDescriptorPool& descriptor_pool, VkDescriptorSetLayout layout); ~DescriptorAllocator() override; + DescriptorAllocator& operator=(const DescriptorAllocator&) = delete; DescriptorAllocator(const DescriptorAllocator&) = delete; - VkDescriptorSet Commit(VKFence& fence); + VkDescriptorSet Commit(); protected: void Allocate(std::size_t begin, std::size_t end) override; @@ -36,15 +39,19 @@ class VKDescriptorPool final { friend DescriptorAllocator; public: - explicit VKDescriptorPool(const VKDevice& device); + explicit VKDescriptorPool(const Device& device, VKScheduler& scheduler); ~VKDescriptorPool(); + VKDescriptorPool(const VKDescriptorPool&) = delete; + VKDescriptorPool& operator=(const VKDescriptorPool&) = delete; + private: vk::DescriptorPool* AllocateNewPool(); vk::DescriptorSets AllocateDescriptors(VkDescriptorSetLayout layout, std::size_t count); - const VKDevice& device; + const Device& device; + MasterSemaphore& master_semaphore; std::vector<vk::DescriptorPool> pools; vk::DescriptorPool* active_pool; diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp index d7f65d435..3bec48d14 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp @@ -3,23 +3,21 @@ // Refer to the license.txt file included. #include <memory> -#include <thread> #include "video_core/renderer_vulkan/vk_buffer_cache.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_fence_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload, bool is_stubbed) - : VideoCommon::FenceBase(payload, is_stubbed), device{device}, scheduler{scheduler} {} +InnerFence::InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_) + : FenceBase{payload_, is_stubbed_}, scheduler{scheduler_} {} -InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address, - u32 payload, bool is_stubbed) - : VideoCommon::FenceBase(address, payload, is_stubbed), device{device}, scheduler{scheduler} {} +InnerFence::InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_) + : FenceBase{address_, payload_, is_stubbed_}, scheduler{scheduler_} {} InnerFence::~InnerFence() = default; @@ -27,63 +25,38 @@ void InnerFence::Queue() { if (is_stubbed) { return; } - ASSERT(!event); - - event = device.GetLogical().CreateNewEvent(); - ticks = scheduler.Ticks(); - - scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([event = *event](vk::CommandBuffer cmdbuf) { - cmdbuf.SetEvent(event, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - }); + // Get the current tick so we can wait for it + wait_tick = scheduler.CurrentTick(); + scheduler.Flush(); } bool InnerFence::IsSignaled() const { if (is_stubbed) { return true; } - ASSERT(event); - return IsEventSignalled(); + return scheduler.IsFree(wait_tick); } void InnerFence::Wait() { if (is_stubbed) { return; } - ASSERT(event); - - if (ticks >= scheduler.Ticks()) { - scheduler.Flush(); - } - while (!IsEventSignalled()) { - std::this_thread::yield(); - } -} - -bool InnerFence::IsEventSignalled() const { - switch (const VkResult result = event.GetStatus()) { - case VK_EVENT_SET: - return true; - case VK_EVENT_RESET: - return false; - default: - throw vk::Exception(result); - } + scheduler.Wait(wait_tick); } -VKFenceManager::VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKScheduler& scheduler, - VKTextureCache& texture_cache, VKBufferCache& buffer_cache, - VKQueryCache& query_cache) - : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache), - device{device}, scheduler{scheduler} {} +VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_, + TextureCache& texture_cache_, BufferCache& buffer_cache_, + VKQueryCache& query_cache_, const Device& device_, + VKScheduler& scheduler_) + : GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_}, + scheduler{scheduler_} {} Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) { - return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed); + return std::make_shared<InnerFence>(scheduler, value, is_stubbed); } Fence VKFenceManager::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) { - return std::make_shared<InnerFence>(device, scheduler, addr, value, is_stubbed); + return std::make_shared<InnerFence>(scheduler, addr, value, is_stubbed); } void VKFenceManager::QueueFence(Fence& fence) { diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h index 043fe7947..2f8322d29 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.h +++ b/src/video_core/renderer_vulkan/vk_fence_manager.h @@ -8,7 +8,8 @@ #include "video_core/fence_manager.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Core { class System; @@ -20,18 +21,14 @@ class RasterizerInterface; namespace Vulkan { -class VKBufferCache; -class VKDevice; +class Device; class VKQueryCache; class VKScheduler; -class VKTextureCache; class InnerFence : public VideoCommon::FenceBase { public: - explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload, - bool is_stubbed); - explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address, - u32 payload, bool is_stubbed); + explicit InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_); + explicit InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_); ~InnerFence(); void Queue(); @@ -41,24 +38,20 @@ public: void Wait(); private: - bool IsEventSignalled() const; - - const VKDevice& device; VKScheduler& scheduler; - vk::Event event; - u64 ticks = 0; + u64 wait_tick = 0; }; using Fence = std::shared_ptr<InnerFence>; using GenericFenceManager = - VideoCommon::FenceManager<Fence, VKTextureCache, VKBufferCache, VKQueryCache>; + VideoCommon::FenceManager<Fence, TextureCache, BufferCache, VKQueryCache>; class VKFenceManager final : public GenericFenceManager { public: - explicit VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKScheduler& scheduler, - VKTextureCache& texture_cache, VKBufferCache& buffer_cache, - VKQueryCache& query_cache); + explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu, + TextureCache& texture_cache, BufferCache& buffer_cache, + VKQueryCache& query_cache, const Device& device, + VKScheduler& scheduler); protected: Fence CreateFence(u32 value, bool is_stubbed) override; @@ -68,7 +61,6 @@ protected: void WaitFence(Fence& fence) override; private: - const VKDevice& device; VKScheduler& scheduler; }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 2e46c6278..fc6dd83eb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -12,13 +12,12 @@ #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -69,23 +68,45 @@ VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) { }; } +VkSampleCountFlagBits ConvertMsaaMode(Tegra::Texture::MsaaMode msaa_mode) { + switch (msaa_mode) { + case Tegra::Texture::MsaaMode::Msaa1x1: + return VK_SAMPLE_COUNT_1_BIT; + case Tegra::Texture::MsaaMode::Msaa2x1: + case Tegra::Texture::MsaaMode::Msaa2x1_D3D: + return VK_SAMPLE_COUNT_2_BIT; + case Tegra::Texture::MsaaMode::Msaa2x2: + case Tegra::Texture::MsaaMode::Msaa2x2_VC4: + case Tegra::Texture::MsaaMode::Msaa2x2_VC12: + return VK_SAMPLE_COUNT_4_BIT; + case Tegra::Texture::MsaaMode::Msaa4x2: + case Tegra::Texture::MsaaMode::Msaa4x2_D3D: + case Tegra::Texture::MsaaMode::Msaa4x2_VC8: + case Tegra::Texture::MsaaMode::Msaa4x2_VC24: + return VK_SAMPLE_COUNT_8_BIT; + case Tegra::Texture::MsaaMode::Msaa4x4: + return VK_SAMPLE_COUNT_16_BIT; + default: + UNREACHABLE_MSG("Invalid msaa_mode={}", static_cast<int>(msaa_mode)); + return VK_SAMPLE_COUNT_1_BIT; + } +} + } // Anonymous namespace -VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - VKRenderPassCache& renderpass_cache, +VKGraphicsPipeline::VKGraphicsPipeline(const Device& device_, VKScheduler& scheduler_, + VKDescriptorPool& descriptor_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, const GraphicsPipelineCacheKey& key, vk::Span<VkDescriptorSetLayoutBinding> bindings, - const SPIRVProgram& program) - : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()}, + const SPIRVProgram& program, u32 num_color_buffers) + : device{device_}, scheduler{scheduler_}, cache_key{key}, hash{cache_key.Hash()}, descriptor_set_layout{CreateDescriptorSetLayout(bindings)}, - descriptor_allocator{descriptor_pool, *descriptor_set_layout}, - update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, - descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules( - program)}, - renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)}, - pipeline{CreatePipeline(cache_key.renderpass_params, program)} {} + descriptor_allocator{descriptor_pool_, *descriptor_set_layout}, + update_descriptor_queue{update_descriptor_queue_}, layout{CreatePipelineLayout()}, + descriptor_template{CreateDescriptorUpdateTemplate(program)}, + modules(CreateShaderModules(program)), + pipeline(CreatePipeline(program, cache_key.renderpass, num_color_buffers)) {} VKGraphicsPipeline::~VKGraphicsPipeline() = default; @@ -93,7 +114,7 @@ VkDescriptorSet VKGraphicsPipeline::CommitDescriptorSet() { if (!descriptor_template) { return {}; } - const auto set = descriptor_allocator.Commit(scheduler.GetFence()); + const VkDescriptorSet set = descriptor_allocator.Commit(); update_descriptor_queue.Send(*descriptor_template, set); return set; } @@ -159,10 +180,12 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules( .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pNext = nullptr, .flags = 0, + .codeSize = 0, + .pCode = nullptr, }; - std::vector<vk::ShaderModule> modules; - modules.reserve(Maxwell::MaxShaderStage); + std::vector<vk::ShaderModule> shader_modules; + shader_modules.reserve(Maxwell::MaxShaderStage); for (std::size_t i = 0; i < Maxwell::MaxShaderStage; ++i) { const auto& stage = program[i]; if (!stage) { @@ -173,13 +196,14 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules( ci.codeSize = stage->code.size() * sizeof(u32); ci.pCode = stage->code.data(); - modules.push_back(device.GetLogical().CreateShaderModule(ci)); + shader_modules.push_back(device.GetLogical().CreateShaderModule(ci)); } - return modules; + return shader_modules; } -vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, - const SPIRVProgram& program) const { +vk::Pipeline VKGraphicsPipeline::CreatePipeline(const SPIRVProgram& program, + VkRenderPass renderpass, + u32 num_color_buffers) const { const auto& state = cache_key.fixed_state; const auto& viewport_swizzles = state.viewport_swizzles; @@ -189,11 +213,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa // state is ignored dynamic.raw1 = 0; dynamic.raw2 = 0; - for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) { - // Enable all vertex bindings - binding.raw = 0; - binding.enabled.Assign(1); - } + dynamic.vertex_strides.fill(0); } else { dynamic = state.dynamic_state; } @@ -201,19 +221,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa std::vector<VkVertexInputBindingDescription> vertex_bindings; std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { - const auto& binding = dynamic.vertex_bindings[index]; - if (!binding.enabled) { - continue; - } const bool instanced = state.binding_divisors[index] != 0; const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; - vertex_bindings.push_back({ .binding = static_cast<u32>(index), - .stride = binding.stride, + .stride = dynamic.vertex_strides[index], .inputRate = rate, }); - if (instanced) { vertex_binding_divisors.push_back({ .binding = static_cast<u32>(index), @@ -229,7 +243,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa if (!attribute.enabled) { continue; } - if (input_attributes.find(static_cast<u32>(index)) == input_attributes.end()) { + if (!input_attributes.contains(static_cast<u32>(index))) { // Skip attributes not used by the vertex shaders. continue; } @@ -261,12 +275,12 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa vertex_input_ci.pNext = &input_divisor_ci; } - const auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology()); + const auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, state.topology); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology()), + .topology = MaxwellToVK::PrimitiveTopology(device, state.topology), .primitiveRestartEnable = state.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_topology), }; @@ -289,8 +303,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa }; std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles; - std::transform(viewport_swizzles.begin(), viewport_swizzles.end(), swizzles.begin(), - UnpackViewportSwizzle); + std::ranges::transform(viewport_swizzles, swizzles.begin(), UnpackViewportSwizzle); VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV, .pNext = nullptr, @@ -311,8 +324,8 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa .rasterizerDiscardEnable = static_cast<VkBool32>(state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE), .polygonMode = VK_POLYGON_MODE_FILL, - .cullMode = - dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE, + .cullMode = static_cast<VkCullModeFlags>( + dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE), .frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace()), .depthBiasEnable = state.depth_bias_enable, .depthBiasConstantFactor = 0.0f, @@ -325,7 +338,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .rasterizationSamples = ConvertMsaaMode(state.msaa_mode), .sampleShadingEnable = VK_FALSE, .minSampleShading = 0.0f, .pSampleMask = nullptr, @@ -351,8 +364,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa }; std::array<VkPipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments; - const auto num_attachments = static_cast<std::size_t>(renderpass_params.num_color_attachments); - for (std::size_t index = 0; index < num_attachments; ++index) { + for (std::size_t index = 0; index < num_color_buffers; ++index) { static constexpr std::array COMPONENT_TABLE{ VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, @@ -386,8 +398,9 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa .flags = 0, .logicOpEnable = VK_FALSE, .logicOp = VK_LOGIC_OP_COPY, - .attachmentCount = static_cast<u32>(num_attachments), + .attachmentCount = num_color_buffers, .pAttachments = cb_attachments.data(), + .blendConstants = {}, }; std::vector dynamic_states{ @@ -400,7 +413,6 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa static constexpr std::array extended{ VK_DYNAMIC_STATE_CULL_MODE_EXT, VK_DYNAMIC_STATE_FRONT_FACE_EXT, - VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT, @@ -446,8 +458,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa stage_ci.pNext = &subgroup_size_ci; } } - - const VkGraphicsPipelineCreateInfo ci{ + return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -467,8 +478,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa .subpass = 0, .basePipelineHandle = nullptr, .basePipelineIndex = 0, - }; - return device.GetLogical().CreateGraphicsPipeline(ci); + }); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 58aa35efd..8b6a98fe0 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -8,20 +8,19 @@ #include <optional> #include <vector> +#include "common/common_types.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { using Maxwell = Tegra::Engines::Maxwell3D::Regs; struct GraphicsPipelineCacheKey { - RenderPassParams renderpass_params; - u32 padding; + VkRenderPass renderpass; std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; FixedPipelineState fixed_state; @@ -34,16 +33,15 @@ struct GraphicsPipelineCacheKey { } std::size_t Size() const noexcept { - return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size(); + return sizeof(renderpass) + sizeof(shaders) + fixed_state.Size(); } }; static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>); static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>); static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>); +class Device; class VKDescriptorPool; -class VKDevice; -class VKRenderPassCache; class VKScheduler; class VKUpdateDescriptorQueue; @@ -51,13 +49,12 @@ using SPIRVProgram = std::array<std::optional<SPIRVShader>, Maxwell::MaxShaderSt class VKGraphicsPipeline final { public: - explicit VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler, + explicit VKGraphicsPipeline(const Device& device_, VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - VKRenderPassCache& renderpass_cache, + VKUpdateDescriptorQueue& update_descriptor_queue_, const GraphicsPipelineCacheKey& key, vk::Span<VkDescriptorSetLayoutBinding> bindings, - const SPIRVProgram& program); + const SPIRVProgram& program, u32 num_color_buffers); ~VKGraphicsPipeline(); VkDescriptorSet CommitDescriptorSet(); @@ -70,10 +67,6 @@ public: return *layout; } - VkRenderPass GetRenderPass() const { - return renderpass; - } - GraphicsPipelineCacheKey GetCacheKey() const { return cache_key; } @@ -89,10 +82,10 @@ private: std::vector<vk::ShaderModule> CreateShaderModules(const SPIRVProgram& program) const; - vk::Pipeline CreatePipeline(const RenderPassParams& renderpass_params, - const SPIRVProgram& program) const; + vk::Pipeline CreatePipeline(const SPIRVProgram& program, VkRenderPass renderpass, + u32 num_color_buffers) const; - const VKDevice& device; + const Device& device; VKScheduler& scheduler; const GraphicsPipelineCacheKey cache_key; const u64 hash; @@ -104,7 +97,6 @@ private: vk::DescriptorUpdateTemplateKHR descriptor_template; std::vector<vk::ShaderModule> modules; - VkRenderPass renderpass; vk::Pipeline pipeline; }; diff --git a/src/video_core/renderer_vulkan/vk_image.cpp b/src/video_core/renderer_vulkan/vk_image.cpp deleted file mode 100644 index 1c418ea17..000000000 --- a/src/video_core/renderer_vulkan/vk_image.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <vector> - -#include "common/assert.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_image.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -VKImage::VKImage(const VKDevice& device, VKScheduler& scheduler, const VkImageCreateInfo& image_ci, - VkImageAspectFlags aspect_mask) - : device{device}, scheduler{scheduler}, format{image_ci.format}, aspect_mask{aspect_mask}, - image_num_layers{image_ci.arrayLayers}, image_num_levels{image_ci.mipLevels} { - UNIMPLEMENTED_IF_MSG(image_ci.queueFamilyIndexCount != 0, - "Queue family tracking is not implemented"); - - image = device.GetLogical().CreateImage(image_ci); - - const u32 num_ranges = image_num_layers * image_num_levels; - barriers.resize(num_ranges); - subrange_states.resize(num_ranges, {{}, image_ci.initialLayout}); -} - -VKImage::~VKImage() = default; - -void VKImage::Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, - VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access, - VkImageLayout new_layout) { - if (!HasChanged(base_layer, num_layers, base_level, num_levels, new_access, new_layout)) { - return; - } - - std::size_t cursor = 0; - for (u32 layer_it = 0; layer_it < num_layers; ++layer_it) { - for (u32 level_it = 0; level_it < num_levels; ++level_it, ++cursor) { - const u32 layer = base_layer + layer_it; - const u32 level = base_level + level_it; - auto& state = GetSubrangeState(layer, level); - auto& barrier = barriers[cursor]; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = state.access; - barrier.dstAccessMask = new_access; - barrier.oldLayout = state.layout; - barrier.newLayout = new_layout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = *image; - barrier.subresourceRange.aspectMask = aspect_mask; - barrier.subresourceRange.baseMipLevel = level; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = layer; - barrier.subresourceRange.layerCount = 1; - state.access = new_access; - state.layout = new_layout; - } - } - - scheduler.RequestOutsideRenderPassOperationContext(); - - scheduler.Record([barriers = barriers, cursor](vk::CommandBuffer cmdbuf) { - // TODO(Rodrigo): Implement a way to use the latest stage across subresources. - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, {}, {}, - vk::Span(barriers.data(), cursor)); - }); -} - -bool VKImage::HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, - VkAccessFlags new_access, VkImageLayout new_layout) noexcept { - const bool is_full_range = base_layer == 0 && num_layers == image_num_layers && - base_level == 0 && num_levels == image_num_levels; - if (!is_full_range) { - state_diverged = true; - } - - if (!state_diverged) { - auto& state = GetSubrangeState(0, 0); - if (state.access != new_access || state.layout != new_layout) { - return true; - } - } - - for (u32 layer_it = 0; layer_it < num_layers; ++layer_it) { - for (u32 level_it = 0; level_it < num_levels; ++level_it) { - const u32 layer = base_layer + layer_it; - const u32 level = base_level + level_it; - auto& state = GetSubrangeState(layer, level); - if (state.access != new_access || state.layout != new_layout) { - return true; - } - } - } - return false; -} - -void VKImage::CreatePresentView() { - // Image type has to be 2D to be presented. - present_view = device.GetLogical().CreateImageView({ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = *image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = format, - .components = - { - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, - }, - .subresourceRange = - { - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }); -} - -VKImage::SubrangeState& VKImage::GetSubrangeState(u32 layer, u32 level) noexcept { - return subrange_states[static_cast<std::size_t>(layer * image_num_levels) + - static_cast<std::size_t>(level)]; -} - -} // namespace Vulkan
\ No newline at end of file diff --git a/src/video_core/renderer_vulkan/vk_image.h b/src/video_core/renderer_vulkan/vk_image.h deleted file mode 100644 index b4d7229e5..000000000 --- a/src/video_core/renderer_vulkan/vk_image.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <vector> - -#include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -class VKDevice; -class VKScheduler; - -class VKImage { -public: - explicit VKImage(const VKDevice& device, VKScheduler& scheduler, - const VkImageCreateInfo& image_ci, VkImageAspectFlags aspect_mask); - ~VKImage(); - - /// Records in the passed command buffer an image transition and updates the state of the image. - void Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, - VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access, - VkImageLayout new_layout); - - /// Returns a view compatible with presentation, the image has to be 2D. - VkImageView GetPresentView() { - if (!present_view) { - CreatePresentView(); - } - return *present_view; - } - - /// Returns the Vulkan image handler. - const vk::Image& GetHandle() const { - return image; - } - - /// Returns the Vulkan format for this image. - VkFormat GetFormat() const { - return format; - } - - /// Returns the Vulkan aspect mask. - VkImageAspectFlags GetAspectMask() const { - return aspect_mask; - } - -private: - struct SubrangeState final { - VkAccessFlags access = 0; ///< Current access bits. - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; ///< Current image layout. - }; - - bool HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, - VkAccessFlags new_access, VkImageLayout new_layout) noexcept; - - /// Creates a presentation view. - void CreatePresentView(); - - /// Returns the subrange state for a layer and layer. - SubrangeState& GetSubrangeState(u32 layer, u32 level) noexcept; - - const VKDevice& device; ///< Device handler. - VKScheduler& scheduler; ///< Device scheduler. - - const VkFormat format; ///< Vulkan format. - const VkImageAspectFlags aspect_mask; ///< Vulkan aspect mask. - const u32 image_num_layers; ///< Number of layers. - const u32 image_num_levels; ///< Number of mipmap levels. - - vk::Image image; ///< Image handle. - vk::ImageView present_view; ///< Image view compatible with presentation. - - std::vector<VkImageMemoryBarrier> barriers; ///< Pool of barriers. - std::vector<SubrangeState> subrange_states; ///< Current subrange state. - - bool state_diverged = false; ///< True when subresources mismatch in layout. -}; - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp new file mode 100644 index 000000000..56ec5e380 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -0,0 +1,56 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <atomic> +#include <chrono> + +#include "core/settings.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +using namespace std::chrono_literals; + +MasterSemaphore::MasterSemaphore(const Device& device) { + static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, + .pNext = nullptr, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR, + .initialValue = 0, + }; + static constexpr VkSemaphoreCreateInfo semaphore_ci{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &semaphore_type_ci, + .flags = 0, + }; + semaphore = device.GetLogical().CreateSemaphore(semaphore_ci); + + if (!Settings::values.renderer_debug) { + return; + } + // Validation layers have a bug where they fail to track resource usage when using timeline + // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have + // a separate thread waiting for each timeline semaphore value. + debug_thread = std::thread([this] { + u64 counter = 0; + while (!shutdown) { + if (semaphore.Wait(counter, 10'000'000)) { + ++counter; + } + } + }); +} + +MasterSemaphore::~MasterSemaphore() { + shutdown = true; + + // This thread might not be started + if (debug_thread.joinable()) { + debug_thread.join(); + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h new file mode 100644 index 000000000..2c7ed654d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h @@ -0,0 +1,75 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <thread> + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; + +class MasterSemaphore { +public: + explicit MasterSemaphore(const Device& device); + ~MasterSemaphore(); + + /// Returns the current logical tick. + [[nodiscard]] u64 CurrentTick() const noexcept { + return current_tick.load(std::memory_order_relaxed); + } + + /// Returns the last known GPU tick. + [[nodiscard]] u64 KnownGpuTick() const noexcept { + return gpu_tick.load(std::memory_order_relaxed); + } + + /// Returns the timeline semaphore handle. + [[nodiscard]] VkSemaphore Handle() const noexcept { + return *semaphore; + } + + /// Returns true when a tick has been hit by the GPU. + [[nodiscard]] bool IsFree(u64 tick) { + return gpu_tick.load(std::memory_order_relaxed) >= tick; + } + + /// Advance to the logical tick. + void NextTick() noexcept { + ++current_tick; + } + + /// Refresh the known GPU tick + void Refresh() { + gpu_tick.store(semaphore.GetCounter(), std::memory_order_relaxed); + } + + /// Waits for a tick to be hit on the GPU + void Wait(u64 tick) { + // No need to wait if the GPU is ahead of the tick + if (IsFree(tick)) { + return; + } + // Update the GPU tick and try again + Refresh(); + if (IsFree(tick)) { + return; + } + // If none of the above is hit, fallback to a regular wait + semaphore.Wait(tick); + } + +private: + vk::Semaphore semaphore; ///< Timeline semaphore. + std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. + std::atomic<u64> current_tick{1}; ///< Current logical tick. + std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed. + std::thread debug_thread; ///< Debug thread to workaround validation layer bugs. +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.cpp b/src/video_core/renderer_vulkan/vk_memory_manager.cpp deleted file mode 100644 index 24c8960ac..000000000 --- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <optional> -#include <tuple> -#include <vector> - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -namespace { - -u64 GetAllocationChunkSize(u64 required_size) { - static constexpr u64 sizes[] = {16ULL << 20, 32ULL << 20, 64ULL << 20, 128ULL << 20}; - auto it = std::lower_bound(std::begin(sizes), std::end(sizes), required_size); - return it != std::end(sizes) ? *it : Common::AlignUp(required_size, 256ULL << 20); -} - -} // Anonymous namespace - -class VKMemoryAllocation final { -public: - explicit VKMemoryAllocation(const VKDevice& device, vk::DeviceMemory memory, - VkMemoryPropertyFlags properties, u64 allocation_size, u32 type) - : device{device}, memory{std::move(memory)}, properties{properties}, - allocation_size{allocation_size}, shifted_type{ShiftType(type)} {} - - VKMemoryCommit Commit(VkDeviceSize commit_size, VkDeviceSize alignment) { - auto found = TryFindFreeSection(free_iterator, allocation_size, - static_cast<u64>(commit_size), static_cast<u64>(alignment)); - if (!found) { - found = TryFindFreeSection(0, free_iterator, static_cast<u64>(commit_size), - static_cast<u64>(alignment)); - if (!found) { - // Signal out of memory, it'll try to do more allocations. - return nullptr; - } - } - auto commit = std::make_unique<VKMemoryCommitImpl>(device, this, memory, *found, - *found + commit_size); - commits.push_back(commit.get()); - - // Last commit's address is highly probable to be free. - free_iterator = *found + commit_size; - - return commit; - } - - void Free(const VKMemoryCommitImpl* commit) { - ASSERT(commit); - - const auto it = std::find(std::begin(commits), std::end(commits), commit); - if (it == commits.end()) { - UNREACHABLE_MSG("Freeing unallocated commit!"); - return; - } - commits.erase(it); - } - - /// Returns whether this allocation is compatible with the arguments. - bool IsCompatible(VkMemoryPropertyFlags wanted_properties, u32 type_mask) const { - return (wanted_properties & properties) && (type_mask & shifted_type) != 0; - } - -private: - static constexpr u32 ShiftType(u32 type) { - return 1U << type; - } - - /// A memory allocator, it may return a free region between "start" and "end" with the solicited - /// requirements. - std::optional<u64> TryFindFreeSection(u64 start, u64 end, u64 size, u64 alignment) const { - u64 iterator = Common::AlignUp(start, alignment); - while (iterator + size <= end) { - const u64 try_left = iterator; - const u64 try_right = try_left + size; - - bool overlap = false; - for (const auto& commit : commits) { - const auto [commit_left, commit_right] = commit->interval; - if (try_left < commit_right && commit_left < try_right) { - // There's an overlap, continue the search where the overlapping commit ends. - iterator = Common::AlignUp(commit_right, alignment); - overlap = true; - break; - } - } - if (!overlap) { - // A free address has been found. - return try_left; - } - } - - // No free regions where found, return an empty optional. - return std::nullopt; - } - - const VKDevice& device; ///< Vulkan device. - const vk::DeviceMemory memory; ///< Vulkan memory allocation handler. - const VkMemoryPropertyFlags properties; ///< Vulkan properties. - const u64 allocation_size; ///< Size of this allocation. - const u32 shifted_type; ///< Stored Vulkan type of this allocation, shifted. - - /// Hints where the next free region is likely going to be. - u64 free_iterator{}; - - /// Stores all commits done from this allocation. - std::vector<const VKMemoryCommitImpl*> commits; -}; - -VKMemoryManager::VKMemoryManager(const VKDevice& device) - : device{device}, properties{device.GetPhysical().GetMemoryProperties()} {} - -VKMemoryManager::~VKMemoryManager() = default; - -VKMemoryCommit VKMemoryManager::Commit(const VkMemoryRequirements& requirements, - bool host_visible) { - const u64 chunk_size = GetAllocationChunkSize(requirements.size); - - // When a host visible commit is asked, search for host visible and coherent, otherwise search - // for a fast device local type. - const VkMemoryPropertyFlags wanted_properties = - host_visible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT - : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - - if (auto commit = TryAllocCommit(requirements, wanted_properties)) { - return commit; - } - - // Commit has failed, allocate more memory. - if (!AllocMemory(wanted_properties, requirements.memoryTypeBits, chunk_size)) { - // TODO(Rodrigo): Handle these situations in some way like flushing to guest memory. - // Allocation has failed, panic. - UNREACHABLE_MSG("Ran out of VRAM!"); - return {}; - } - - // Commit again, this time it won't fail since there's a fresh allocation above. If it does, - // there's a bug. - auto commit = TryAllocCommit(requirements, wanted_properties); - ASSERT(commit); - return commit; -} - -VKMemoryCommit VKMemoryManager::Commit(const vk::Buffer& buffer, bool host_visible) { - auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), host_visible); - buffer.BindMemory(commit->GetMemory(), commit->GetOffset()); - return commit; -} - -VKMemoryCommit VKMemoryManager::Commit(const vk::Image& image, bool host_visible) { - auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), host_visible); - image.BindMemory(commit->GetMemory(), commit->GetOffset()); - return commit; -} - -bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, - u64 size) { - const u32 type = [&] { - for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) { - const auto flags = properties.memoryTypes[type_index].propertyFlags; - if ((type_mask & (1U << type_index)) && (flags & wanted_properties)) { - // The type matches in type and in the wanted properties. - return type_index; - } - } - UNREACHABLE_MSG("Couldn't find a compatible memory type!"); - return 0U; - }(); - - // Try to allocate found type. - vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = nullptr, - .allocationSize = size, - .memoryTypeIndex = type, - }); - if (!memory) { - LOG_CRITICAL(Render_Vulkan, "Device allocation failed!"); - return false; - } - - allocations.push_back(std::make_unique<VKMemoryAllocation>(device, std::move(memory), - wanted_properties, size, type)); - return true; -} - -VKMemoryCommit VKMemoryManager::TryAllocCommit(const VkMemoryRequirements& requirements, - VkMemoryPropertyFlags wanted_properties) { - for (auto& allocation : allocations) { - if (!allocation->IsCompatible(wanted_properties, requirements.memoryTypeBits)) { - continue; - } - if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) { - return commit; - } - } - return {}; -} - -VKMemoryCommitImpl::VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation, - const vk::DeviceMemory& memory, u64 begin, u64 end) - : device{device}, memory{memory}, interval{begin, end}, allocation{allocation} {} - -VKMemoryCommitImpl::~VKMemoryCommitImpl() { - allocation->Free(this); -} - -MemoryMap VKMemoryCommitImpl::Map(u64 size, u64 offset_) const { - return MemoryMap{this, memory.Map(interval.first + offset_, size)}; -} - -void VKMemoryCommitImpl::Unmap() const { - memory.Unmap(); -} - -MemoryMap VKMemoryCommitImpl::Map() const { - return Map(interval.second - interval.first); -} - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.h b/src/video_core/renderer_vulkan/vk_memory_manager.h deleted file mode 100644 index 1af88e3d4..000000000 --- a/src/video_core/renderer_vulkan/vk_memory_manager.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <utility> -#include <vector> -#include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -class MemoryMap; -class VKDevice; -class VKMemoryAllocation; -class VKMemoryCommitImpl; - -using VKMemoryCommit = std::unique_ptr<VKMemoryCommitImpl>; - -class VKMemoryManager final { -public: - explicit VKMemoryManager(const VKDevice& device); - VKMemoryManager(const VKMemoryManager&) = delete; - ~VKMemoryManager(); - - /** - * Commits a memory with the specified requeriments. - * @param requirements Requirements returned from a Vulkan call. - * @param host_visible Signals the allocator that it *must* use host visible and coherent - * memory. When passing false, it will try to allocate device local memory. - * @returns A memory commit. - */ - VKMemoryCommit Commit(const VkMemoryRequirements& requirements, bool host_visible); - - /// Commits memory required by the buffer and binds it. - VKMemoryCommit Commit(const vk::Buffer& buffer, bool host_visible); - - /// Commits memory required by the image and binds it. - VKMemoryCommit Commit(const vk::Image& image, bool host_visible); - -private: - /// Allocates a chunk of memory. - bool AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, u64 size); - - /// Tries to allocate a memory commit. - VKMemoryCommit TryAllocCommit(const VkMemoryRequirements& requirements, - VkMemoryPropertyFlags wanted_properties); - - const VKDevice& device; ///< Device handler. - const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties. - std::vector<std::unique_ptr<VKMemoryAllocation>> allocations; ///< Current allocations. -}; - -class VKMemoryCommitImpl final { - friend VKMemoryAllocation; - friend MemoryMap; - -public: - explicit VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation, - const vk::DeviceMemory& memory, u64 begin, u64 end); - ~VKMemoryCommitImpl(); - - /// Maps a memory region and returns a pointer to it. - /// It's illegal to have more than one memory map at the same time. - MemoryMap Map(u64 size, u64 offset = 0) const; - - /// Maps the whole commit and returns a pointer to it. - /// It's illegal to have more than one memory map at the same time. - MemoryMap Map() const; - - /// Returns the Vulkan memory handler. - VkDeviceMemory GetMemory() const { - return *memory; - } - - /// Returns the start position of the commit relative to the allocation. - VkDeviceSize GetOffset() const { - return static_cast<VkDeviceSize>(interval.first); - } - -private: - /// Unmaps memory. - void Unmap() const; - - const VKDevice& device; ///< Vulkan device. - const vk::DeviceMemory& memory; ///< Vulkan device memory handler. - std::pair<u64, u64> interval{}; ///< Interval where the commit exists. - VKMemoryAllocation* allocation{}; ///< Pointer to the large memory allocation. -}; - -/// Holds ownership of a memory map. -class MemoryMap final { -public: - explicit MemoryMap(const VKMemoryCommitImpl* commit, u8* address) - : commit{commit}, address{address} {} - - ~MemoryMap() { - if (commit) { - commit->Unmap(); - } - } - - /// Prematurely releases the memory map. - void Release() { - commit->Unmap(); - commit = nullptr; - } - - /// Returns the address of the memory map. - u8* GetAddress() const { - return address; - } - - /// Returns the address of the memory map; - operator u8*() const { - return address; - } - -private: - const VKMemoryCommitImpl* commit{}; ///< Mapped memory commit. - u8* address{}; ///< Address to the mapped memory. -}; - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index cfdcdd6ab..8991505ca 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -7,6 +7,8 @@ #include <memory> #include <vector> +#include "common/bit_cast.h" +#include "common/cityhash.h" #include "common/microprofile.h" #include "core/core.h" #include "core/memory.h" @@ -17,18 +19,17 @@ #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/shader/compiler_settings.h" #include "video_core/shader/memory_util.h" #include "video_core/shader_cache.h" #include "video_core/shader_notify.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -51,7 +52,9 @@ constexpr VkDescriptorType STORAGE_TEXEL_BUFFER = VK_DESCRIPTOR_TYPE_STORAGE_TEX constexpr VkDescriptorType STORAGE_IMAGE = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; constexpr VideoCommon::Shader::CompilerSettings compiler_settings{ - VideoCommon::Shader::CompileDepth::FullDecompile}; + .depth = VideoCommon::Shader::CompileDepth::FullDecompile, + .disable_else_derivation = true, +}; constexpr std::size_t GetStageFromProgram(std::size_t program) { return program == 0 ? 0 : program - 1; @@ -74,7 +77,7 @@ ShaderType GetShaderType(Maxwell::ShaderProgram program) { case Maxwell::ShaderProgram::Fragment: return ShaderType::Fragment; default: - UNIMPLEMENTED_MSG("program={}", static_cast<u32>(program)); + UNIMPLEMENTED_MSG("program={}", program); return ShaderType::Vertex; } } @@ -135,64 +138,54 @@ bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) con return std::memcmp(&rhs, this, sizeof *this) == 0; } -Shader::Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, - VideoCommon::Shader::ProgramCode program_code, u32 main_offset) - : gpu_addr{gpu_addr}, program_code{std::move(program_code)}, - registry{stage, GetEngine(system, stage)}, shader_ir{this->program_code, main_offset, - compiler_settings, registry}, - entries{GenerateShaderEntries(shader_ir)} {} +Shader::Shader(Tegra::Engines::ConstBufferEngineInterface& engine_, ShaderType stage_, + GPUVAddr gpu_addr_, VAddr cpu_addr_, ProgramCode program_code_, u32 main_offset_) + : gpu_addr(gpu_addr_), program_code(std::move(program_code_)), registry(stage_, engine_), + shader_ir(program_code, main_offset_, compiler_settings, registry), + entries(GenerateShaderEntries(shader_ir)) {} Shader::~Shader() = default; -Tegra::Engines::ConstBufferEngineInterface& Shader::GetEngine(Core::System& system, - Tegra::Engines::ShaderType stage) { - if (stage == ShaderType::Compute) { - return system.GPU().KeplerCompute(); - } else { - return system.GPU().Maxwell3D(); - } -} - -VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer, - const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - VKRenderPassCache& renderpass_cache) - : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system}, device{device}, - scheduler{scheduler}, descriptor_pool{descriptor_pool}, - update_descriptor_queue{update_descriptor_queue}, renderpass_cache{renderpass_cache} {} +VKPipelineCache::VKPipelineCache(RasterizerVulkan& rasterizer_, Tegra::GPU& gpu_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_, const Device& device_, + VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_) + : VideoCommon::ShaderCache<Shader>{rasterizer_}, gpu{gpu_}, maxwell3d{maxwell3d_}, + kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, device{device_}, + scheduler{scheduler_}, descriptor_pool{descriptor_pool_}, update_descriptor_queue{ + update_descriptor_queue_} {} VKPipelineCache::~VKPipelineCache() = default; std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() { - const auto& gpu = system.GPU().Maxwell3D(); - std::array<Shader*, Maxwell::MaxShaderProgram> shaders{}; + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { const auto program{static_cast<Maxwell::ShaderProgram>(index)}; // Skip stages that are not enabled - if (!gpu.regs.IsShaderConfigEnabled(index)) { + if (!maxwell3d.regs.IsShaderConfigEnabled(index)) { continue; } - auto& memory_manager{system.GPU().MemoryManager()}; - const GPUVAddr program_addr{GetShaderAddress(system, program)}; - const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr); + const GPUVAddr gpu_addr{GetShaderAddress(maxwell3d, program)}; + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); ASSERT(cpu_addr); Shader* result = cpu_addr ? TryGet(*cpu_addr) : null_shader.get(); if (!result) { - const auto host_ptr{memory_manager.GetPointer(program_addr)}; + const u8* const host_ptr{gpu_memory.GetPointer(gpu_addr)}; // No shader found - create a new one - constexpr u32 stage_offset = STAGE_MAIN_OFFSET; + static constexpr u32 stage_offset = STAGE_MAIN_OFFSET; const auto stage = static_cast<ShaderType>(index == 0 ? 0 : index - 1); - ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, false); + ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, false); const std::size_t size_in_bytes = code.size() * sizeof(u64); - auto shader = std::make_unique<Shader>(system, stage, program_addr, std::move(code), - stage_offset); + auto shader = std::make_unique<Shader>(maxwell3d, stage, gpu_addr, *cpu_addr, + std::move(code), stage_offset); result = shader.get(); if (cpu_addr) { @@ -207,7 +200,8 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() { } VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline( - const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) { + const GraphicsPipelineCacheKey& key, u32 num_color_buffers, + VideoCommon::Shader::AsyncShaders& async_shaders) { MICROPROFILE_SCOPE(Vulkan_PipelineCache); if (last_graphics_pipeline && last_graphics_key == key) { @@ -215,16 +209,16 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline( } last_graphics_key = key; - if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(system.GPU())) { + if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(gpu)) { std::unique_lock lock{pipeline_cache}; const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); if (is_cache_miss) { - system.GPU().ShaderNotify().MarkSharderBuilding(); + gpu.ShaderNotify().MarkSharderBuilding(); LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); const auto [program, bindings] = DecompileShaders(key.fixed_state); async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool, - update_descriptor_queue, renderpass_cache, bindings, - program, key); + update_descriptor_queue, bindings, program, key, + num_color_buffers); } last_graphics_pipeline = pair->second.get(); return last_graphics_pipeline; @@ -233,13 +227,13 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline( const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); auto& entry = pair->second; if (is_cache_miss) { - system.GPU().ShaderNotify().MarkSharderBuilding(); + gpu.ShaderNotify().MarkSharderBuilding(); LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); const auto [program, bindings] = DecompileShaders(key.fixed_state); entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool, - update_descriptor_queue, renderpass_cache, key, - bindings, program); - system.GPU().ShaderNotify().MarkShaderComplete(); + update_descriptor_queue, key, bindings, + program, num_color_buffers); + gpu.ShaderNotify().MarkShaderComplete(); } last_graphics_pipeline = entry.get(); return last_graphics_pipeline; @@ -255,22 +249,21 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach } LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); - auto& memory_manager = system.GPU().MemoryManager(); - const auto program_addr = key.shader; + const GPUVAddr gpu_addr = key.shader; - const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); ASSERT(cpu_addr); Shader* shader = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get(); if (!shader) { // No shader found - create a new one - const auto host_ptr = memory_manager.GetPointer(program_addr); + const auto host_ptr = gpu_memory.GetPointer(gpu_addr); - ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, true); + ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, true); const std::size_t size_in_bytes = code.size() * sizeof(u64); - auto shader_info = std::make_unique<Shader>(system, ShaderType::Compute, program_addr, - std::move(code), KERNEL_MAIN_OFFSET); + auto shader_info = std::make_unique<Shader>(kepler_compute, ShaderType::Compute, gpu_addr, + *cpu_addr, std::move(code), KERNEL_MAIN_OFFSET); shader = shader_info.get(); if (cpu_addr) { @@ -298,7 +291,7 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach } void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) { - system.GPU().ShaderNotify().MarkShaderComplete(); + gpu.ShaderNotify().MarkShaderComplete(); std::unique_lock lock{pipeline_cache}; graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline); } @@ -339,12 +332,8 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) { std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { - auto& memory_manager = system.GPU().MemoryManager(); - const auto& gpu = system.GPU().Maxwell3D(); - Specialization specialization; - if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points || - device.IsExtExtendedDynamicStateSupported()) { + if (fixed_state.topology == Maxwell::PrimitiveTopology::Points) { float point_size; std::memcpy(&point_size, &fixed_state.point_size, sizeof(float)); specialization.point_size = point_size; @@ -356,20 +345,24 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { specialization.attribute_types[i] = attribute.Type(); } specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one; + specialization.early_fragment_tests = fixed_state.early_z; + + // Alpha test + specialization.alpha_test_func = + FixedPipelineState::UnpackComparisonOp(fixed_state.alpha_test_func.Value()); + specialization.alpha_test_ref = Common::BitCast<float>(fixed_state.alpha_test_ref); SPIRVProgram program; std::vector<VkDescriptorSetLayoutBinding> bindings; - for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { + for (std::size_t index = 1; index < Maxwell::MaxShaderProgram; ++index) { const auto program_enum = static_cast<Maxwell::ShaderProgram>(index); - // Skip stages that are not enabled - if (!gpu.regs.IsShaderConfigEnabled(index)) { + if (!maxwell3d.regs.IsShaderConfigEnabled(index)) { continue; } - - const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum); - const std::optional<VAddr> cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr); + const GPUVAddr gpu_addr = GetShaderAddress(maxwell3d, program_enum); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); Shader* const shader = cpu_addr ? TryGet(*cpu_addr) : null_shader.get(); const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5 @@ -377,12 +370,8 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { const auto& entries = shader->GetEntries(); program[stage] = { Decompile(device, shader->GetIR(), program_type, shader->GetRegistry(), specialization), - entries}; - - if (program_enum == Maxwell::ShaderProgram::VertexA) { - // VertexB was combined with VertexA, so we skip the VertexB iteration - ++index; - } + entries, + }; const u32 old_binding = specialization.base_binding; specialization.base_binding = diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index c04829e77..89d635a3d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -19,14 +19,13 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/shader/async_shaders.h" #include "video_core/shader/memory_util.h" #include "video_core/shader/registry.h" #include "video_core/shader/shader_ir.h" #include "video_core/shader_cache.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Core { class System; @@ -34,11 +33,10 @@ class System; namespace Vulkan { +class Device; class RasterizerVulkan; class VKComputePipeline; class VKDescriptorPool; -class VKDevice; -class VKFence; class VKScheduler; class VKUpdateDescriptorQueue; @@ -85,8 +83,9 @@ namespace Vulkan { class Shader { public: - explicit Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, - VideoCommon::Shader::ProgramCode program_code, u32 main_offset); + explicit Shader(Tegra::Engines::ConstBufferEngineInterface& engine_, + Tegra::Engines::ShaderType stage_, GPUVAddr gpu_addr, VAddr cpu_addr_, + VideoCommon::Shader::ProgramCode program_code, u32 main_offset_); ~Shader(); GPUVAddr GetGpuAddr() const { @@ -97,22 +96,19 @@ public: return shader_ir; } - const VideoCommon::Shader::Registry& GetRegistry() const { - return registry; - } - const VideoCommon::Shader::ShaderIR& GetIR() const { return shader_ir; } + const VideoCommon::Shader::Registry& GetRegistry() const { + return registry; + } + const ShaderEntries& GetEntries() const { return entries; } private: - static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system, - Tegra::Engines::ShaderType stage); - GPUVAddr gpu_addr{}; VideoCommon::Shader::ProgramCode program_code; VideoCommon::Shader::Registry registry; @@ -122,16 +118,18 @@ private: class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> { public: - explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer, - const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, - VKUpdateDescriptorQueue& update_descriptor_queue, - VKRenderPassCache& renderpass_cache); + explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu, + Tegra::Engines::Maxwell3D& maxwell3d, + Tegra::Engines::KeplerCompute& kepler_compute, + Tegra::MemoryManager& gpu_memory, const Device& device, + VKScheduler& scheduler, VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue); ~VKPipelineCache() override; std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders(); VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key, + u32 num_color_buffers, VideoCommon::Shader::AsyncShaders& async_shaders); VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); @@ -145,12 +143,15 @@ private: std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders( const FixedPipelineState& fixed_state); - Core::System& system; - const VKDevice& device; + Tegra::GPU& gpu; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; + + const Device& device; VKScheduler& scheduler; VKDescriptorPool& descriptor_pool; VKUpdateDescriptorQueue& update_descriptor_queue; - VKRenderPassCache& renderpass_cache; std::unique_ptr<Shader> null_shader; std::unique_ptr<Shader> null_kernel; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 6cd63d090..7cadd5147 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -7,37 +7,35 @@ #include <utility> #include <vector> -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_query_cache.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { +using VideoCore::QueryType; + namespace { constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION}; -constexpr VkQueryType GetTarget(VideoCore::QueryType type) { +constexpr VkQueryType GetTarget(QueryType type) { return QUERY_TARGETS[static_cast<std::size_t>(type)]; } } // Anonymous namespace -QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {} +QueryPool::QueryPool(const Device& device_, VKScheduler& scheduler, QueryType type_) + : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {} QueryPool::~QueryPool() = default; -void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) { - device = &device_; - type = type_; -} - -std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) { +std::pair<VkQueryPool, u32> QueryPool::Commit() { std::size_t index; do { - index = CommitResource(fence); + index = CommitResource(); } while (usage[index]); usage[index] = true; @@ -47,7 +45,7 @@ std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) { void QueryPool::Allocate(std::size_t begin, std::size_t end) { usage.resize(end); - pools.push_back(device->GetLogical().CreateQueryPool({ + pools.push_back(device.GetLogical().CreateQueryPool({ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -68,32 +66,39 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; } -VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKScheduler& scheduler) - : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter, - QueryPool>{system, rasterizer}, - device{device}, scheduler{scheduler} { - for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) { - query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i)); +VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_, + const Device& device_, VKScheduler& scheduler_) + : QueryCacheBase{rasterizer_, maxwell3d_, gpu_memory_}, device{device_}, scheduler{scheduler_}, + query_pools{ + QueryPool{device_, scheduler_, QueryType::SamplesPassed}, + } {} + +VKQueryCache::~VKQueryCache() { + // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class + // destructor is called. The query cache should be redesigned to have a proper ownership model + // instead of using shared pointers. + for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) { + auto& stream = Stream(static_cast<QueryType>(query_type)); + stream.Update(false); + stream.Reset(); } } -VKQueryCache::~VKQueryCache() = default; - -std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(VideoCore::QueryType type) { - return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence()); +std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(QueryType type) { + return query_pools[static_cast<std::size_t>(type)].Commit(); } -void VKQueryCache::Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query) { +void VKQueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) { query_pools[static_cast<std::size_t>(type)].Reserve(query); } -HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency, - VideoCore::QueryType type) - : VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache}, - type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} { - const vk::Device* logical = &cache.Device().GetLogical(); - cache.Scheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) { +HostCounter::HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_, + QueryType type_) + : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, + query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} { + const vk::Device* logical = &cache.GetDevice().GetLogical(); + cache.GetScheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) { logical->ResetQueryPoolEXT(query.first, query.second, 1); cmdbuf.BeginQuery(query.first, query.second, VK_QUERY_CONTROL_PRECISE_BIT); }); @@ -104,26 +109,28 @@ HostCounter::~HostCounter() { } void HostCounter::EndQuery() { - cache.Scheduler().Record( + cache.GetScheduler().Record( [query = query](vk::CommandBuffer cmdbuf) { cmdbuf.EndQuery(query.first, query.second); }); } u64 HostCounter::BlockingQuery() const { - if (ticks >= cache.Scheduler().Ticks()) { - cache.Scheduler().Flush(); + if (tick >= cache.GetScheduler().CurrentTick()) { + cache.GetScheduler().Flush(); } + u64 data; - const VkResult result = cache.Device().GetLogical().GetQueryResults( + const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( query.first, query.second, 1, sizeof(data), &data, sizeof(data), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); - switch (result) { + + switch (query_result) { case VK_SUCCESS: return data; case VK_ERROR_DEVICE_LOST: - cache.Device().ReportLoss(); + cache.GetDevice().ReportLoss(); [[fallthrough]]; default: - throw vk::Exception(result); + throw vk::Exception(query_result); } } diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index 40119e6d3..7190946b9 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h @@ -11,8 +11,8 @@ #include "common/common_types.h" #include "video_core/query_cache.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace VideoCore { class RasterizerInterface; @@ -21,21 +21,19 @@ class RasterizerInterface; namespace Vulkan { class CachedQuery; +class Device; class HostCounter; -class VKDevice; class VKQueryCache; class VKScheduler; using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>; -class QueryPool final : public VKFencedPool { +class QueryPool final : public ResourcePool { public: - explicit QueryPool(); + explicit QueryPool(const Device& device, VKScheduler& scheduler, VideoCore::QueryType type); ~QueryPool() override; - void Initialize(const VKDevice& device, VideoCore::QueryType type); - - std::pair<VkQueryPool, u32> Commit(VKFence& fence); + std::pair<VkQueryPool, u32> Commit(); void Reserve(std::pair<VkQueryPool, u32> query); @@ -45,42 +43,43 @@ protected: private: static constexpr std::size_t GROW_STEP = 512; - const VKDevice* device = nullptr; - VideoCore::QueryType type = {}; + const Device& device; + const VideoCore::QueryType type; std::vector<vk::QueryPool> pools; std::vector<bool> usage; }; class VKQueryCache final - : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter, - QueryPool> { + : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter> { public: - explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKScheduler& scheduler); + explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_, + const Device& device_, VKScheduler& scheduler_); ~VKQueryCache(); std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type); void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query); - const VKDevice& Device() const noexcept { + const Device& GetDevice() const noexcept { return device; } - VKScheduler& Scheduler() const noexcept { + VKScheduler& GetScheduler() const noexcept { return scheduler; } private: - const VKDevice& device; + const Device& device; VKScheduler& scheduler; + std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; }; class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> { public: - explicit HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency, - VideoCore::QueryType type); + explicit HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_, + VideoCore::QueryType type_); ~HostCounter(); void EndQuery(); @@ -91,13 +90,13 @@ private: VKQueryCache& cache; const VideoCore::QueryType type; const std::pair<VkQueryPool, u32> query; - const u64 ticks; + const u64 tick; }; class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> { public: - explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr, u8* host_ptr) - : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr} {} + explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_) + : CachedQueryBase{cpu_addr_, host_ptr_} {} }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index ff1b52eab..394a1c4e9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -8,8 +8,6 @@ #include <mutex> #include <vector> -#include <boost/container/static_vector.hpp> - #include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" @@ -19,54 +17,54 @@ #include "core/settings.h" #include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" -#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/vk_sampler_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/shader_cache.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { using Maxwell = Tegra::Engines::Maxwell3D::Regs; +using VideoCommon::ImageViewId; +using VideoCommon::ImageViewType; MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192)); MICROPROFILE_DEFINE(Vulkan_Drawing, "Vulkan", "Record drawing", MP_RGB(192, 128, 128)); MICROPROFILE_DEFINE(Vulkan_Compute, "Vulkan", "Record compute", MP_RGB(192, 128, 128)); MICROPROFILE_DEFINE(Vulkan_Clearing, "Vulkan", "Record clearing", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_Geometry, "Vulkan", "Setup geometry", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_ConstBuffers, "Vulkan", "Setup constant buffers", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_GlobalBuffers, "Vulkan", "Setup global buffers", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_RenderTargets, "Vulkan", "Setup render targets", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_Textures, "Vulkan", "Setup textures", MP_RGB(192, 128, 128)); -MICROPROFILE_DEFINE(Vulkan_Images, "Vulkan", "Setup images", MP_RGB(192, 128, 128)); MICROPROFILE_DEFINE(Vulkan_PipelineCache, "Vulkan", "Pipeline cache", MP_RGB(192, 128, 128)); namespace { +struct DrawParams { + u32 base_instance; + u32 num_instances; + u32 base_vertex; + u32 num_vertices; + bool is_indexed; +}; -constexpr auto ComputeShaderIndex = static_cast<std::size_t>(Tegra::Engines::ShaderType::Compute); +constexpr auto COMPUTE_SHADER_INDEX = static_cast<size_t>(Tegra::Engines::ShaderType::Compute); -VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::size_t index) { +VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) { const auto& src = regs.viewport_transform[index]; const float width = src.scale_x * 2.0f; const float height = src.scale_y * 2.0f; const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; - VkViewport viewport{ .x = src.translate_x - src.scale_x, .y = src.translate_y - src.scale_y, @@ -75,16 +73,14 @@ VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::si .minDepth = src.translate_z - src.scale_z * reduce_z, .maxDepth = src.translate_z + src.scale_z, }; - if (!device.IsExtDepthRangeUnrestrictedSupported()) { viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f); viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f); } - return viewport; } -VkRect2D GetScissorState(const Maxwell& regs, std::size_t index) { +VkRect2D GetScissorState(const Maxwell& regs, size_t index) { const auto& src = regs.scissor_test[index]; VkRect2D scissor; if (src.enable) { @@ -104,305 +100,157 @@ VkRect2D GetScissorState(const Maxwell& regs, std::size_t index) { std::array<GPUVAddr, Maxwell::MaxShaderProgram> GetShaderAddresses( const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) { std::array<GPUVAddr, Maxwell::MaxShaderProgram> addresses; - for (std::size_t i = 0; i < std::size(addresses); ++i) { + for (size_t i = 0; i < std::size(addresses); ++i) { addresses[i] = shaders[i] ? shaders[i]->GetGpuAddr() : 0; } return addresses; } -void TransitionImages(const std::vector<ImageView>& views, VkPipelineStageFlags pipeline_stage, - VkAccessFlags access) { - for (auto& [view, layout] : views) { - view->Transition(*layout, pipeline_stage, access); +struct TextureHandle { + constexpr TextureHandle(u32 data, bool via_header_index) { + const Tegra::Texture::TextureHandle handle{data}; + image = handle.tic_id; + sampler = via_header_index ? image : handle.tsc_id.Value(); } -} + + u32 image; + u32 sampler; +}; template <typename Engine, typename Entry> -Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry, - std::size_t stage, std::size_t index = 0) { - const auto stage_type = static_cast<Tegra::Engines::ShaderType>(stage); +TextureHandle GetTextureInfo(const Engine& engine, bool via_header_index, const Entry& entry, + size_t stage, size_t index = 0) { + const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage); if constexpr (std::is_same_v<Entry, SamplerEntry>) { if (entry.is_separated) { const u32 buffer_1 = entry.buffer; const u32 buffer_2 = entry.secondary_buffer; const u32 offset_1 = entry.offset; const u32 offset_2 = entry.secondary_offset; - const u32 handle_1 = engine.AccessConstBuffer32(stage_type, buffer_1, offset_1); - const u32 handle_2 = engine.AccessConstBuffer32(stage_type, buffer_2, offset_2); - return engine.GetTextureInfo(handle_1 | handle_2); + const u32 handle_1 = engine.AccessConstBuffer32(shader_type, buffer_1, offset_1); + const u32 handle_2 = engine.AccessConstBuffer32(shader_type, buffer_2, offset_2); + return TextureHandle(handle_1 | handle_2, via_header_index); } } if (entry.is_bindless) { - const auto tex_handle = engine.AccessConstBuffer32(stage_type, entry.buffer, entry.offset); - return engine.GetTextureInfo(tex_handle); - } - const auto& gpu_profile = engine.AccessGuestDriverProfile(); - const u32 entry_offset = static_cast<u32>(index * gpu_profile.GetTextureHandlerSize()); - const u32 offset = entry.offset + entry_offset; - if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) { - return engine.GetStageTexture(stage_type, offset); - } else { - return engine.GetTexture(offset); - } -} - -/// @brief Determine if an attachment to be updated has to preserve contents -/// @param is_clear True when a clear is being executed -/// @param regs 3D registers -/// @return True when the contents have to be preserved -bool HasToPreserveColorContents(bool is_clear, const Maxwell& regs) { - if (!is_clear) { - return true; - } - // First we have to make sure all clear masks are enabled. - if (!regs.clear_buffers.R || !regs.clear_buffers.G || !regs.clear_buffers.B || - !regs.clear_buffers.A) { - return true; - } - // If scissors are disabled, the whole screen is cleared - if (!regs.clear_flags.scissor) { - return false; - } - // Then we have to confirm scissor testing clears the whole image - const std::size_t index = regs.clear_buffers.RT; - const auto& scissor = regs.scissor_test[0]; - return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.rt[index].width || - scissor.max_y < regs.rt[index].height; -} - -/// @brief Determine if an attachment to be updated has to preserve contents -/// @param is_clear True when a clear is being executed -/// @param regs 3D registers -/// @return True when the contents have to be preserved -bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) { - // If we are not clearing, the contents have to be preserved - if (!is_clear) { - return true; - } - // For depth stencil clears we only have to confirm scissor test covers the whole image - if (!regs.clear_flags.scissor) { - return false; + const u32 raw = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset); + return TextureHandle(raw, via_header_index); + } + const u32 buffer = engine.GetBoundBuffer(); + const u64 offset = (entry.offset + index) * sizeof(u32); + return TextureHandle(engine.AccessConstBuffer32(shader_type, buffer, offset), via_header_index); +} + +ImageViewType ImageViewTypeFromEntry(const SamplerEntry& entry) { + if (entry.is_buffer) { + return ImageViewType::e2D; + } + switch (entry.type) { + case Tegra::Shader::TextureType::Texture1D: + return entry.is_array ? ImageViewType::e1DArray : ImageViewType::e1D; + case Tegra::Shader::TextureType::Texture2D: + return entry.is_array ? ImageViewType::e2DArray : ImageViewType::e2D; + case Tegra::Shader::TextureType::Texture3D: + return ImageViewType::e3D; + case Tegra::Shader::TextureType::TextureCube: + return entry.is_array ? ImageViewType::CubeArray : ImageViewType::Cube; + } + UNREACHABLE(); + return ImageViewType::e2D; +} + +ImageViewType ImageViewTypeFromEntry(const ImageEntry& entry) { + switch (entry.type) { + case Tegra::Shader::ImageType::Texture1D: + return ImageViewType::e1D; + case Tegra::Shader::ImageType::Texture1DArray: + return ImageViewType::e1DArray; + case Tegra::Shader::ImageType::Texture2D: + return ImageViewType::e2D; + case Tegra::Shader::ImageType::Texture2DArray: + return ImageViewType::e2DArray; + case Tegra::Shader::ImageType::Texture3D: + return ImageViewType::e3D; + case Tegra::Shader::ImageType::TextureBuffer: + return ImageViewType::Buffer; + } + UNREACHABLE(); + return ImageViewType::e2D; +} + +void PushImageDescriptors(const ShaderEntries& entries, TextureCache& texture_cache, + VKUpdateDescriptorQueue& update_descriptor_queue, + ImageViewId*& image_view_id_ptr, VkSampler*& sampler_ptr) { + for ([[maybe_unused]] const auto& entry : entries.uniform_texels) { + const ImageViewId image_view_id = *image_view_id_ptr++; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + update_descriptor_queue.AddTexelBuffer(image_view.BufferView()); } - // Make sure the clear cover the whole image - const auto& scissor = regs.scissor_test[0]; - return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.zeta_width || - scissor.max_y < regs.zeta_height; -} - -template <std::size_t N> -std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) { - std::array<VkDeviceSize, N> expanded; - std::copy(strides.begin(), strides.end(), expanded.begin()); - return expanded; -} - -} // Anonymous namespace - -class BufferBindings final { -public: - void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) { - vertex.buffers[vertex.num_buffers] = buffer; - vertex.offsets[vertex.num_buffers] = offset; - vertex.sizes[vertex.num_buffers] = size; - vertex.strides[vertex.num_buffers] = static_cast<u16>(stride); - ++vertex.num_buffers; - } - - void SetIndexBinding(VkBuffer buffer, VkDeviceSize offset, VkIndexType type) { - index.buffer = buffer; - index.offset = offset; - index.type = type; - } - - void Bind(const VKDevice& device, VKScheduler& scheduler) const { - // Use this large switch case to avoid dispatching more memory in the record lambda than - // what we need. It looks horrible, but it's the best we can do on standard C++. - switch (vertex.num_buffers) { - case 0: - return BindStatic<0>(device, scheduler); - case 1: - return BindStatic<1>(device, scheduler); - case 2: - return BindStatic<2>(device, scheduler); - case 3: - return BindStatic<3>(device, scheduler); - case 4: - return BindStatic<4>(device, scheduler); - case 5: - return BindStatic<5>(device, scheduler); - case 6: - return BindStatic<6>(device, scheduler); - case 7: - return BindStatic<7>(device, scheduler); - case 8: - return BindStatic<8>(device, scheduler); - case 9: - return BindStatic<9>(device, scheduler); - case 10: - return BindStatic<10>(device, scheduler); - case 11: - return BindStatic<11>(device, scheduler); - case 12: - return BindStatic<12>(device, scheduler); - case 13: - return BindStatic<13>(device, scheduler); - case 14: - return BindStatic<14>(device, scheduler); - case 15: - return BindStatic<15>(device, scheduler); - case 16: - return BindStatic<16>(device, scheduler); - case 17: - return BindStatic<17>(device, scheduler); - case 18: - return BindStatic<18>(device, scheduler); - case 19: - return BindStatic<19>(device, scheduler); - case 20: - return BindStatic<20>(device, scheduler); - case 21: - return BindStatic<21>(device, scheduler); - case 22: - return BindStatic<22>(device, scheduler); - case 23: - return BindStatic<23>(device, scheduler); - case 24: - return BindStatic<24>(device, scheduler); - case 25: - return BindStatic<25>(device, scheduler); - case 26: - return BindStatic<26>(device, scheduler); - case 27: - return BindStatic<27>(device, scheduler); - case 28: - return BindStatic<28>(device, scheduler); - case 29: - return BindStatic<29>(device, scheduler); - case 30: - return BindStatic<30>(device, scheduler); - case 31: - return BindStatic<31>(device, scheduler); - case 32: - return BindStatic<32>(device, scheduler); - } - UNREACHABLE(); - } - -private: - // Some of these fields are intentionally left uninitialized to avoid initializing them twice. - struct { - std::size_t num_buffers = 0; - std::array<VkBuffer, Maxwell::NumVertexArrays> buffers; - std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets; - std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes; - std::array<u16, Maxwell::NumVertexArrays> strides; - } vertex; - - struct { - VkBuffer buffer = nullptr; - VkDeviceSize offset; - VkIndexType type; - } index; - - template <std::size_t N> - void BindStatic(const VKDevice& device, VKScheduler& scheduler) const { - if (device.IsExtExtendedDynamicStateSupported()) { - if (index.buffer) { - BindStatic<N, true, true>(scheduler); - } else { - BindStatic<N, false, true>(scheduler); - } - } else { - if (index.buffer) { - BindStatic<N, true, false>(scheduler); - } else { - BindStatic<N, false, false>(scheduler); - } + for (const auto& entry : entries.samplers) { + for (size_t i = 0; i < entry.size; ++i) { + const VkSampler sampler = *sampler_ptr++; + const ImageViewId image_view_id = *image_view_id_ptr++; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + const VkImageView handle = image_view.Handle(ImageViewTypeFromEntry(entry)); + update_descriptor_queue.AddSampledImage(handle, sampler); } } - - template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state> - void BindStatic(VKScheduler& scheduler) const { - static_assert(N <= Maxwell::NumVertexArrays); - if constexpr (N == 0) { - return; - } - - std::array<VkBuffer, N> buffers; - std::array<VkDeviceSize, N> offsets; - std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin()); - std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin()); - - if constexpr (has_extended_dynamic_state) { - // With extended dynamic states we can specify the length and stride of a vertex buffer - std::array<VkDeviceSize, N> sizes; - std::array<u16, N> strides; - std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin()); - std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin()); - - if constexpr (is_indexed) { - scheduler.Record( - [buffers, offsets, sizes, strides, index = index](vk::CommandBuffer cmdbuf) { - cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type); - cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), - offsets.data(), sizes.data(), - ExpandStrides(strides).data()); - }); - } else { - scheduler.Record([buffers, offsets, sizes, strides](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), - offsets.data(), sizes.data(), - ExpandStrides(strides).data()); - }); - } - return; - } - - if constexpr (is_indexed) { - // Indexed draw - scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) { - cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type); - cmdbuf.BindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data()); - }); - } else { - // Array draw - scheduler.Record([buffers, offsets](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data()); - }); - } + for ([[maybe_unused]] const auto& entry : entries.storage_texels) { + const ImageViewId image_view_id = *image_view_id_ptr++; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + update_descriptor_queue.AddTexelBuffer(image_view.BufferView()); } -}; - -void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const { - if (is_indexed) { - cmdbuf.DrawIndexed(num_vertices, num_instances, 0, base_vertex, base_instance); - } else { - cmdbuf.Draw(num_vertices, num_instances, base_vertex, base_instance); + for (const auto& entry : entries.images) { + // TODO: Mark as modified + const ImageViewId image_view_id = *image_view_id_ptr++; + const ImageView& image_view = texture_cache.GetImageView(image_view_id); + const VkImageView handle = image_view.Handle(ImageViewTypeFromEntry(entry)); + update_descriptor_queue.AddImage(handle); + } +} + +DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_instanced, + bool is_indexed) { + DrawParams params{ + .base_instance = regs.vb_base_instance, + .num_instances = is_instanced ? num_instances : 1, + .base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first, + .num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count, + .is_indexed = is_indexed, + }; + if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) { + // 6 triangle vertices per quad, base vertex is part of the index + // See BindQuadArrayIndexBuffer for more details + params.num_vertices = (params.num_vertices / 4) * 6; + params.base_vertex = 0; + params.is_indexed = true; } + return params; } +} // Anonymous namespace -RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer, - VKScreenInfo& screen_info, const VKDevice& device, - VKResourceManager& resource_manager, - VKMemoryManager& memory_manager, StateTracker& state_tracker, - VKScheduler& scheduler) - : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer}, - screen_info{screen_info}, device{device}, resource_manager{resource_manager}, - memory_manager{memory_manager}, state_tracker{state_tracker}, scheduler{scheduler}, - staging_pool(device, memory_manager, scheduler), descriptor_pool(device), - update_descriptor_queue(device, scheduler), renderpass_cache(device), - quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), - quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), - uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), - texture_cache(system, *this, device, resource_manager, memory_manager, scheduler, - staging_pool), - pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue, - renderpass_cache), - buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), - sampler_cache(device), - fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache), - query_cache(system, *this, device, scheduler), - wfi_event{device.GetLogical().CreateNewEvent()}, async_shaders{renderer} { +RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_, + Tegra::MemoryManager& gpu_memory_, + Core::Memory::Memory& cpu_memory_, VKScreenInfo& screen_info_, + const Device& device_, MemoryAllocator& memory_allocator_, + StateTracker& state_tracker_, VKScheduler& scheduler_) + : RasterizerAccelerated{cpu_memory_}, gpu{gpu_}, + gpu_memory{gpu_memory_}, maxwell3d{gpu.Maxwell3D()}, kepler_compute{gpu.KeplerCompute()}, + screen_info{screen_info_}, device{device_}, memory_allocator{memory_allocator_}, + state_tracker{state_tracker_}, scheduler{scheduler_}, + staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler), + update_descriptor_queue(device, scheduler), + blit_image(device, scheduler, state_tracker, descriptor_pool), + texture_cache_runtime{device, scheduler, memory_allocator, staging_pool, blit_image}, + texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory), + buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool, + update_descriptor_queue, descriptor_pool), + buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime), + pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler, + descriptor_pool, update_descriptor_queue), + query_cache{*this, maxwell3d, gpu_memory, device, scheduler}, + fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), + wfi_event(device.GetLogical().CreateEvent()), async_shaders(emu_window_) { scheduler.SetQueryCache(query_cache); if (device.UseAsynchronousShaders()) { async_shaders.AllocateWorkers(); @@ -414,64 +262,57 @@ RasterizerVulkan::~RasterizerVulkan() = default; void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { MICROPROFILE_SCOPE(Vulkan_Drawing); + SCOPE_EXIT({ gpu.TickWork(); }); FlushWork(); query_cache.UpdateCounters(); - SCOPE_EXIT({ system.GPU().TickWork(); }); - - const auto& gpu = system.GPU().Maxwell3D(); - GraphicsPipelineCacheKey key; - key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); - - buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed)); + graphics_key.fixed_state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported()); - BufferBindings buffer_bindings; - const DrawParameters draw_params = - SetupGeometry(key.fixed_state, buffer_bindings, is_indexed, is_instanced); + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; - update_descriptor_queue.Acquire(); - sampled_views.clear(); - image_views.clear(); + texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.UpdateRenderTargets(false); const auto shaders = pipeline_cache.GetShaders(); - key.shaders = GetShaderAddresses(shaders); - SetupShaderDescriptors(shaders); - - buffer_cache.Unmap(); + graphics_key.shaders = GetShaderAddresses(shaders); - const Texceptions texceptions = UpdateAttachments(false); - SetupImageTransitions(texceptions, color_attachments, zeta_attachment); + graphics_key.shaders = GetShaderAddresses(shaders); + SetupShaderDescriptors(shaders, is_indexed); - key.renderpass_params = GetRenderPassParams(texceptions); - key.padding = 0; + const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); + graphics_key.renderpass = framebuffer->RenderPass(); - auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders); + VKGraphicsPipeline* const pipeline = pipeline_cache.GetGraphicsPipeline( + graphics_key, framebuffer->NumColorBuffers(), async_shaders); if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) { // Async graphics pipeline was not ready. return; } - scheduler.BindGraphicsPipeline(pipeline->GetHandle()); - - const auto renderpass = pipeline->GetRenderPass(); - const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); - scheduler.RequestRenderpass(renderpass, framebuffer, render_area); + BeginTransformFeedback(); + scheduler.RequestRenderpass(framebuffer); + scheduler.BindGraphicsPipeline(pipeline->GetHandle()); UpdateDynamicStates(); - buffer_bindings.Bind(device, scheduler); - - BeginTransformFeedback(); - - const auto pipeline_layout = pipeline->GetLayout(); - const auto descriptor_set = pipeline->CommitDescriptorSet(); + const auto& regs = maxwell3d.regs; + const u32 num_instances = maxwell3d.mme_draw.instance_count; + const DrawParams draw_params = MakeDrawParams(regs, num_instances, is_instanced, is_indexed); + const VkPipelineLayout pipeline_layout = pipeline->GetLayout(); + const VkDescriptorSet descriptor_set = pipeline->CommitDescriptorSet(); scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) { if (descriptor_set) { cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, - DESCRIPTOR_SET, descriptor_set, {}); + DESCRIPTOR_SET, descriptor_set, nullptr); + } + if (draw_params.is_indexed) { + cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances, 0, + draw_params.base_vertex, draw_params.base_instance); + } else { + cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances, + draw_params.base_vertex, draw_params.base_instance); } - draw_params.Draw(cmdbuf); }); EndTransformFeedback(); @@ -480,17 +321,13 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { void RasterizerVulkan::Clear() { MICROPROFILE_SCOPE(Vulkan_Clearing); - const auto& gpu = system.GPU().Maxwell3D(); - if (!system.GPU().Maxwell3D().ShouldExecute()) { + if (!maxwell3d.ShouldExecute()) { return; } - sampled_views.clear(); - image_views.clear(); - query_cache.UpdateCounters(); - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B || regs.clear_buffers.A; const bool use_depth = regs.clear_buffers.Z; @@ -499,20 +336,24 @@ void RasterizerVulkan::Clear() { return; } - [[maybe_unused]] const auto texceptions = UpdateAttachments(true); - DEBUG_ASSERT(texceptions.none()); - SetupImageTransitions(0, color_attachments, zeta_attachment); + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.UpdateRenderTargets(true); + const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); + const VkExtent2D render_area = framebuffer->RenderArea(); + scheduler.RequestRenderpass(framebuffer); - const VkRenderPass renderpass = renderpass_cache.GetRenderPass(GetRenderPassParams(0)); - const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); - scheduler.RequestRenderpass(renderpass, framebuffer, render_area); - - VkClearRect clear_rect; - clear_rect.baseArrayLayer = regs.clear_buffers.layer; - clear_rect.layerCount = 1; - clear_rect.rect = GetScissorState(regs, 0); - clear_rect.rect.extent.width = std::min(clear_rect.rect.extent.width, render_area.width); - clear_rect.rect.extent.height = std::min(clear_rect.rect.extent.height, render_area.height); + VkClearRect clear_rect{ + .rect = GetScissorState(regs, 0), + .baseArrayLayer = regs.clear_buffers.layer, + .layerCount = 1, + }; + if (clear_rect.rect.extent.width == 0 || clear_rect.rect.extent.height == 0) { + return; + } + clear_rect.rect.extent = VkExtent2D{ + .width = std::min(clear_rect.rect.extent.width, render_area.width), + .height = std::min(clear_rect.rect.extent.height, render_area.height), + }; if (use_color) { VkClearValue clear_value; @@ -539,7 +380,6 @@ void RasterizerVulkan::Clear() { if (use_stencil) { aspect_flags |= VK_IMAGE_ASPECT_STENCIL_BIT; } - scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { VkClearAttachment attachment; @@ -553,51 +393,69 @@ void RasterizerVulkan::Clear() { void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) { MICROPROFILE_SCOPE(Vulkan_Compute); - update_descriptor_queue.Acquire(); - sampled_views.clear(); - image_views.clear(); query_cache.UpdateCounters(); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + const auto& launch_desc = kepler_compute.launch_description; auto& pipeline = pipeline_cache.GetComputePipeline({ .shader = code_addr, .shared_memory_size = launch_desc.shared_alloc, - .workgroup_size = - { - launch_desc.block_dim_x, - launch_desc.block_dim_y, - launch_desc.block_dim_z, - }, + .workgroup_size{ + launch_desc.block_dim_x, + launch_desc.block_dim_y, + launch_desc.block_dim_z, + }, }); // Compute dispatches can't be executed inside a renderpass scheduler.RequestOutsideRenderPassOperationContext(); - buffer_cache.Map(CalculateComputeStreamBufferSize()); + image_view_indices.clear(); + sampler_handles.clear(); + + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; const auto& entries = pipeline.GetEntries(); - SetupComputeConstBuffers(entries); - SetupComputeGlobalBuffers(entries); + buffer_cache.SetEnabledComputeUniformBuffers(entries.enabled_uniform_buffers); + buffer_cache.UnbindComputeStorageBuffers(); + u32 ssbo_index = 0; + for (const auto& buffer : entries.global_buffers) { + buffer_cache.BindComputeStorageBuffer(ssbo_index, buffer.cbuf_index, buffer.cbuf_offset, + buffer.is_written); + ++ssbo_index; + } + buffer_cache.UpdateComputeBuffers(); + + texture_cache.SynchronizeComputeDescriptors(); + SetupComputeUniformTexels(entries); SetupComputeTextures(entries); SetupComputeStorageTexels(entries); SetupComputeImages(entries); - buffer_cache.Unmap(); + const std::span indices_span(image_view_indices.data(), image_view_indices.size()); + texture_cache.FillComputeImageViews(indices_span, image_view_ids); + + update_descriptor_queue.Acquire(); - TransitionImages(sampled_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_SHADER_READ_BIT); - TransitionImages(image_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + buffer_cache.BindHostComputeBuffers(); + ImageViewId* image_view_id_ptr = image_view_ids.data(); + VkSampler* sampler_ptr = sampler_handles.data(); + PushImageDescriptors(entries, texture_cache, update_descriptor_queue, image_view_id_ptr, + sampler_ptr); + + const VkPipeline pipeline_handle = pipeline.GetHandle(); + const VkPipelineLayout pipeline_layout = pipeline.GetLayout(); + const VkDescriptorSet descriptor_set = pipeline.CommitDescriptorSet(); scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y, - grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(), - layout = pipeline.GetLayout(), - descriptor_set = pipeline.CommitDescriptorSet()](vk::CommandBuffer cmdbuf) { + grid_z = launch_desc.grid_dim_z, pipeline_handle, pipeline_layout, + descriptor_set](vk::CommandBuffer cmdbuf) { cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_handle); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, DESCRIPTOR_SET, - descriptor_set, {}); + if (descriptor_set) { + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, + DESCRIPTOR_SET, descriptor_set, nullptr); + } cmdbuf.Dispatch(grid_x, grid_y, grid_z); }); } @@ -611,31 +469,50 @@ void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type, query_cache.Query(gpu_addr, type, timestamp); } +void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, + u32 size) { + buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); +} + void RasterizerVulkan::FlushAll() {} void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.FlushRegion(addr, size); - buffer_cache.FlushRegion(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.DownloadMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.DownloadMemory(addr, size); + } query_cache.FlushRegion(addr, size); } bool RasterizerVulkan::MustFlushRegion(VAddr addr, u64 size) { + std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex}; if (!Settings::IsGPULevelHigh()) { - return buffer_cache.MustFlushRegion(addr, size); + return buffer_cache.IsRegionGpuModified(addr, size); } - return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size); + return texture_cache.IsRegionGpuModified(addr, size) || + buffer_cache.IsRegionGpuModified(addr, size); } void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.InvalidateRegion(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.WriteMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.WriteMemory(addr, size); + } pipeline_cache.InvalidateRegion(addr, size); - buffer_cache.InvalidateRegion(addr, size); query_cache.InvalidateRegion(addr, size); } @@ -643,28 +520,46 @@ void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - texture_cache.OnCPUWrite(addr, size); pipeline_cache.OnCPUWrite(addr, size); - buffer_cache.OnCPUWrite(addr, size); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.WriteMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.CachedWriteMemory(addr, size); + } } void RasterizerVulkan::SyncGuestHost() { - texture_cache.SyncGuestHost(); - buffer_cache.SyncGuestHost(); pipeline_cache.SyncGuestHost(); + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.FlushCachedWrites(); + } +} + +void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) { + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.UnmapMemory(addr, size); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.WriteMemory(addr, size); + } + pipeline_cache.OnCPUWrite(addr, size); } void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { - gpu.MemoryManager().Write<u32>(addr, value); + gpu_memory.Write<u32>(addr, value); return; } fence_manager.SignalSemaphore(addr, value); } void RasterizerVulkan::SignalSyncPoint(u32 value) { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { gpu.IncrementSyncPoint(value); return; @@ -673,7 +568,6 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) { } void RasterizerVulkan::ReleaseFences() { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { return; } @@ -707,6 +601,14 @@ void RasterizerVulkan::WaitForIdle() { }); } +void RasterizerVulkan::FragmentBarrier() { + // We already put barriers when a render pass finishes +} + +void RasterizerVulkan::TiledCacheBarrier() { + // TODO: Implementing tiled barriers requires rewriting a good chunk of the Vulkan backend +} + void RasterizerVulkan::FlushCommands() { if (draw_counter > 0) { draw_counter = 0; @@ -717,14 +619,23 @@ void RasterizerVulkan::FlushCommands() { void RasterizerVulkan::TickFrame() { draw_counter = 0; update_descriptor_queue.TickFrame(); - buffer_cache.TickFrame(); + fence_manager.TickFrame(); staging_pool.TickFrame(); + { + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.TickFrame(); + } + { + std::scoped_lock lock{buffer_cache.mutex}; + buffer_cache.TickFrame(); + } } -bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, +bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Config& copy_config) { - texture_cache.DoFermiCopy(src, dst, copy_config); + std::scoped_lock lock{texture_cache.mutex}; + texture_cache.BlitImage(dst, src, copy_config); return true; } @@ -733,28 +644,18 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config, if (!framebuffer_addr) { return false; } - - const auto surface{texture_cache.TryFindFramebufferSurface(framebuffer_addr)}; - if (!surface) { + std::scoped_lock lock{texture_cache.mutex}; + ImageView* const image_view = texture_cache.TryFindFramebufferImageView(framebuffer_addr); + if (!image_view) { return false; } - - // Verify that the cached surface is the same size and format as the requested framebuffer - const auto& params{surface->GetSurfaceParams()}; - ASSERT_MSG(params.width == config.width, "Framebuffer width is different"); - ASSERT_MSG(params.height == config.height, "Framebuffer height is different"); - - screen_info.image = &surface->GetImage(); - screen_info.width = params.width; - screen_info.height = params.height; - screen_info.is_srgb = surface->GetSurfaceParams().srgb_conversion; + screen_info.image_view = image_view->Handle(VideoCommon::ImageViewType::e2D); + screen_info.width = image_view->size.width; + screen_info.height = image_view->size.height; + screen_info.is_srgb = VideoCore::Surface::IsPixelFormatSRGB(image_view->format); return true; } -void RasterizerVulkan::SetupDirtyFlags() { - state_tracker.Initialize(); -} - void RasterizerVulkan::FlushWork() { static constexpr u32 DRAWS_TO_DISPATCH = 4096; @@ -776,178 +677,54 @@ void RasterizerVulkan::FlushWork() { draw_counter = 0; } -RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments(bool is_clear) { - MICROPROFILE_SCOPE(Vulkan_RenderTargets); - auto& maxwell3d = system.GPU().Maxwell3D(); - auto& dirty = maxwell3d.dirty.flags; - auto& regs = maxwell3d.regs; - - const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets]; - dirty[VideoCommon::Dirty::RenderTargets] = false; - - texture_cache.GuardRenderTargets(true); - - Texceptions texceptions; - for (std::size_t rt = 0; rt < Maxwell::NumRenderTargets; ++rt) { - if (update_rendertargets) { - const bool preserve_contents = HasToPreserveColorContents(is_clear, regs); - color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, preserve_contents); - } - if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) { - texceptions[rt] = true; - } - } - - if (update_rendertargets) { - const bool preserve_contents = HasToPreserveDepthContents(is_clear, regs); - zeta_attachment = texture_cache.GetDepthBufferSurface(preserve_contents); - } - if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) { - texceptions[ZETA_TEXCEPTION_INDEX] = true; - } - - texture_cache.GuardRenderTargets(false); - - return texceptions; -} - -bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachment) { - bool overlap = false; - for (auto& [view, layout] : sampled_views) { - if (!attachment.IsSameSurface(*view)) { - continue; - } - overlap = true; - *layout = VK_IMAGE_LAYOUT_GENERAL; - } - return overlap; -} - -std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers( - VkRenderPass renderpass) { - FramebufferCacheKey key{ - .renderpass = renderpass, - .width = std::numeric_limits<u32>::max(), - .height = std::numeric_limits<u32>::max(), - .layers = std::numeric_limits<u32>::max(), - .views = {}, - }; - - const auto try_push = [&key](const View& view) { - if (!view) { - return false; - } - key.views.push_back(view->GetAttachment()); - key.width = std::min(key.width, view->GetWidth()); - key.height = std::min(key.height, view->GetHeight()); - key.layers = std::min(key.layers, view->GetNumLayers()); - return true; - }; - - const auto& regs = system.GPU().Maxwell3D().regs; - const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count); - for (std::size_t index = 0; index < num_attachments; ++index) { - if (try_push(color_attachments[index])) { - texture_cache.MarkColorBufferInUse(index); - } - } - if (try_push(zeta_attachment)) { - texture_cache.MarkDepthBufferInUse(); - } - - const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key); - auto& framebuffer = fbentry->second; - if (is_cache_miss) { - framebuffer = device.GetLogical().CreateFramebuffer({ - .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .renderPass = key.renderpass, - .attachmentCount = static_cast<u32>(key.views.size()), - .pAttachments = key.views.data(), - .width = key.width, - .height = key.height, - .layers = key.layers, - }); - } - - return {*framebuffer, VkExtent2D{key.width, key.height}}; -} - -RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineState& fixed_state, - BufferBindings& buffer_bindings, - bool is_indexed, - bool is_instanced) { - MICROPROFILE_SCOPE(Vulkan_Geometry); - - const auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; - - SetupVertexArrays(buffer_bindings); - - const u32 base_instance = regs.vb_base_instance; - const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1; - const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first; - const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count; - - DrawParameters params{base_instance, num_instances, base_vertex, num_vertices, is_indexed}; - SetupIndexBuffer(buffer_bindings, params, is_indexed); - - return params; -} - void RasterizerVulkan::SetupShaderDescriptors( - const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) { - texture_cache.GuardSamplers(true); - - for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { - // Skip VertexA stage + const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders, bool is_indexed) { + image_view_indices.clear(); + sampler_handles.clear(); + for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { Shader* const shader = shaders[stage + 1]; if (!shader) { continue; } - const auto& entries = shader->GetEntries(); - SetupGraphicsConstBuffers(entries, stage); - SetupGraphicsGlobalBuffers(entries, stage); + const ShaderEntries& entries = shader->GetEntries(); SetupGraphicsUniformTexels(entries, stage); SetupGraphicsTextures(entries, stage); SetupGraphicsStorageTexels(entries, stage); SetupGraphicsImages(entries, stage); + + buffer_cache.SetEnabledUniformBuffers(stage, entries.enabled_uniform_buffers); + buffer_cache.UnbindGraphicsStorageBuffers(stage); + u32 ssbo_index = 0; + for (const auto& buffer : entries.global_buffers) { + buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, buffer.cbuf_index, + buffer.cbuf_offset, buffer.is_written); + ++ssbo_index; + } } - texture_cache.GuardSamplers(false); -} + const std::span indices_span(image_view_indices.data(), image_view_indices.size()); + buffer_cache.UpdateGraphicsBuffers(is_indexed); + texture_cache.FillGraphicsImageViews(indices_span, image_view_ids); + + buffer_cache.BindHostGeometryBuffers(is_indexed); -void RasterizerVulkan::SetupImageTransitions( - Texceptions texceptions, const std::array<View, Maxwell::NumRenderTargets>& color_attachments, - const View& zeta_attachment) { - TransitionImages(sampled_views, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); - TransitionImages(image_views, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + update_descriptor_queue.Acquire(); - for (std::size_t rt = 0; rt < std::size(color_attachments); ++rt) { - const auto color_attachment = color_attachments[rt]; - if (color_attachment == nullptr) { + ImageViewId* image_view_id_ptr = image_view_ids.data(); + VkSampler* sampler_ptr = sampler_handles.data(); + for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { + // Skip VertexA stage + Shader* const shader = shaders[stage + 1]; + if (!shader) { continue; } - const auto image_layout = - texceptions[rt] ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - color_attachment->Transition(image_layout, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); - } - - if (zeta_attachment != nullptr) { - const auto image_layout = texceptions[ZETA_TEXCEPTION_INDEX] - ? VK_IMAGE_LAYOUT_GENERAL - : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - zeta_attachment->Transition(image_layout, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); + buffer_cache.BindHostStageBuffers(stage); + PushImageDescriptors(shader->GetEntries(), texture_cache, update_descriptor_queue, + image_view_id_ptr, sampler_ptr); } } void RasterizerVulkan::UpdateDynamicStates() { - auto& regs = system.GPU().Maxwell3D().regs; + auto& regs = maxwell3d.regs; UpdateViewportsState(regs); UpdateScissorsState(regs); UpdateDepthBias(regs); @@ -961,14 +738,13 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateDepthWriteEnable(regs); UpdateDepthCompareOp(regs); UpdateFrontFace(regs); - UpdatePrimitiveTopology(regs); UpdateStencilOp(regs); UpdateStencilTestEnable(regs); } } void RasterizerVulkan::BeginTransformFeedback() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } @@ -976,322 +752,108 @@ void RasterizerVulkan::BeginTransformFeedback() { LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); return; } - UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) || regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) || regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry)); - - UNIMPLEMENTED_IF(regs.tfb_bindings[1].buffer_enable); - UNIMPLEMENTED_IF(regs.tfb_bindings[2].buffer_enable); - UNIMPLEMENTED_IF(regs.tfb_bindings[3].buffer_enable); - - const auto& binding = regs.tfb_bindings[0]; - UNIMPLEMENTED_IF(binding.buffer_enable == 0); - UNIMPLEMENTED_IF(binding.buffer_offset != 0); - - const GPUVAddr gpu_addr = binding.Address(); - const VkDeviceSize size = static_cast<VkDeviceSize>(binding.buffer_size); - const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true); - - scheduler.Record([buffer = info.handle, offset = info.offset, size](vk::CommandBuffer cmdbuf) { - cmdbuf.BindTransformFeedbackBuffersEXT(0, 1, &buffer, &offset, &size); - cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); - }); + scheduler.Record( + [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); } void RasterizerVulkan::EndTransformFeedback() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } if (!device.IsExtTransformFeedbackSupported()) { return; } - scheduler.Record( [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); }); } -void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) { - const auto& regs = system.GPU().Maxwell3D().regs; - - for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { - const auto& vertex_array = regs.vertex_array[index]; - if (!vertex_array.IsEnabled()) { - continue; - } - const GPUVAddr start{vertex_array.StartAddress()}; - const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; - - ASSERT(end >= start); - const std::size_t size = end - start; - if (size == 0) { - buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0); - continue; - } - const auto info = buffer_cache.UploadMemory(start, size); - buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride); - } -} - -void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, - bool is_indexed) { - if (params.num_vertices == 0) { - return; - } - const auto& regs = system.GPU().Maxwell3D().regs; - switch (regs.draw.topology) { - case Maxwell::PrimitiveTopology::Quads: { - if (!params.is_indexed) { - const auto [buffer, offset] = - quad_array_pass.Assemble(params.num_vertices, params.base_vertex); - buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32); - params.base_vertex = 0; - params.num_vertices = params.num_vertices * 6 / 4; - params.is_indexed = true; - break; - } - const GPUVAddr gpu_addr = regs.index_array.IndexStart(); - const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize()); - VkBuffer buffer = info.handle; - u64 offset = info.offset; - std::tie(buffer, offset) = quad_indexed_pass.Assemble( - regs.index_array.format, params.num_vertices, params.base_vertex, buffer, offset); - - buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32); - params.num_vertices = (params.num_vertices / 4) * 6; - params.base_vertex = 0; - break; - } - default: { - if (!is_indexed) { - break; - } - const GPUVAddr gpu_addr = regs.index_array.IndexStart(); - const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize()); - VkBuffer buffer = info.handle; - u64 offset = info.offset; - - auto format = regs.index_array.format; - const bool is_uint8 = format == Maxwell::IndexFormat::UnsignedByte; - if (is_uint8 && !device.IsExtIndexTypeUint8Supported()) { - std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, buffer, offset); - format = Maxwell::IndexFormat::UnsignedShort; - } - - buffer_bindings.SetIndexBinding(buffer, offset, MaxwellToVK::IndexFormat(device, format)); - break; - } - } -} - -void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_ConstBuffers); - const auto& gpu = system.GPU().Maxwell3D(); - const auto& shader_stage = gpu.state.shader_stages[stage]; - for (const auto& entry : entries.const_buffers) { - SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]); - } -} - -void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_GlobalBuffers); - auto& gpu{system.GPU()}; - const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]}; - - for (const auto& entry : entries.global_buffers) { - const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset(); - SetupGlobalBuffer(entry, addr); - } -} - -void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); +void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, size_t stage) { + const auto& regs = maxwell3d.regs; + const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : entries.uniform_texels) { - const auto image = GetTextureInfo(gpu, entry, stage).tic; - SetupUniformTexels(image, entry); + const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage); + image_view_indices.push_back(handle.image); } } -void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); +void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, size_t stage) { + const auto& regs = maxwell3d.regs; + const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : entries.samplers) { - for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(gpu, entry, stage, i); - SetupTexture(texture, entry); + for (size_t index = 0; index < entry.size; ++index) { + const TextureHandle handle = + GetTextureInfo(maxwell3d, via_header_index, entry, stage, index); + image_view_indices.push_back(handle.image); + + Sampler* const sampler = texture_cache.GetGraphicsSampler(handle.sampler); + sampler_handles.push_back(sampler->Handle()); } } } -void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); +void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, size_t stage) { + const auto& regs = maxwell3d.regs; + const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : entries.storage_texels) { - const auto image = GetTextureInfo(gpu, entry, stage).tic; - SetupStorageTexel(image, entry); + const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage); + image_view_indices.push_back(handle.image); } } -void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) { - MICROPROFILE_SCOPE(Vulkan_Images); - const auto& gpu = system.GPU().Maxwell3D(); +void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, size_t stage) { + const auto& regs = maxwell3d.regs; + const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex; for (const auto& entry : entries.images) { - const auto tic = GetTextureInfo(gpu, entry, stage).tic; - SetupImage(tic, entry); - } -} - -void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_ConstBuffers); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; - for (const auto& entry : entries.const_buffers) { - const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; - const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); - Tegra::Engines::ConstBufferInfo buffer; - buffer.address = config.Address(); - buffer.size = config.size; - buffer.enabled = mask[entry.GetIndex()]; - SetupConstBuffer(entry, buffer); - } -} - -void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_GlobalBuffers); - const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config}; - for (const auto& entry : entries.global_buffers) { - const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()}; - SetupGlobalBuffer(entry, addr); + const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage); + image_view_indices.push_back(handle.image); } } void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : entries.uniform_texels) { - const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; - SetupUniformTexels(image, entry); + const TextureHandle handle = + GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX); + image_view_indices.push_back(handle.image); } } void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : entries.samplers) { - for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i); - SetupTexture(texture, entry); + for (size_t index = 0; index < entry.size; ++index) { + const TextureHandle handle = GetTextureInfo(kepler_compute, via_header_index, entry, + COMPUTE_SHADER_INDEX, index); + image_view_indices.push_back(handle.image); + + Sampler* const sampler = texture_cache.GetComputeSampler(handle.sampler); + sampler_handles.push_back(sampler->Handle()); } } } void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : entries.storage_texels) { - const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; - SetupStorageTexel(image, entry); + const TextureHandle handle = + GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX); + image_view_indices.push_back(handle.image); } } void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) { - MICROPROFILE_SCOPE(Vulkan_Images); - const auto& gpu = system.GPU().KeplerCompute(); + const bool via_header_index = kepler_compute.launch_description.linked_tsc; for (const auto& entry : entries.images) { - const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; - SetupImage(tic, entry); + const TextureHandle handle = + GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX); + image_view_indices.push_back(handle.image); } } -void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry, - const Tegra::Engines::ConstBufferInfo& buffer) { - if (!buffer.enabled) { - // Set values to zero to unbind buffers - update_descriptor_queue.AddBuffer(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE); - return; - } - - // Align the size to avoid bad std140 interactions - const std::size_t size = - Common::AlignUp(CalculateConstBufferSize(entry, buffer), 4 * sizeof(float)); - ASSERT(size <= MaxConstbufferSize); - - const auto info = - buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment()); - update_descriptor_queue.AddBuffer(info.handle, info.offset, size); -} - -void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) { - auto& memory_manager{system.GPU().MemoryManager()}; - const auto actual_addr = memory_manager.Read<u64>(address); - const auto size = memory_manager.Read<u32>(address + 8); - - if (size == 0) { - // Sometimes global memory pointers don't have a proper size. Upload a dummy entry - // because Vulkan doesn't like empty buffers. - // Note: Do *not* use DefaultBuffer() here, storage buffers can be written breaking the - // default buffer. - static constexpr std::size_t dummy_size = 4; - const auto info = buffer_cache.GetEmptyBuffer(dummy_size); - update_descriptor_queue.AddBuffer(info.handle, info.offset, dummy_size); - return; - } - - const auto info = buffer_cache.UploadMemory( - actual_addr, size, device.GetStorageBufferAlignment(), entry.IsWritten()); - update_descriptor_queue.AddBuffer(info.handle, info.offset, size); -} - -void RasterizerVulkan::SetupUniformTexels(const Tegra::Texture::TICEntry& tic, - const UniformTexelEntry& entry) { - const auto view = texture_cache.GetTextureSurface(tic, entry); - ASSERT(view->IsBufferView()); - - update_descriptor_queue.AddTexelBuffer(view->GetBufferView()); -} - -void RasterizerVulkan::SetupTexture(const Tegra::Texture::FullTextureInfo& texture, - const SamplerEntry& entry) { - auto view = texture_cache.GetTextureSurface(texture.tic, entry); - ASSERT(!view->IsBufferView()); - - const VkImageView image_view = view->GetImageView(texture.tic.x_source, texture.tic.y_source, - texture.tic.z_source, texture.tic.w_source); - const auto sampler = sampler_cache.GetSampler(texture.tsc); - update_descriptor_queue.AddSampledImage(sampler, image_view); - - VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout(); - *image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - sampled_views.push_back(ImageView{std::move(view), image_layout}); -} - -void RasterizerVulkan::SetupStorageTexel(const Tegra::Texture::TICEntry& tic, - const StorageTexelEntry& entry) { - const auto view = texture_cache.GetImageSurface(tic, entry); - ASSERT(view->IsBufferView()); - - update_descriptor_queue.AddTexelBuffer(view->GetBufferView()); -} - -void RasterizerVulkan::SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry) { - auto view = texture_cache.GetImageSurface(tic, entry); - - if (entry.is_written) { - view->MarkAsModified(texture_cache.Tick()); - } - - UNIMPLEMENTED_IF(tic.IsBuffer()); - - const VkImageView image_view = - view->GetImageView(tic.x_source, tic.y_source, tic.z_source, tic.w_source); - update_descriptor_queue.AddImage(image_view); - - VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout(); - *image_layout = VK_IMAGE_LAYOUT_GENERAL; - image_views.push_back(ImageView{std::move(view), image_layout}); -} - void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchViewports()) { return; @@ -1304,7 +866,8 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg GetViewportState(device, regs, 8), GetViewportState(device, regs, 9), GetViewportState(device, regs, 10), GetViewportState(device, regs, 11), GetViewportState(device, regs, 12), GetViewportState(device, regs, 13), - GetViewportState(device, regs, 14), GetViewportState(device, regs, 15)}; + GetViewportState(device, regs, 14), GetViewportState(device, regs, 15), + }; scheduler.Record([viewports](vk::CommandBuffer cmdbuf) { cmdbuf.SetViewport(0, viewports); }); } @@ -1312,13 +875,14 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs if (!state_tracker.TouchScissors()) { return; } - const std::array scissors = { + const std::array scissors{ GetScissorState(regs, 0), GetScissorState(regs, 1), GetScissorState(regs, 2), GetScissorState(regs, 3), GetScissorState(regs, 4), GetScissorState(regs, 5), GetScissorState(regs, 6), GetScissorState(regs, 7), GetScissorState(regs, 8), GetScissorState(regs, 9), GetScissorState(regs, 10), GetScissorState(regs, 11), GetScissorState(regs, 12), GetScissorState(regs, 13), GetScissorState(regs, 14), - GetScissorState(regs, 15)}; + GetScissorState(regs, 15), + }; scheduler.Record([scissors](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissors); }); } @@ -1442,16 +1006,6 @@ void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) { [front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); }); } -void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) { - const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value(); - if (!state_tracker.ChangePrimitiveTopology(primitive_topology)) { - return; - } - scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) { - cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology)); - }); -} - void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchStencilOp()) { return; @@ -1493,101 +1047,4 @@ void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& }); } -std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const { - std::size_t size = CalculateVertexArraysSize(); - if (is_indexed) { - size = Common::AlignUp(size, 4) + CalculateIndexBufferSize(); - } - size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment()); - return size; -} - -std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const { - return Tegra::Engines::KeplerCompute::NumConstBuffers * - (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); -} - -std::size_t RasterizerVulkan::CalculateVertexArraysSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; - - std::size_t size = 0; - for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { - // This implementation assumes that all attributes are used in the shader. - const GPUVAddr start{regs.vertex_array[index].StartAddress()}; - const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; - DEBUG_ASSERT(end >= start); - - size += (end - start) * regs.vertex_array[index].enable; - } - return size; -} - -std::size_t RasterizerVulkan::CalculateIndexBufferSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; - return static_cast<std::size_t>(regs.index_array.count) * - static_cast<std::size_t>(regs.index_array.FormatSizeInBytes()); -} - -std::size_t RasterizerVulkan::CalculateConstBufferSize( - const ConstBufferEntry& entry, const Tegra::Engines::ConstBufferInfo& buffer) const { - if (entry.IsIndirect()) { - // Buffer is accessed indirectly, so upload the entire thing - return buffer.size; - } else { - // Buffer is accessed directly, upload just what we use - return entry.GetSize(); - } -} - -RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const { - const auto& regs = system.GPU().Maxwell3D().regs; - const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count); - - RenderPassParams params; - params.color_formats = {}; - std::size_t color_texceptions = 0; - - std::size_t index = 0; - for (std::size_t rt = 0; rt < num_attachments; ++rt) { - const auto& rendertarget = regs.rt[rt]; - if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) { - continue; - } - params.color_formats[index] = static_cast<u8>(rendertarget.format); - color_texceptions |= (texceptions[rt] ? 1ULL : 0ULL) << index; - ++index; - } - params.num_color_attachments = static_cast<u8>(index); - params.texceptions = static_cast<u8>(color_texceptions); - - params.zeta_format = regs.zeta_enable ? static_cast<u8>(regs.zeta.format) : 0; - params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX]; - return params; -} - -VkBuffer RasterizerVulkan::DefaultBuffer() { - if (default_buffer) { - return *default_buffer; - } - - default_buffer = device.GetLogical().CreateBuffer({ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = DEFAULT_BUFFER_SIZE, - .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - }); - default_buffer_commit = memory_manager.Commit(default_buffer, false); - - scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([buffer = *default_buffer](vk::CommandBuffer cmdbuf) { - cmdbuf.FillBuffer(buffer, 0, DEFAULT_BUFFER_SIZE, 0); - }); - return *default_buffer; -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index f640ba649..acea1ba2d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -11,28 +11,25 @@ #include <vector> #include <boost/container/static_vector.hpp> -#include <boost/functional/hash.hpp> #include "common/common_types.h" #include "video_core/rasterizer_accelerated.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" -#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_fence_manager.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_query_cache.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/vk_sampler_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" #include "video_core/shader/async_shaders.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Core { class System; @@ -50,66 +47,15 @@ namespace Vulkan { struct VKScreenInfo; -using ImageViewsPack = boost::container::static_vector<VkImageView, Maxwell::NumRenderTargets + 1>; - -struct FramebufferCacheKey { - VkRenderPass renderpass{}; - u32 width = 0; - u32 height = 0; - u32 layers = 0; - ImageViewsPack views; - - std::size_t Hash() const noexcept { - std::size_t hash = 0; - boost::hash_combine(hash, static_cast<VkRenderPass>(renderpass)); - for (const auto& view : views) { - boost::hash_combine(hash, static_cast<VkImageView>(view)); - } - boost::hash_combine(hash, width); - boost::hash_combine(hash, height); - boost::hash_combine(hash, layers); - return hash; - } - - bool operator==(const FramebufferCacheKey& rhs) const noexcept { - return std::tie(renderpass, views, width, height, layers) == - std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height, rhs.layers); - } - - bool operator!=(const FramebufferCacheKey& rhs) const noexcept { - return !operator==(rhs); - } -}; - -} // namespace Vulkan - -namespace std { - -template <> -struct hash<Vulkan::FramebufferCacheKey> { - std::size_t operator()(const Vulkan::FramebufferCacheKey& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace Vulkan { - class StateTracker; -class BufferBindings; - -struct ImageView { - View view; - VkImageLayout* layout = nullptr; -}; class RasterizerVulkan final : public VideoCore::RasterizerAccelerated { public: - explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window, - VKScreenInfo& screen_info, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - StateTracker& state_tracker, VKScheduler& scheduler); + explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_, + Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_, + VKScreenInfo& screen_info_, const Device& device_, + MemoryAllocator& memory_allocator_, StateTracker& state_tracker_, + VKScheduler& scheduler_); ~RasterizerVulkan() override; void Draw(bool is_indexed, bool is_instanced) override; @@ -117,25 +63,28 @@ public: void DispatchCompute(GPUVAddr code_addr) override; void ResetCounter(VideoCore::QueryType type) override; void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; + void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; void FlushAll() override; void FlushRegion(VAddr addr, u64 size) override; bool MustFlushRegion(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size) override; void OnCPUWrite(VAddr addr, u64 size) override; void SyncGuestHost() override; + void UnmapMemory(VAddr addr, u64 size) override; void SignalSemaphore(GPUVAddr addr, u32 value) override; void SignalSyncPoint(u32 value) override; void ReleaseFences() override; void FlushAndInvalidateRegion(VAddr addr, u64 size) override; void WaitForIdle() override; + void FragmentBarrier() override; + void TiledCacheBarrier() override; void FlushCommands() override; void TickFrame() override; - bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, + bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Config& copy_config) override; bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; - void SetupDirtyFlags() override; VideoCommon::Shader::AsyncShaders& GetAsyncShaders() { return async_shaders; @@ -146,45 +95,22 @@ public: } /// Maximum supported size that a constbuffer can have in bytes. - static constexpr std::size_t MaxConstbufferSize = 0x10000; + static constexpr size_t MaxConstbufferSize = 0x10000; static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0, "The maximum size of a constbuffer must be a multiple of the size of GLvec4"); private: - struct DrawParameters { - void Draw(vk::CommandBuffer cmdbuf) const; + static constexpr size_t MAX_TEXTURES = 192; + static constexpr size_t MAX_IMAGES = 48; + static constexpr size_t MAX_IMAGE_VIEWS = MAX_TEXTURES + MAX_IMAGES; - u32 base_instance = 0; - u32 num_instances = 0; - u32 base_vertex = 0; - u32 num_vertices = 0; - bool is_indexed = 0; - }; - - using Texceptions = std::bitset<Maxwell::NumRenderTargets + 1>; - - static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8; static constexpr VkDeviceSize DEFAULT_BUFFER_SIZE = 4 * sizeof(float); void FlushWork(); - /// @brief Updates the currently bound attachments - /// @param is_clear True when the framebuffer is updated as a clear - /// @return Bitfield of attachments being used as sampled textures - Texceptions UpdateAttachments(bool is_clear); - - std::tuple<VkFramebuffer, VkExtent2D> ConfigureFramebuffers(VkRenderPass renderpass); - - /// Setups geometry buffers and state. - DrawParameters SetupGeometry(FixedPipelineState& fixed_state, BufferBindings& buffer_bindings, - bool is_indexed, bool is_instanced); - /// Setup descriptors in the graphics pipeline. - void SetupShaderDescriptors(const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders); - - void SetupImageTransitions(Texceptions texceptions, - const std::array<View, Maxwell::NumRenderTargets>& color_attachments, - const View& zeta_attachment); + void SetupShaderDescriptors(const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders, + bool is_indexed); void UpdateDynamicStates(); @@ -192,18 +118,6 @@ private: void EndTransformFeedback(); - bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment); - - void SetupVertexArrays(BufferBindings& buffer_bindings); - - void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed); - - /// Setup constant buffers in the graphics pipeline. - void SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage); - - /// Setup global buffers in the graphics pipeline. - void SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage); - /// Setup uniform texels in the graphics pipeline. void SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage); @@ -216,12 +130,6 @@ private: /// Setup images in the graphics pipeline. void SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage); - /// Setup constant buffers in the compute pipeline. - void SetupComputeConstBuffers(const ShaderEntries& entries); - - /// Setup global buffers in the compute pipeline. - void SetupComputeGlobalBuffers(const ShaderEntries& entries); - /// Setup texel buffers in the compute pipeline. void SetupComputeUniformTexels(const ShaderEntries& entries); @@ -234,19 +142,6 @@ private: /// Setup images in the compute pipeline. void SetupComputeImages(const ShaderEntries& entries); - void SetupConstBuffer(const ConstBufferEntry& entry, - const Tegra::Engines::ConstBufferInfo& buffer); - - void SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address); - - void SetupUniformTexels(const Tegra::Texture::TICEntry& image, const UniformTexelEntry& entry); - - void SetupTexture(const Tegra::Texture::FullTextureInfo& texture, const SamplerEntry& entry); - - void SetupStorageTexel(const Tegra::Texture::TICEntry& tic, const StorageTexelEntry& entry); - - void SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry); - void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs); @@ -260,64 +155,43 @@ private: void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs); - void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); - std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const; - - std::size_t CalculateComputeStreamBufferSize() const; - - std::size_t CalculateVertexArraysSize() const; - - std::size_t CalculateIndexBufferSize() const; - - std::size_t CalculateConstBufferSize(const ConstBufferEntry& entry, - const Tegra::Engines::ConstBufferInfo& buffer) const; - - RenderPassParams GetRenderPassParams(Texceptions texceptions) const; + Tegra::GPU& gpu; + Tegra::MemoryManager& gpu_memory; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; - VkBuffer DefaultBuffer(); - - Core::System& system; - Core::Frontend::EmuWindow& render_window; VKScreenInfo& screen_info; - const VKDevice& device; - VKResourceManager& resource_manager; - VKMemoryManager& memory_manager; + const Device& device; + MemoryAllocator& memory_allocator; StateTracker& state_tracker; VKScheduler& scheduler; - VKStagingBufferPool staging_pool; + StagingBufferPool staging_pool; VKDescriptorPool descriptor_pool; VKUpdateDescriptorQueue update_descriptor_queue; - VKRenderPassCache renderpass_cache; - QuadArrayPass quad_array_pass; - QuadIndexedPass quad_indexed_pass; - Uint8Pass uint8_pass; + BlitImageHelper blit_image; + + GraphicsPipelineCacheKey graphics_key; - VKTextureCache texture_cache; + TextureCacheRuntime texture_cache_runtime; + TextureCache texture_cache; + BufferCacheRuntime buffer_cache_runtime; + BufferCache buffer_cache; VKPipelineCache pipeline_cache; - VKBufferCache buffer_cache; - VKSamplerCache sampler_cache; - VKFenceManager fence_manager; VKQueryCache query_cache; + VKFenceManager fence_manager; - vk::Buffer default_buffer; - VKMemoryCommit default_buffer_commit; vk::Event wfi_event; VideoCommon::Shader::AsyncShaders async_shaders; - std::array<View, Maxwell::NumRenderTargets> color_attachments; - View zeta_attachment; - - std::vector<ImageView> sampled_views; - std::vector<ImageView> image_views; + boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices; + std::array<VideoCommon::ImageViewId, MAX_IMAGE_VIEWS> image_view_ids; + boost::container::static_vector<VkSampler, MAX_TEXTURES> sampler_handles; u32 draw_counter = 0; - - // TODO(Rodrigo): Invalidate on image destruction - std::unordered_map<FramebufferCacheKey, vk::Framebuffer> framebuffer_cache; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp deleted file mode 100644 index 80284cf92..000000000 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstring> -#include <memory> -#include <vector> - -#include "common/cityhash.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/renderer_vulkan/maxwell_to_vk.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_renderpass_cache.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -std::size_t RenderPassParams::Hash() const noexcept { - const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this); - return static_cast<std::size_t>(hash); -} - -bool RenderPassParams::operator==(const RenderPassParams& rhs) const noexcept { - return std::memcmp(&rhs, this, sizeof *this) == 0; -} - -VKRenderPassCache::VKRenderPassCache(const VKDevice& device) : device{device} {} - -VKRenderPassCache::~VKRenderPassCache() = default; - -VkRenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) { - const auto [pair, is_cache_miss] = cache.try_emplace(params); - auto& entry = pair->second; - if (is_cache_miss) { - entry = CreateRenderPass(params); - } - return *entry; -} - -vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& params) const { - using namespace VideoCore::Surface; - const std::size_t num_attachments = static_cast<std::size_t>(params.num_color_attachments); - - std::vector<VkAttachmentDescription> descriptors; - descriptors.reserve(num_attachments); - - std::vector<VkAttachmentReference> color_references; - color_references.reserve(num_attachments); - - for (std::size_t rt = 0; rt < num_attachments; ++rt) { - const auto guest_format = static_cast<Tegra::RenderTargetFormat>(params.color_formats[rt]); - const PixelFormat pixel_format = PixelFormatFromRenderTargetFormat(guest_format); - const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format); - ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", - static_cast<int>(pixel_format)); - - // TODO(Rodrigo): Add MAY_ALIAS_BIT when it's needed. - const VkImageLayout color_layout = ((params.texceptions >> rt) & 1) != 0 - ? VK_IMAGE_LAYOUT_GENERAL - : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - descriptors.push_back({ - .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT, - .format = format.format, - .samples = VK_SAMPLE_COUNT_1_BIT, - .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = color_layout, - .finalLayout = color_layout, - }); - - color_references.push_back({ - .attachment = static_cast<u32>(rt), - .layout = color_layout, - }); - } - - VkAttachmentReference zeta_attachment_ref; - const bool has_zeta = params.zeta_format != 0; - if (has_zeta) { - const auto guest_format = static_cast<Tegra::DepthFormat>(params.zeta_format); - const PixelFormat pixel_format = PixelFormatFromDepthFormat(guest_format); - const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format); - ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", - static_cast<int>(pixel_format)); - - const VkImageLayout zeta_layout = params.zeta_texception != 0 - ? VK_IMAGE_LAYOUT_GENERAL - : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - descriptors.push_back({ - .flags = 0, - .format = format.format, - .samples = VK_SAMPLE_COUNT_1_BIT, - .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, - .initialLayout = zeta_layout, - .finalLayout = zeta_layout, - }); - - zeta_attachment_ref = { - .attachment = static_cast<u32>(num_attachments), - .layout = zeta_layout, - }; - } - - const VkSubpassDescription subpass_description{ - .flags = 0, - .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, - .inputAttachmentCount = 0, - .pInputAttachments = nullptr, - .colorAttachmentCount = static_cast<u32>(color_references.size()), - .pColorAttachments = color_references.data(), - .pResolveAttachments = nullptr, - .pDepthStencilAttachment = has_zeta ? &zeta_attachment_ref : nullptr, - .preserveAttachmentCount = 0, - .pPreserveAttachments = nullptr, - }; - - VkAccessFlags access = 0; - VkPipelineStageFlags stage = 0; - if (!color_references.empty()) { - access |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - } - - if (has_zeta) { - access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - stage |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - } - - const VkSubpassDependency subpass_dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = stage, - .dstStageMask = stage, - .srcAccessMask = 0, - .dstAccessMask = access, - .dependencyFlags = 0, - }; - - return device.GetLogical().CreateRenderPass({ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .attachmentCount = static_cast<u32>(descriptors.size()), - .pAttachments = descriptors.data(), - .subpassCount = 1, - .pSubpasses = &subpass_description, - .dependencyCount = 1, - .pDependencies = &subpass_dependency, - }); -} - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h deleted file mode 100644 index 8b0fec720..000000000 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <type_traits> -#include <unordered_map> - -#include <boost/container/static_vector.hpp> -#include <boost/functional/hash.hpp> - -#include "video_core/engines/maxwell_3d.h" -#include "video_core/renderer_vulkan/wrapper.h" -#include "video_core/surface.h" - -namespace Vulkan { - -class VKDevice; - -struct RenderPassParams { - std::array<u8, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> color_formats; - u8 num_color_attachments; - u8 texceptions; - - u8 zeta_format; - u8 zeta_texception; - - std::size_t Hash() const noexcept; - - bool operator==(const RenderPassParams& rhs) const noexcept; - - bool operator!=(const RenderPassParams& rhs) const noexcept { - return !operator==(rhs); - } -}; -static_assert(std::has_unique_object_representations_v<RenderPassParams>); -static_assert(std::is_trivially_copyable_v<RenderPassParams>); -static_assert(std::is_trivially_constructible_v<RenderPassParams>); - -} // namespace Vulkan - -namespace std { - -template <> -struct hash<Vulkan::RenderPassParams> { - std::size_t operator()(const Vulkan::RenderPassParams& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace Vulkan { - -class VKRenderPassCache final { -public: - explicit VKRenderPassCache(const VKDevice& device); - ~VKRenderPassCache(); - - VkRenderPass GetRenderPass(const RenderPassParams& params); - -private: - vk::RenderPass CreateRenderPass(const RenderPassParams& params) const; - - const VKDevice& device; - std::unordered_map<RenderPassParams, vk::RenderPass> cache; -}; - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp deleted file mode 100644 index f19330a36..000000000 --- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <optional> -#include "common/assert.h" -#include "common/logging/log.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -namespace { - -// TODO(Rodrigo): Fine tune these numbers. -constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000; -constexpr std::size_t FENCES_GROW_STEP = 0x40; - -constexpr VkFenceCreateInfo BuildFenceCreateInfo() { - return { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - }; -} - -} // Anonymous namespace - -class CommandBufferPool final : public VKFencedPool { -public: - explicit CommandBufferPool(const VKDevice& device) - : VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {} - - void Allocate(std::size_t begin, std::size_t end) override { - // Command buffers are going to be commited, recorded, executed every single usage cycle. - // They are also going to be reseted when commited. - Pool& pool = pools.emplace_back(); - pool.handle = device.GetLogical().CreateCommandPool({ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | - VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = device.GetGraphicsFamily(), - }); - pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE); - } - - VkCommandBuffer Commit(VKFence& fence) { - const std::size_t index = CommitResource(fence); - const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; - const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; - return pools[pool_index].cmdbufs[sub_index]; - } - -private: - struct Pool { - vk::CommandPool handle; - vk::CommandBuffers cmdbufs; - }; - - const VKDevice& device; - std::vector<Pool> pools; -}; - -VKResource::VKResource() = default; - -VKResource::~VKResource() = default; - -VKFence::VKFence(const VKDevice& device) - : device{device}, handle{device.GetLogical().CreateFence(BuildFenceCreateInfo())} {} - -VKFence::~VKFence() = default; - -void VKFence::Wait() { - switch (const VkResult result = handle.Wait()) { - case VK_SUCCESS: - return; - case VK_ERROR_DEVICE_LOST: - device.ReportLoss(); - [[fallthrough]]; - default: - throw vk::Exception(result); - } -} - -void VKFence::Release() { - ASSERT(is_owned); - is_owned = false; -} - -void VKFence::Commit() { - is_owned = true; - is_used = true; -} - -bool VKFence::Tick(bool gpu_wait, bool owner_wait) { - if (!is_used) { - // If a fence is not used it's always free. - return true; - } - if (is_owned && !owner_wait) { - // The fence is still being owned (Release has not been called) and ownership wait has - // not been asked. - return false; - } - - if (gpu_wait) { - // Wait for the fence if it has been requested. - (void)handle.Wait(); - } else { - if (handle.GetStatus() != VK_SUCCESS) { - // Vulkan fence is not ready, not much it can do here - return false; - } - } - - // Broadcast resources their free state. - for (auto* resource : protected_resources) { - resource->OnFenceRemoval(this); - } - protected_resources.clear(); - - // Prepare fence for reusage. - handle.Reset(); - is_used = false; - return true; -} - -void VKFence::Protect(VKResource* resource) { - protected_resources.push_back(resource); -} - -void VKFence::Unprotect(VKResource* resource) { - const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource); - ASSERT(it != protected_resources.end()); - - resource->OnFenceRemoval(this); - protected_resources.erase(it); -} - -void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept { - std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource, - new_resource); -} - -VKFenceWatch::VKFenceWatch() = default; - -VKFenceWatch::VKFenceWatch(VKFence& initial_fence) { - Watch(initial_fence); -} - -VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept { - fence = std::exchange(rhs.fence, nullptr); - if (fence) { - fence->RedirectProtection(&rhs, this); - } -} - -VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept { - fence = std::exchange(rhs.fence, nullptr); - if (fence) { - fence->RedirectProtection(&rhs, this); - } - return *this; -} - -VKFenceWatch::~VKFenceWatch() { - if (fence) { - fence->Unprotect(this); - } -} - -void VKFenceWatch::Wait() { - if (fence == nullptr) { - return; - } - fence->Wait(); - fence->Unprotect(this); -} - -void VKFenceWatch::Watch(VKFence& new_fence) { - Wait(); - fence = &new_fence; - fence->Protect(this); -} - -bool VKFenceWatch::TryWatch(VKFence& new_fence) { - if (fence) { - return false; - } - fence = &new_fence; - fence->Protect(this); - return true; -} - -void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) { - ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence"); - fence = nullptr; -} - -VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {} - -VKFencedPool::~VKFencedPool() = default; - -std::size_t VKFencedPool::CommitResource(VKFence& fence) { - const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> { - for (std::size_t iterator = begin; iterator < end; ++iterator) { - if (watches[iterator]->TryWatch(fence)) { - // The resource is now being watched, a free resource was successfully found. - return iterator; - } - } - return {}; - }; - // Try to find a free resource from the hinted position to the end. - auto found = Search(free_iterator, watches.size()); - if (!found) { - // Search from beginning to the hinted position. - found = Search(0, free_iterator); - if (!found) { - // Both searches failed, the pool is full; handle it. - const std::size_t free_resource = ManageOverflow(); - - // Watch will wait for the resource to be free. - watches[free_resource]->Watch(fence); - found = free_resource; - } - } - // Free iterator is hinted to the resource after the one that's been commited. - free_iterator = (*found + 1) % watches.size(); - return *found; -} - -std::size_t VKFencedPool::ManageOverflow() { - const std::size_t old_capacity = watches.size(); - Grow(); - - // The last entry is guaranted to be free, since it's the first element of the freshly - // allocated resources. - return old_capacity; -} - -void VKFencedPool::Grow() { - const std::size_t old_capacity = watches.size(); - watches.resize(old_capacity + grow_step); - std::generate(watches.begin() + old_capacity, watches.end(), - []() { return std::make_unique<VKFenceWatch>(); }); - Allocate(old_capacity, old_capacity + grow_step); -} - -VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} { - GrowFences(FENCES_GROW_STEP); - command_buffer_pool = std::make_unique<CommandBufferPool>(device); -} - -VKResourceManager::~VKResourceManager() = default; - -VKFence& VKResourceManager::CommitFence() { - const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* { - const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); }; - const auto hinted = fences.begin() + fences_iterator; - - auto it = std::find_if(hinted, fences.end(), Tick); - if (it == fences.end()) { - it = std::find_if(fences.begin(), hinted, Tick); - if (it == hinted) { - return nullptr; - } - } - fences_iterator = std::distance(fences.begin(), it) + 1; - if (fences_iterator >= fences.size()) - fences_iterator = 0; - - auto& fence = *it; - fence->Commit(); - return fence.get(); - }; - - VKFence* found_fence = StepFences(false, false); - if (!found_fence) { - // Try again, this time waiting. - found_fence = StepFences(true, false); - - if (!found_fence) { - // Allocate new fences and try again. - LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(), - fences.size() + FENCES_GROW_STEP); - - GrowFences(FENCES_GROW_STEP); - found_fence = StepFences(true, false); - ASSERT(found_fence != nullptr); - } - } - return *found_fence; -} - -VkCommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) { - return command_buffer_pool->Commit(fence); -} - -void VKResourceManager::GrowFences(std::size_t new_fences_count) { - const std::size_t previous_size = fences.size(); - fences.resize(previous_size + new_fences_count); - - std::generate(fences.begin() + previous_size, fences.end(), - [this] { return std::make_unique<VKFence>(device); }); -} - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h deleted file mode 100644 index f683d2276..000000000 --- a/src/video_core/renderer_vulkan/vk_resource_manager.h +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <memory> -#include <vector> -#include "video_core/renderer_vulkan/wrapper.h" - -namespace Vulkan { - -class VKDevice; -class VKFence; -class VKResourceManager; - -class CommandBufferPool; - -/// Interface for a Vulkan resource -class VKResource { -public: - explicit VKResource(); - virtual ~VKResource(); - - /** - * Signals the object that an owning fence has been signaled. - * @param signaling_fence Fence that signals its usage end. - */ - virtual void OnFenceRemoval(VKFence* signaling_fence) = 0; -}; - -/** - * Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access. - * They must be commited from the resource manager. Their usage flow is: commit the fence from the - * resource manager, protect resources with it and use them, send the fence to an execution queue - * and Wait for it if needed and then call Release. Used resources will automatically be signaled - * when they are free to be reused. - * @brief Protects resources for concurrent usage and signals its release. - */ -class VKFence { - friend class VKResourceManager; - -public: - explicit VKFence(const VKDevice& device); - ~VKFence(); - - /** - * Waits for the fence to be signaled. - * @warning You must have ownership of the fence and it has to be previously sent to a queue to - * call this function. - */ - void Wait(); - - /** - * Releases ownership of the fence. Pass after it has been sent to an execution queue. - * Unmanaged usage of the fence after the call will result in undefined behavior because it may - * be being used for something else. - */ - void Release(); - - /// Protects a resource with this fence. - void Protect(VKResource* resource); - - /// Removes protection for a resource. - void Unprotect(VKResource* resource); - - /// Redirects one protected resource to a new address. - void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept; - - /// Retreives the fence. - operator VkFence() const { - return *handle; - } - -private: - /// Take ownership of the fence. - void Commit(); - - /** - * Updates the fence status. - * @warning Waiting for the owner might soft lock the execution. - * @param gpu_wait Wait for the fence to be signaled by the driver. - * @param owner_wait Wait for the owner to signal its freedom. - * @returns True if the fence is free. Waiting for gpu and owner will always return true. - */ - bool Tick(bool gpu_wait, bool owner_wait); - - const VKDevice& device; ///< Device handler - vk::Fence handle; ///< Vulkan fence - std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence - bool is_owned = false; ///< The fence has been commited but not released yet. - bool is_used = false; ///< The fence has been commited but it has not been checked to be free. -}; - -/** - * A fence watch is used to keep track of the usage of a fence and protect a resource or set of - * resources without having to inherit VKResource from their handlers. - */ -class VKFenceWatch final : public VKResource { -public: - explicit VKFenceWatch(); - VKFenceWatch(VKFence& initial_fence); - VKFenceWatch(VKFenceWatch&&) noexcept; - VKFenceWatch(const VKFenceWatch&) = delete; - ~VKFenceWatch() override; - - VKFenceWatch& operator=(VKFenceWatch&&) noexcept; - - /// Waits for the fence to be released. - void Wait(); - - /** - * Waits for a previous fence and watches a new one. - * @param new_fence New fence to wait to. - */ - void Watch(VKFence& new_fence); - - /** - * Checks if it's currently being watched and starts watching it if it's available. - * @returns True if a watch has started, false if it's being watched. - */ - bool TryWatch(VKFence& new_fence); - - void OnFenceRemoval(VKFence* signaling_fence) override; - - /** - * Do not use it paired with Watch. Use TryWatch instead. - * Returns true when the watch is free. - */ - bool IsUsed() const { - return fence != nullptr; - } - -private: - VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free. -}; - -/** - * Handles a pool of resources protected by fences. Manages resource overflow allocating more - * resources. - */ -class VKFencedPool { -public: - explicit VKFencedPool(std::size_t grow_step); - virtual ~VKFencedPool(); - -protected: - /** - * Commits a free resource and protects it with a fence. It may allocate new resources. - * @param fence Fence that protects the commited resource. - * @returns Index of the resource commited. - */ - std::size_t CommitResource(VKFence& fence); - - /// Called when a chunk of resources have to be allocated. - virtual void Allocate(std::size_t begin, std::size_t end) = 0; - -private: - /// Manages pool overflow allocating new resources. - std::size_t ManageOverflow(); - - /// Allocates a new page of resources. - void Grow(); - - std::size_t grow_step = 0; ///< Number of new resources created after an overflow - std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found - std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources -}; - -/** - * The resource manager handles all resources that can be protected with a fence avoiding - * driver-side or GPU-side concurrent usage. Usage is documented in VKFence. - */ -class VKResourceManager final { -public: - explicit VKResourceManager(const VKDevice& device); - ~VKResourceManager(); - - /// Commits a fence. It has to be sent to a queue and released. - VKFence& CommitFence(); - - /// Commits an unused command buffer and protects it with a fence. - VkCommandBuffer CommitCommandBuffer(VKFence& fence); - -private: - /// Allocates new fences. - void GrowFences(std::size_t new_fences_count); - - const VKDevice& device; ///< Device handler. - std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found. - std::vector<std::unique_ptr<VKFence>> fences; ///< Pool of fences. - std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers. -}; - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp new file mode 100644 index 000000000..a8bf7bda8 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -0,0 +1,63 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <optional> + +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" + +namespace Vulkan { + +ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_) + : master_semaphore{master_semaphore_}, grow_step{grow_step_} {} + +ResourcePool::~ResourcePool() = default; + +size_t ResourcePool::CommitResource() { + // Refresh semaphore to query updated results + master_semaphore.Refresh(); + const u64 gpu_tick = master_semaphore.KnownGpuTick(); + const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> { + for (size_t iterator = begin; iterator < end; ++iterator) { + if (gpu_tick >= ticks[iterator]) { + ticks[iterator] = master_semaphore.CurrentTick(); + return iterator; + } + } + return std::nullopt; + }; + // Try to find a free resource from the hinted position to the end. + std::optional<size_t> found = search(hint_iterator, ticks.size()); + if (!found) { + // Search from beginning to the hinted position. + found = search(0, hint_iterator); + if (!found) { + // Both searches failed, the pool is full; handle it. + const size_t free_resource = ManageOverflow(); + + ticks[free_resource] = master_semaphore.CurrentTick(); + found = free_resource; + } + } + // Free iterator is hinted to the resource after the one that's been commited. + hint_iterator = (*found + 1) % ticks.size(); + return *found; +} + +size_t ResourcePool::ManageOverflow() { + const size_t old_capacity = ticks.size(); + Grow(); + + // The last entry is guaranted to be free, since it's the first element of the freshly + // allocated resources. + return old_capacity; +} + +void ResourcePool::Grow() { + const size_t old_capacity = ticks.size(); + ticks.resize(old_capacity + grow_step); + Allocate(old_capacity, old_capacity + grow_step); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h new file mode 100644 index 000000000..9d0bb3b4d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_pool.h @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "common/common_types.h" + +namespace Vulkan { + +class MasterSemaphore; + +/** + * Handles a pool of resources protected by fences. Manages resource overflow allocating more + * resources. + */ +class ResourcePool { +public: + explicit ResourcePool(MasterSemaphore& master_semaphore, size_t grow_step); + virtual ~ResourcePool(); + +protected: + size_t CommitResource(); + + /// Called when a chunk of resources have to be allocated. + virtual void Allocate(size_t begin, size_t end) = 0; + +private: + /// Manages pool overflow allocating new resources. + size_t ManageOverflow(); + + /// Allocates a new page of resources. + void Grow(); + + MasterSemaphore& master_semaphore; + size_t grow_step = 0; ///< Number of new resources created after an overflow + size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found + std::vector<u64> ticks; ///< Ticks for each resource +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp deleted file mode 100644 index b068888f9..000000000 --- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <unordered_map> - -#include "video_core/renderer_vulkan/maxwell_to_vk.h" -#include "video_core/renderer_vulkan/vk_sampler_cache.h" -#include "video_core/renderer_vulkan/wrapper.h" -#include "video_core/textures/texture.h" - -using Tegra::Texture::TextureMipmapFilter; - -namespace Vulkan { - -namespace { - -VkBorderColor ConvertBorderColor(std::array<float, 4> color) { - // TODO(Rodrigo): Manage integer border colors - if (color == std::array<float, 4>{0, 0, 0, 0}) { - return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; - } else if (color == std::array<float, 4>{0, 0, 0, 1}) { - return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; - } else if (color == std::array<float, 4>{1, 1, 1, 1}) { - return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - } - if (color[0] + color[1] + color[2] > 1.35f) { - // If color elements are brighter than roughly 0.5 average, use white border - return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - } else if (color[3] > 0.5f) { - return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; - } else { - return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; - } -} - -} // Anonymous namespace - -VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {} - -VKSamplerCache::~VKSamplerCache() = default; - -vk::Sampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const { - const bool arbitrary_borders = device.IsExtCustomBorderColorSupported(); - const std::array color = tsc.GetBorderColor(); - - VkSamplerCustomBorderColorCreateInfoEXT border{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, - .pNext = nullptr, - .customBorderColor = {}, - .format = VK_FORMAT_UNDEFINED, - }; - std::memcpy(&border.customBorderColor, color.data(), sizeof(color)); - - return device.GetLogical().CreateSampler({ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = arbitrary_borders ? &border : nullptr, - .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), - .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), - .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), - .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), - .mipLodBias = tsc.GetLodBias(), - .anisotropyEnable = - static_cast<VkBool32>(tsc.GetMaxAnisotropy() > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = tsc.GetMaxAnisotropy(), - .compareEnable = tsc.depth_compare_enabled, - .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), - .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.GetMinLod(), - .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.GetMaxLod(), - .borderColor = - arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color), - .unnormalizedCoordinates = VK_FALSE, - }); -} - -VkSampler VKSamplerCache::ToSamplerType(const vk::Sampler& sampler) const { - return *sampler; -} - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h deleted file mode 100644 index a33d1c0ee..000000000 --- a/src/video_core/renderer_vulkan/vk_sampler_cache.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "video_core/renderer_vulkan/wrapper.h" -#include "video_core/sampler_cache.h" -#include "video_core/textures/texture.h" - -namespace Vulkan { - -class VKDevice; - -class VKSamplerCache final : public VideoCommon::SamplerCache<VkSampler, vk::Sampler> { -public: - explicit VKSamplerCache(const VKDevice& device); - ~VKSamplerCache(); - -protected: - vk::Sampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override; - - VkSampler ToSamplerType(const vk::Sampler& sampler) const override; - -private: - const VKDevice& device; -}; - -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index dbbd0961a..f35c120b0 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -10,12 +10,14 @@ #include "common/microprofile.h" #include "common/thread.h" -#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_command_pool.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_query_cache.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -35,10 +37,10 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { last = nullptr; } -VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager, - StateTracker& state_tracker) - : device{device}, resource_manager{resource_manager}, state_tracker{state_tracker}, - next_fence{&resource_manager.CommitFence()} { +VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_) + : device{device_}, state_tracker{state_tracker_}, + master_semaphore{std::make_unique<MasterSemaphore>(device)}, + command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} { AcquireNewChunk(); AllocateNewContext(); worker_thread = std::thread(&VKScheduler::WorkerThread, this); @@ -50,20 +52,15 @@ VKScheduler::~VKScheduler() { worker_thread.join(); } -void VKScheduler::Flush(bool release_fence, VkSemaphore semaphore) { +void VKScheduler::Flush(VkSemaphore semaphore) { SubmitExecution(semaphore); - if (release_fence) { - current_fence->Release(); - } AllocateNewContext(); } -void VKScheduler::Finish(bool release_fence, VkSemaphore semaphore) { +void VKScheduler::Finish(VkSemaphore semaphore) { + const u64 presubmit_tick = CurrentTick(); SubmitExecution(semaphore); - current_fence->Wait(); - if (release_fence) { - current_fence->Release(); - } + Wait(presubmit_tick); AllocateNewContext(); } @@ -88,38 +85,39 @@ void VKScheduler::DispatchWork() { AcquireNewChunk(); } -void VKScheduler::RequestRenderpass(VkRenderPass renderpass, VkFramebuffer framebuffer, - VkExtent2D render_area) { - if (renderpass == state.renderpass && framebuffer == state.framebuffer && +void VKScheduler::RequestRenderpass(const Framebuffer* framebuffer) { + const VkRenderPass renderpass = framebuffer->RenderPass(); + const VkFramebuffer framebuffer_handle = framebuffer->Handle(); + const VkExtent2D render_area = framebuffer->RenderArea(); + if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer && render_area.width == state.render_area.width && render_area.height == state.render_area.height) { return; } - const bool end_renderpass = state.renderpass != nullptr; + EndRenderPass(); state.renderpass = renderpass; - state.framebuffer = framebuffer; + state.framebuffer = framebuffer_handle; state.render_area = render_area; - const VkRenderPassBeginInfo renderpass_bi{ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .pNext = nullptr, - .renderPass = renderpass, - .framebuffer = framebuffer, - .renderArea = - { - .offset = {.x = 0, .y = 0}, - .extent = render_area, - }, - .clearValueCount = 0, - .pClearValues = nullptr, - }; - - Record([renderpass_bi, end_renderpass](vk::CommandBuffer cmdbuf) { - if (end_renderpass) { - cmdbuf.EndRenderPass(); - } + Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) { + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = renderpass, + .framebuffer = framebuffer_handle, + .renderArea = + { + .offset = {.x = 0, .y = 0}, + .extent = render_area, + }, + .clearValueCount = 0, + .pClearValues = nullptr, + }; cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); }); + num_renderpass_images = framebuffer->NumImages(); + renderpass_images = framebuffer->Images(); + renderpass_image_ranges = framebuffer->ImageRanges(); } void VKScheduler::RequestOutsideRenderPassOperationContext() { @@ -160,18 +158,38 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) { current_cmdbuf.End(); + const VkSemaphore timeline_semaphore = master_semaphore->Handle(); + const u32 num_signal_semaphores = semaphore ? 2U : 1U; + + const u64 signal_value = master_semaphore->CurrentTick(); + const u64 wait_value = signal_value - 1; + const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + + master_semaphore->NextTick(); + + const std::array signal_values{signal_value, u64(0)}; + const std::array signal_semaphores{timeline_semaphore, semaphore}; + + const VkTimelineSemaphoreSubmitInfoKHR timeline_si{ + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &wait_value, + .signalSemaphoreValueCount = num_signal_semaphores, + .pSignalSemaphoreValues = signal_values.data(), + }; const VkSubmitInfo submit_info{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .pWaitDstStageMask = nullptr, + .pNext = &timeline_si, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &timeline_semaphore, + .pWaitDstStageMask = &wait_stage_mask, .commandBufferCount = 1, .pCommandBuffers = current_cmdbuf.address(), - .signalSemaphoreCount = semaphore ? 1U : 0U, - .pSignalSemaphores = &semaphore, + .signalSemaphoreCount = num_signal_semaphores, + .pSignalSemaphores = signal_semaphores.data(), }; - switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info, *current_fence)) { + switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) { case VK_SUCCESS: break; case VK_ERROR_DEVICE_LOST: @@ -183,14 +201,9 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) { } void VKScheduler::AllocateNewContext() { - ++ticks; - std::unique_lock lock{mutex}; - current_fence = next_fence; - next_fence = &resource_manager.CommitFence(); - current_cmdbuf = vk::CommandBuffer(resource_manager.CommitCommandBuffer(*current_fence), - device.GetDispatchLoader()); + current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader()); current_cmdbuf.Begin({ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = nullptr, @@ -218,8 +231,37 @@ void VKScheduler::EndRenderPass() { if (!state.renderpass) { return; } + Record([num_images = num_renderpass_images, images = renderpass_images, + ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) { + std::array<VkImageMemoryBarrier, 9> barriers; + for (size_t i = 0; i < num_images; ++i) { + barriers[i] = VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = images[i], + .subresourceRange = ranges[i], + }; + } + cmdbuf.EndRenderPass(); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr, + vk::Span(barriers.data(), num_images)); + }); state.renderpass = nullptr; - Record([](vk::CommandBuffer cmdbuf) { cmdbuf.EndRenderPass(); }); + num_renderpass_images = 0; } void VKScheduler::AcquireNewChunk() { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 970a65566..3ce48e9d2 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -6,52 +6,37 @@ #include <atomic> #include <condition_variable> +#include <cstddef> #include <memory> #include <stack> #include <thread> #include <utility> +#include "common/alignment.h" #include "common/common_types.h" #include "common/threadsafe_queue.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { +class CommandPool; +class Device; +class Framebuffer; class StateTracker; -class VKDevice; -class VKFence; class VKQueryCache; -class VKResourceManager; - -class VKFenceView { -public: - VKFenceView() = default; - VKFenceView(VKFence* const& fence) : fence{fence} {} - - VKFence* operator->() const noexcept { - return fence; - } - - operator VKFence&() const noexcept { - return *fence; - } - -private: - VKFence* const& fence; -}; /// The scheduler abstracts command buffer and fence management with an interface that's able to do /// OpenGL-like operations on Vulkan command buffers. class VKScheduler { public: - explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager, - StateTracker& state_tracker); + explicit VKScheduler(const Device& device, StateTracker& state_tracker); ~VKScheduler(); /// Sends the current execution context to the GPU. - void Flush(bool release_fence = true, VkSemaphore semaphore = nullptr); + void Flush(VkSemaphore semaphore = nullptr); /// Sends the current execution context to the GPU and waits for it to complete. - void Finish(bool release_fence = true, VkSemaphore semaphore = nullptr); + void Finish(VkSemaphore semaphore = nullptr); /// Waits for the worker thread to finish executing everything. After this function returns it's /// safe to touch worker resources. @@ -61,8 +46,7 @@ public: void DispatchWork(); /// Requests to begin a renderpass. - void RequestRenderpass(VkRenderPass renderpass, VkFramebuffer framebuffer, - VkExtent2D render_area); + void RequestRenderpass(const Framebuffer* framebuffer); /// Requests the current executino context to be able to execute operations only allowed outside /// of a renderpass. @@ -71,6 +55,9 @@ public: /// Binds a pipeline to the current execution context. void BindGraphicsPipeline(VkPipeline pipeline); + /// Invalidates current command buffer state except for render passes + void InvalidateState(); + /// Assigns the query cache. void SetQueryCache(VKQueryCache& query_cache_) { query_cache = &query_cache_; @@ -86,14 +73,24 @@ public: (void)chunk->Record(command); } - /// Gets a reference to the current fence. - VKFenceView GetFence() const { - return current_fence; + /// Returns the current command buffer tick. + [[nodiscard]] u64 CurrentTick() const noexcept { + return master_semaphore->CurrentTick(); } - /// Returns the current command buffer tick. - u64 Ticks() const { - return ticks; + /// Returns true when a tick has been triggered by the GPU. + [[nodiscard]] bool IsFree(u64 tick) const noexcept { + return master_semaphore->IsFree(tick); + } + + /// Waits for the given tick to trigger on the GPU. + void Wait(u64 tick) { + master_semaphore->Wait(tick); + } + + /// Returns the master timeline semaphore. + [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept { + return *master_semaphore; } private: @@ -118,7 +115,7 @@ private: template <typename T> class TypedCommand final : public Command { public: - explicit TypedCommand(T&& command) : command{std::move(command)} {} + explicit TypedCommand(T&& command_) : command{std::move(command_)} {} ~TypedCommand() override = default; TypedCommand(TypedCommand&&) = delete; @@ -141,12 +138,11 @@ private: using FuncType = TypedCommand<T>; static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large"); + command_offset = Common::AlignUp(command_offset, alignof(FuncType)); if (command_offset > sizeof(data) - sizeof(FuncType)) { return false; } - - Command* current_last = last; - + Command* const current_last = last; last = new (data.data() + command_offset) FuncType(std::move(command)); if (current_last) { @@ -154,7 +150,6 @@ private: } else { first = last; } - command_offset += sizeof(FuncType); return true; } @@ -167,8 +162,15 @@ private: Command* first = nullptr; Command* last = nullptr; - std::size_t command_offset = 0; - std::array<u8, 0x8000> data{}; + size_t command_offset = 0; + alignas(std::max_align_t) std::array<u8, 0x8000> data{}; + }; + + struct State { + VkRenderPass renderpass = nullptr; + VkFramebuffer framebuffer = nullptr; + VkExtent2D render_area = {0, 0}; + VkPipeline graphics_pipeline = nullptr; }; void WorkerThread(); @@ -177,39 +179,35 @@ private: void AllocateNewContext(); - void InvalidateState(); - void EndPendingOperations(); void EndRenderPass(); void AcquireNewChunk(); - const VKDevice& device; - VKResourceManager& resource_manager; + const Device& device; StateTracker& state_tracker; + std::unique_ptr<MasterSemaphore> master_semaphore; + std::unique_ptr<CommandPool> command_pool; + VKQueryCache* query_cache = nullptr; vk::CommandBuffer current_cmdbuf; - VKFence* current_fence = nullptr; - VKFence* next_fence = nullptr; - - struct State { - VkRenderPass renderpass = nullptr; - VkFramebuffer framebuffer = nullptr; - VkExtent2D render_area = {0, 0}; - VkPipeline graphics_pipeline = nullptr; - } state; std::unique_ptr<CommandChunk> chunk; std::thread worker_thread; + State state; + + u32 num_renderpass_images = 0; + std::array<VkImage, 9> renderpass_images{}; + std::array<VkImageSubresourceRange, 9> renderpass_image_ranges{}; + Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue; Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve; std::mutex mutex; std::condition_variable cv; - std::atomic<u64> ticks = 0; bool quit = false; }; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index cd7d7a4e4..40e2e0d38 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -22,11 +22,11 @@ #include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_header.h" #include "video_core/engines/shader_type.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" #include "video_core/shader/node.h" #include "video_core/shader/shader_ir.h" #include "video_core/shader/transform_feedback.h" +#include "video_core/vulkan_common/vulkan_device.h" namespace Vulkan { @@ -55,8 +55,8 @@ enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat }; class Expression final { public: - Expression(Id id, Type type) : id{id}, type{type} { - ASSERT(type != Type::Void); + Expression(Id id_, Type type_) : id{id_}, type{type_} { + ASSERT(type_ != Type::Void); } Expression() : type{Type::Void} {} @@ -102,7 +102,7 @@ struct GenericVaryingDescription { bool is_scalar = false; }; -spv::Dim GetSamplerDim(const Sampler& sampler) { +spv::Dim GetSamplerDim(const SamplerEntry& sampler) { ASSERT(!sampler.is_buffer); switch (sampler.type) { case Tegra::Shader::TextureType::Texture1D: @@ -114,12 +114,12 @@ spv::Dim GetSamplerDim(const Sampler& sampler) { case Tegra::Shader::TextureType::TextureCube: return spv::Dim::Cube; default: - UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<int>(sampler.type)); + UNIMPLEMENTED_MSG("Unimplemented sampler type={}", sampler.type); return spv::Dim::Dim2D; } } -std::pair<spv::Dim, bool> GetImageDim(const Image& image) { +std::pair<spv::Dim, bool> GetImageDim(const ImageEntry& image) { switch (image.type) { case Tegra::Shader::ImageType::Texture1D: return {spv::Dim::Dim1D, false}; @@ -134,7 +134,7 @@ std::pair<spv::Dim, bool> GetImageDim(const Image& image) { case Tegra::Shader::ImageType::Texture3D: return {spv::Dim::Dim3D, false}; default: - UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<int>(image.type)); + UNIMPLEMENTED_MSG("Unimplemented image type={}", image.type); return {spv::Dim::Dim2D, false}; } } @@ -274,12 +274,12 @@ bool IsPrecise(Operation operand) { class SPIRVDecompiler final : public Sirit::Module { public: - explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderType stage, - const Registry& registry, const Specialization& specialization) - : Module(0x00010300), device{device}, ir{ir}, stage{stage}, header{ir.GetHeader()}, - registry{registry}, specialization{specialization} { - if (stage != ShaderType::Compute) { - transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo()); + explicit SPIRVDecompiler(const Device& device_, const ShaderIR& ir_, ShaderType stage_, + const Registry& registry_, const Specialization& specialization_) + : Module(0x00010300), device{device_}, ir{ir_}, stage{stage_}, header{ir_.GetHeader()}, + registry{registry_}, specialization{specialization_} { + if (stage_ != ShaderType::Compute) { + transform_feedback = BuildTransformFeedback(registry_.GetGraphicsInfo()); } AddCapability(spv::Capability::Shader); @@ -293,6 +293,7 @@ public: AddCapability(spv::Capability::DrawParameters); AddCapability(spv::Capability::SubgroupBallotKHR); AddCapability(spv::Capability::SubgroupVoteKHR); + AddExtension("SPV_KHR_16bit_storage"); AddExtension("SPV_KHR_shader_ballot"); AddExtension("SPV_KHR_subgroup_vote"); AddExtension("SPV_KHR_storage_buffer_storage_class"); @@ -307,7 +308,6 @@ public: "supported on this device"); } } - if (ir.UsesLayer() || ir.UsesViewportIndex()) { if (ir.UsesViewportIndex()) { AddCapability(spv::Capability::MultiViewport); @@ -317,15 +317,13 @@ public: AddCapability(spv::Capability::ShaderViewportIndexLayerEXT); } } - if (device.IsFormatlessImageLoadSupported()) { AddCapability(spv::Capability::StorageImageReadWithoutFormat); } - if (device.IsFloat16Supported()) { AddCapability(spv::Capability::Float16); } - t_scalar_half = Name(TypeFloat(device.IsFloat16Supported() ? 16 : 32), "scalar_half"); + t_scalar_half = Name(TypeFloat(device_.IsFloat16Supported() ? 16 : 32), "scalar_half"); t_half = Name(TypeVector(t_scalar_half, 2), "half"); const Id main = Decompile(); @@ -369,6 +367,9 @@ public: if (header.ps.omap.depth) { AddExecutionMode(main, spv::ExecutionMode::DepthReplacing); } + if (specialization.early_fragment_tests) { + AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests); + } break; case ShaderType::Compute: const auto workgroup_size = specialization.workgroup_size; @@ -972,7 +973,7 @@ private: return binding; } - void DeclareImage(const Image& image, u32& binding) { + void DeclareImage(const ImageEntry& image, u32& binding) { const auto [dim, arrayed] = GetImageDim(image); constexpr int depth = 0; constexpr bool ms = false; @@ -1080,9 +1081,9 @@ private: indices.point_size = AddBuiltIn(t_float, spv::BuiltIn::PointSize, "point_size"); } - const auto& output_attributes = ir.GetOutputAttributes(); - const bool declare_clip_distances = - std::any_of(output_attributes.begin(), output_attributes.end(), [](const auto& index) { + const auto& ir_output_attributes = ir.GetOutputAttributes(); + const bool declare_clip_distances = std::any_of( + ir_output_attributes.begin(), ir_output_attributes.end(), [](const auto& index) { return index == Attribute::Index::ClipDistances0123 || index == Attribute::Index::ClipDistances4567; }); @@ -1246,7 +1247,7 @@ private: const Id pointer = ArrayPass(type_descriptor.scalar, attribute_id, elements); return {OpLoad(GetTypeDefinition(type), pointer), type}; } - UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); + UNIMPLEMENTED_MSG("Unhandled input attribute: {}", attribute); return {v_float_zero, Type::Float}; } @@ -1333,7 +1334,10 @@ private: } if (const auto comment = std::get_if<CommentNode>(&*node)) { - Name(OpUndef(t_void), comment->GetText()); + if (device.HasDebuggingToolAttached()) { + // We should insert comments with OpString instead of using named variables + Name(OpUndef(t_int), comment->GetText()); + } return {}; } @@ -1882,7 +1886,7 @@ private: case Tegra::Shader::TextureType::Texture3D: return 3; default: - UNREACHABLE_MSG("Invalid texture type={}", static_cast<int>(type)); + UNREACHABLE_MSG("Invalid texture type={}", type); return 2; } }(); @@ -2067,6 +2071,46 @@ private: return {}; } + Id MaxwellToSpirvComparison(Maxwell::ComparisonOp compare_op, Id operand_1, Id operand_2) { + using Compare = Maxwell::ComparisonOp; + switch (compare_op) { + case Compare::NeverOld: + return v_false; // Never let the test pass + case Compare::LessOld: + return OpFOrdLessThan(t_bool, operand_1, operand_2); + case Compare::EqualOld: + return OpFOrdEqual(t_bool, operand_1, operand_2); + case Compare::LessEqualOld: + return OpFOrdLessThanEqual(t_bool, operand_1, operand_2); + case Compare::GreaterOld: + return OpFOrdGreaterThan(t_bool, operand_1, operand_2); + case Compare::NotEqualOld: + return OpFOrdNotEqual(t_bool, operand_1, operand_2); + case Compare::GreaterEqualOld: + return OpFOrdGreaterThanEqual(t_bool, operand_1, operand_2); + default: + UNREACHABLE(); + return v_true; + } + } + + void AlphaTest(Id pointer) { + if (specialization.alpha_test_func == Maxwell::ComparisonOp::AlwaysOld) { + return; + } + const Id true_label = OpLabel(); + const Id discard_label = OpLabel(); + const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref); + const Id alpha_value = OpLoad(t_float, pointer); + const Id condition = + MaxwellToSpirvComparison(specialization.alpha_test_func, alpha_value, alpha_reference); + + OpBranchConditional(condition, true_label, discard_label); + AddLabel(discard_label); + OpKill(); + AddLabel(true_label); + } + void PreExit() { if (stage == ShaderType::Vertex && specialization.ndc_minus_one_to_one) { const u32 position_index = out_indices.position.value(); @@ -2078,8 +2122,7 @@ private: OpStore(z_pointer, depth); } if (stage == ShaderType::Fragment) { - const auto SafeGetRegister = [&](u32 reg) { - // TODO(Rodrigo): Replace with contains once C++20 releases + const auto SafeGetRegister = [this](u32 reg) { if (const auto it = registers.find(reg); it != registers.end()) { return OpLoad(t_float, it->second); } @@ -2089,8 +2132,6 @@ private: UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); - // TODO(Rodrigo): Alpha testing - // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. u32 current_reg = 0; @@ -2102,6 +2143,9 @@ private: } const Id pointer = AccessElement(t_out_float, frag_colors[rt], component); OpStore(pointer, SafeGetRegister(current_reg)); + if (rt == 0 && component == 3) { + AlphaTest(pointer); + } ++current_reg; } } @@ -2701,7 +2745,7 @@ private: }; static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount)); - const VKDevice& device; + const Device& device; const ShaderIR& ir; const ShaderType stage; const Tegra::Shader::Header header; @@ -2843,7 +2887,7 @@ private: class ExprDecompiler { public: - explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {} + explicit ExprDecompiler(SPIRVDecompiler& decomp_) : decomp{decomp_} {} Id operator()(const ExprAnd& expr) { const Id type_def = decomp.GetTypeDefinition(Type::Bool); @@ -2899,7 +2943,7 @@ private: class ASTDecompiler { public: - explicit ASTDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {} + explicit ASTDecompiler(SPIRVDecompiler& decomp_) : decomp{decomp_} {} void operator()(const ASTProgram& ast) { ASTNode current = ast.nodes.GetFirst(); @@ -3062,7 +3106,11 @@ ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir) { entries.const_buffers.emplace_back(cbuf.second, cbuf.first); } for (const auto& [base, usage] : ir.GetGlobalMemory()) { - entries.global_buffers.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_written); + entries.global_buffers.emplace_back(GlobalBufferEntry{ + .cbuf_index = base.cbuf_index, + .cbuf_offset = base.cbuf_offset, + .is_written = usage.is_written, + }); } for (const auto& sampler : ir.GetSamplers()) { if (sampler.is_buffer) { @@ -3083,13 +3131,16 @@ ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir) { entries.attributes.insert(GetGenericAttributeLocation(attribute)); } } + for (const auto& buffer : entries.const_buffers) { + entries.enabled_uniform_buffers |= 1U << buffer.GetIndex(); + } entries.clip_distances = ir.GetClipDistances(); entries.shader_length = ir.GetLength(); entries.uses_warps = ir.UsesWarps(); return entries; } -std::vector<u32> Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir, +std::vector<u32> Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir, ShaderType stage, const VideoCommon::Shader::Registry& registry, const Specialization& specialization) { return SPIRVDecompiler(device, ir, stage, registry, specialization).Assemble(); diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h index 2b0e90396..5d94132a5 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h @@ -15,23 +15,21 @@ #include "video_core/shader/shader_ir.h" namespace Vulkan { -class VKDevice; -} -namespace Vulkan { +class Device; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -using UniformTexelEntry = VideoCommon::Shader::Sampler; -using SamplerEntry = VideoCommon::Shader::Sampler; -using StorageTexelEntry = VideoCommon::Shader::Image; -using ImageEntry = VideoCommon::Shader::Image; +using UniformTexelEntry = VideoCommon::Shader::SamplerEntry; +using SamplerEntry = VideoCommon::Shader::SamplerEntry; +using StorageTexelEntry = VideoCommon::Shader::ImageEntry; +using ImageEntry = VideoCommon::Shader::ImageEntry; constexpr u32 DESCRIPTOR_SET = 0; class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer { public: - explicit constexpr ConstBufferEntry(const VideoCommon::Shader::ConstBuffer& entry, u32 index) - : VideoCommon::Shader::ConstBuffer{entry}, index{index} {} + explicit constexpr ConstBufferEntry(const ConstBuffer& entry_, u32 index_) + : ConstBuffer{entry_}, index{index_} {} constexpr u32 GetIndex() const { return index; @@ -41,24 +39,7 @@ private: u32 index{}; }; -class GlobalBufferEntry { -public: - constexpr explicit GlobalBufferEntry(u32 cbuf_index, u32 cbuf_offset, bool is_written) - : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_written{is_written} {} - - constexpr u32 GetCbufIndex() const { - return cbuf_index; - } - - constexpr u32 GetCbufOffset() const { - return cbuf_offset; - } - - constexpr bool IsWritten() const { - return is_written; - } - -private: +struct GlobalBufferEntry { u32 cbuf_index{}; u32 cbuf_offset{}; bool is_written{}; @@ -80,6 +61,7 @@ struct ShaderEntries { std::set<u32> attributes; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; std::size_t shader_length{}; + u32 enabled_uniform_buffers{}; bool uses_warps{}; }; @@ -95,6 +77,9 @@ struct Specialization final { std::bitset<Maxwell::NumVertexAttributes> enabled_attributes; std::array<Maxwell::VertexAttribute::Type, Maxwell::NumVertexAttributes> attribute_types{}; bool ndc_minus_one_to_one{}; + bool early_fragment_tests{}; + float alpha_test_ref{}; + Maxwell::ComparisonOp alpha_test_func{}; }; // Old gcc versions don't consider this trivially copyable. // static_assert(std::is_trivially_copyable_v<Specialization>); @@ -106,7 +91,7 @@ struct SPIRVShader { ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir); -std::vector<u32> Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir, +std::vector<u32> Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir, Tegra::Engines::ShaderType stage, const VideoCommon::Shader::Registry& registry, const Specialization& specialization); diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index c1a218d76..aaad4f292 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -7,24 +7,19 @@ #include "common/assert.h" #include "common/common_types.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_shader_util.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data) { - // Avoid undefined behavior by copying to a staging allocation - ASSERT(code_size % sizeof(u32) == 0); - const auto data = std::make_unique<u32[]>(code_size / sizeof(u32)); - std::memcpy(data.get(), code_data, code_size); - +vk::ShaderModule BuildShader(const Device& device, std::span<const u32> code) { return device.GetLogical().CreateShaderModule({ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .codeSize = code_size, - .pCode = data.get(), + .codeSize = static_cast<u32>(code.size_bytes()), + .pCode = code.data(), }); } diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h index d1d3f3cae..9517cbe84 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.h +++ b/src/video_core/renderer_vulkan/vk_shader_util.h @@ -4,13 +4,15 @@ #pragma once +#include <span> + #include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; +class Device; -vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data); +vk::ShaderModule BuildShader(const Device& device, std::span<const u32> code); } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 5eca0ab91..7a1232497 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -3,76 +3,214 @@ // Refer to the license.txt file included. #include <algorithm> -#include <unordered_map> #include <utility> #include <vector> +#include <fmt/format.h> + +#include "common/alignment.h" +#include "common/assert.h" #include "common/bit_util.h" #include "common/common_types.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { +namespace { +// Maximum potential alignment of a Vulkan buffer +constexpr VkDeviceSize MAX_ALIGNMENT = 256; +// Maximum size to put elements in the stream buffer +constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8 * 1024 * 1024; +// Stream buffer size in bytes +constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128 * 1024 * 1024; +constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS; -VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, - u64 last_epoch) - : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {} +constexpr VkMemoryPropertyFlags HOST_FLAGS = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +constexpr VkMemoryPropertyFlags STREAM_FLAGS = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | HOST_FLAGS; -VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept { - buffer = std::move(rhs.buffer); - watch = std::move(rhs.watch); - last_epoch = rhs.last_epoch; +bool IsStreamHeap(VkMemoryHeap heap) noexcept { + return STREAM_BUFFER_SIZE < (heap.size * 2) / 3; } -VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default; +std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask, + VkMemoryPropertyFlags flags) noexcept { + for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) { + if (((type_mask >> type_index) & 1) == 0) { + // Memory type is incompatible + continue; + } + const VkMemoryType& memory_type = props.memoryTypes[type_index]; + if ((memory_type.propertyFlags & flags) != flags) { + // Memory type doesn't have the flags we want + continue; + } + if (!IsStreamHeap(props.memoryHeaps[memory_type.heapIndex])) { + // Memory heap is not suitable for streaming + continue; + } + // Success! + return type_index; + } + return std::nullopt; +} -VKStagingBufferPool::StagingBuffer& VKStagingBufferPool::StagingBuffer::operator=( - StagingBuffer&& rhs) noexcept { - buffer = std::move(rhs.buffer); - watch = std::move(rhs.watch); - last_epoch = rhs.last_epoch; - return *this; +u32 FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask) { + // Try to find a DEVICE_LOCAL_BIT type, Nvidia and AMD have a dedicated heap for this + std::optional<u32> type = FindMemoryTypeIndex(props, type_mask, STREAM_FLAGS); + if (type) { + return *type; + } + // Otherwise try without the DEVICE_LOCAL_BIT + type = FindMemoryTypeIndex(props, type_mask, HOST_FLAGS); + if (type) { + return *type; + } + // This should never happen, and in case it does, signal it as an out of memory situation + throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY); } -VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler) - : device{device}, memory_manager{memory_manager}, scheduler{scheduler} {} +size_t Region(size_t iterator) noexcept { + return iterator / REGION_SIZE; +} +} // Anonymous namespace -VKStagingBufferPool::~VKStagingBufferPool() = default; +StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, + VKScheduler& scheduler_) + : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { + const vk::Device& dev = device.GetLogical(); + stream_buffer = dev.CreateBuffer(VkBufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = STREAM_BUFFER_SIZE, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }); + if (device.HasDebuggingToolAttached()) { + stream_buffer.SetObjectNameEXT("Stream Buffer"); + } + VkMemoryDedicatedRequirements dedicated_reqs{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS, + .pNext = nullptr, + .prefersDedicatedAllocation = VK_FALSE, + .requiresDedicatedAllocation = VK_FALSE, + }; + const auto requirements = dev.GetBufferMemoryRequirements(*stream_buffer, &dedicated_reqs); + const bool make_dedicated = dedicated_reqs.prefersDedicatedAllocation == VK_TRUE || + dedicated_reqs.requiresDedicatedAllocation == VK_TRUE; + const VkMemoryDedicatedAllocateInfo dedicated_info{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = nullptr, + .image = nullptr, + .buffer = *stream_buffer, + }; + const auto memory_properties = device.GetPhysical().GetMemoryProperties(); + stream_memory = dev.AllocateMemory(VkMemoryAllocateInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = make_dedicated ? &dedicated_info : nullptr, + .allocationSize = requirements.size, + .memoryTypeIndex = FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits), + }); + if (device.HasDebuggingToolAttached()) { + stream_memory.SetObjectNameEXT("Stream Buffer Memory"); + } + stream_buffer.BindMemory(*stream_memory, 0); + stream_pointer = stream_memory.Map(0, STREAM_BUFFER_SIZE); +} -VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visible) { - if (const auto buffer = TryGetReservedBuffer(size, host_visible)) { - return *buffer; +StagingBufferPool::~StagingBufferPool() = default; + +StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage) { + if (usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) { + return GetStreamBuffer(size); } - return CreateStagingBuffer(size, host_visible); + return GetStagingBuffer(size, usage); } -void VKStagingBufferPool::TickFrame() { - ++epoch; - current_delete_level = (current_delete_level + 1) % NumLevels; +void StagingBufferPool::TickFrame() { + current_delete_level = (current_delete_level + 1) % NUM_LEVELS; - ReleaseCache(true); - ReleaseCache(false); + ReleaseCache(MemoryUsage::DeviceLocal); + ReleaseCache(MemoryUsage::Upload); + ReleaseCache(MemoryUsage::Download); } -VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) { - for (auto& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) { - if (entry.watch.TryWatch(scheduler.GetFence())) { - entry.last_epoch = epoch; - return &*entry.buffer; +StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { + if (AreRegionsActive(Region(free_iterator) + 1, + std::min(Region(iterator + size) + 1, NUM_SYNCS))) { + // Avoid waiting for the previous usages to be free + return GetStagingBuffer(size, MemoryUsage::Upload); + } + const u64 current_tick = scheduler.CurrentTick(); + std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator), + current_tick); + used_iterator = iterator; + free_iterator = std::max(free_iterator, iterator + size); + + if (iterator + size >= STREAM_BUFFER_SIZE) { + std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, + current_tick); + used_iterator = 0; + iterator = 0; + free_iterator = size; + + if (AreRegionsActive(0, Region(size) + 1)) { + // Avoid waiting for the previous usages to be free + return GetStagingBuffer(size, MemoryUsage::Upload); } } - return nullptr; + const size_t offset = iterator; + iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); + return StagingBufferRef{ + .buffer = *stream_buffer, + .offset = static_cast<VkDeviceSize>(offset), + .mapped_span = std::span<u8>(stream_pointer + offset, size), + }; } -VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) { - const u32 log2 = Common::Log2Ceil64(size); +bool StagingBufferPool::AreRegionsActive(size_t region_begin, size_t region_end) const { + const u64 gpu_tick = scheduler.GetMasterSemaphore().KnownGpuTick(); + return std::any_of(sync_ticks.begin() + region_begin, sync_ticks.begin() + region_end, + [gpu_tick](u64 sync_tick) { return gpu_tick < sync_tick; }); +}; - auto buffer = std::make_unique<VKBuffer>(); - buffer->handle = device.GetLogical().CreateBuffer({ +StagingBufferRef StagingBufferPool::GetStagingBuffer(size_t size, MemoryUsage usage) { + if (const std::optional<StagingBufferRef> ref = TryGetReservedBuffer(size, usage)) { + return *ref; + } + return CreateStagingBuffer(size, usage); +} + +std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t size, + MemoryUsage usage) { + StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil64(size)]; + + const auto is_free = [this](const StagingBuffer& entry) { + return scheduler.IsFree(entry.tick); + }; + auto& entries = cache_level.entries; + const auto hint_it = entries.begin() + cache_level.iterate_index; + auto it = std::find_if(entries.begin() + cache_level.iterate_index, entries.end(), is_free); + if (it == entries.end()) { + it = std::find_if(entries.begin(), hint_it, is_free); + if (it == hint_it) { + return std::nullopt; + } + } + cache_level.iterate_index = std::distance(entries.begin(), it) + 1; + it->tick = scheduler.CurrentTick(); + return it->Ref(); +} + +StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage) { + const u32 log2 = Common::Log2Ceil64(size); + vk::Buffer buffer = device.GetLogical().CreateBuffer({ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -84,48 +222,63 @@ VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_v .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, }); - buffer->commit = memory_manager.Commit(buffer->handle, host_visible); - - auto& entries = GetCache(host_visible)[log2].entries; - return *entries.emplace_back(std::move(buffer), scheduler.GetFence(), epoch).buffer; -} + if (device.HasDebuggingToolAttached()) { + ++buffer_index; + buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str()); + } + MemoryCommit commit = memory_allocator.Commit(buffer, usage); + const std::span<u8> mapped_span = IsHostVisible(usage) ? commit.Map() : std::span<u8>{}; -VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) { - return host_visible ? host_staging_buffers : device_staging_buffers; + StagingBuffer& entry = GetCache(usage)[log2].entries.emplace_back(StagingBuffer{ + .buffer = std::move(buffer), + .commit = std::move(commit), + .mapped_span = mapped_span, + .tick = scheduler.CurrentTick(), + }); + return entry.Ref(); } -void VKStagingBufferPool::ReleaseCache(bool host_visible) { - auto& cache = GetCache(host_visible); - const u64 size = ReleaseLevel(cache, current_delete_level); - if (size == 0) { - return; +StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + return device_local_cache; + case MemoryUsage::Upload: + return upload_cache; + case MemoryUsage::Download: + return download_cache; + default: + UNREACHABLE_MSG("Invalid memory usage={}", usage); + return upload_cache; } } -u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t log2) { - static constexpr std::size_t deletions_per_tick = 16; +void StagingBufferPool::ReleaseCache(MemoryUsage usage) { + ReleaseLevel(GetCache(usage), current_delete_level); +} +void StagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, size_t log2) { + constexpr size_t deletions_per_tick = 16; auto& staging = cache[log2]; auto& entries = staging.entries; - const std::size_t old_size = entries.size(); + const size_t old_size = entries.size(); - const auto is_deleteable = [this](const auto& entry) { - static constexpr u64 epochs_to_destroy = 180; - return entry.last_epoch + epochs_to_destroy < epoch && !entry.watch.IsUsed(); + const auto is_deleteable = [this](const StagingBuffer& entry) { + return scheduler.IsFree(entry.tick); }; - const std::size_t begin_offset = staging.delete_index; - const std::size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size); - const auto begin = std::begin(entries) + begin_offset; - const auto end = std::begin(entries) + end_offset; + const size_t begin_offset = staging.delete_index; + const size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size); + const auto begin = entries.begin() + begin_offset; + const auto end = entries.begin() + end_offset; entries.erase(std::remove_if(begin, end, is_deleteable), end); - const std::size_t new_size = entries.size(); + const size_t new_size = entries.size(); staging.delete_index += deletions_per_tick; if (staging.delete_index >= new_size) { staging.delete_index = 0; } - - return (1ULL << log2) * (old_size - new_size); + if (staging.iterate_index > new_size) { + staging.iterate_index = 0; + } } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index 3c4901437..69f7618de 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -9,73 +9,97 @@ #include "common/common_types.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; -class VKFenceWatch; +class Device; class VKScheduler; -struct VKBuffer final { - vk::Buffer handle; - VKMemoryCommit commit; +struct StagingBufferRef { + VkBuffer buffer; + VkDeviceSize offset; + std::span<u8> mapped_span; }; -class VKStagingBufferPool final { +class StagingBufferPool { public: - explicit VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler); - ~VKStagingBufferPool(); + static constexpr size_t NUM_SYNCS = 16; - VKBuffer& GetUnusedBuffer(std::size_t size, bool host_visible); + explicit StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator, + VKScheduler& scheduler); + ~StagingBufferPool(); + + StagingBufferRef Request(size_t size, MemoryUsage usage); void TickFrame(); private: - struct StagingBuffer final { - explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, u64 last_epoch); - StagingBuffer(StagingBuffer&& rhs) noexcept; - StagingBuffer(const StagingBuffer&) = delete; - ~StagingBuffer(); - - StagingBuffer& operator=(StagingBuffer&& rhs) noexcept; + struct StreamBufferCommit { + size_t upper_bound; + u64 tick; + }; - std::unique_ptr<VKBuffer> buffer; - VKFenceWatch watch; - u64 last_epoch = 0; + struct StagingBuffer { + vk::Buffer buffer; + MemoryCommit commit; + std::span<u8> mapped_span; + u64 tick = 0; + + StagingBufferRef Ref() const noexcept { + return { + .buffer = *buffer, + .offset = 0, + .mapped_span = mapped_span, + }; + } }; - struct StagingBuffers final { + struct StagingBuffers { std::vector<StagingBuffer> entries; - std::size_t delete_index = 0; + size_t delete_index = 0; + size_t iterate_index = 0; }; - static constexpr std::size_t NumLevels = sizeof(std::size_t) * CHAR_BIT; - using StagingBuffersCache = std::array<StagingBuffers, NumLevels>; + static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT; + using StagingBuffersCache = std::array<StagingBuffers, NUM_LEVELS>; + + StagingBufferRef GetStreamBuffer(size_t size); - VKBuffer* TryGetReservedBuffer(std::size_t size, bool host_visible); + bool AreRegionsActive(size_t region_begin, size_t region_end) const; - VKBuffer& CreateStagingBuffer(std::size_t size, bool host_visible); + StagingBufferRef GetStagingBuffer(size_t size, MemoryUsage usage); - StagingBuffersCache& GetCache(bool host_visible); + std::optional<StagingBufferRef> TryGetReservedBuffer(size_t size, MemoryUsage usage); - void ReleaseCache(bool host_visible); + StagingBufferRef CreateStagingBuffer(size_t size, MemoryUsage usage); - u64 ReleaseLevel(StagingBuffersCache& cache, std::size_t log2); + StagingBuffersCache& GetCache(MemoryUsage usage); - const VKDevice& device; - VKMemoryManager& memory_manager; + void ReleaseCache(MemoryUsage usage); + + void ReleaseLevel(StagingBuffersCache& cache, size_t log2); + + const Device& device; + MemoryAllocator& memory_allocator; VKScheduler& scheduler; - StagingBuffersCache host_staging_buffers; - StagingBuffersCache device_staging_buffers; + vk::Buffer stream_buffer; + vk::DeviceMemory stream_memory; + u8* stream_pointer = nullptr; + + size_t iterator = 0; + size_t used_iterator = 0; + size_t free_iterator = 0; + std::array<u64, NUM_SYNCS> sync_ticks{}; - u64 epoch = 0; + StagingBuffersCache device_local_cache; + StagingBuffersCache upload_cache; + StagingBuffersCache download_cache; - std::size_t current_delete_level = 0; + size_t current_delete_level = 0; + u64 buffer_index = 0; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index 4bd1009f9..956f86845 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <cstddef> #include <iterator> @@ -14,12 +15,10 @@ #include "video_core/renderer_vulkan/vk_state_tracker.h" #define OFF(field_name) MAXWELL3D_REG_INDEX(field_name) -#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / sizeof(u32)) +#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32))) namespace Vulkan { - namespace { - using namespace Dirty; using namespace VideoCommon::Dirty; using Tegra::Engines::Maxwell3D; @@ -29,21 +28,18 @@ using Table = Maxwell3D::DirtyState::Table; using Flags = Maxwell3D::DirtyState::Flags; Flags MakeInvalidationFlags() { + static constexpr int INVALIDATION_FLAGS[]{ + Viewports, Scissors, DepthBias, BlendConstants, DepthBounds, + StencilProperties, CullMode, DepthBoundsEnable, DepthTestEnable, DepthWriteEnable, + DepthCompareOp, FrontFace, StencilOp, StencilTestEnable, VertexBuffers, + }; Flags flags{}; - flags[Viewports] = true; - flags[Scissors] = true; - flags[DepthBias] = true; - flags[BlendConstants] = true; - flags[DepthBounds] = true; - flags[StencilProperties] = true; - flags[CullMode] = true; - flags[DepthBoundsEnable] = true; - flags[DepthTestEnable] = true; - flags[DepthWriteEnable] = true; - flags[DepthCompareOp] = true; - flags[FrontFace] = true; - flags[StencilOp] = true; - flags[StencilTestEnable] = true; + for (const int flag : INVALIDATION_FLAGS) { + flags[flag] = true; + } + for (int index = VertexBuffer0; index <= VertexBuffer31; ++index) { + flags[index] = true; + } return flags; } @@ -130,15 +126,40 @@ void SetupDirtyStencilTestEnable(Tables& tables) { tables[0][OFF(stencil_enable)] = StencilTestEnable; } -} // Anonymous namespace +void SetupDirtyBlending(Tables& tables) { + tables[0][OFF(color_mask_common)] = Blending; + tables[0][OFF(independent_blend_enable)] = Blending; + FillBlock(tables[0], OFF(color_mask), NUM(color_mask), Blending); + FillBlock(tables[0], OFF(blend), NUM(blend), Blending); + FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending); +} + +void SetupDirtyInstanceDivisors(Tables& tables) { + static constexpr size_t divisor_offset = 3; + for (size_t index = 0; index < Regs::NumVertexArrays; ++index) { + tables[0][OFF(instanced_arrays) + index] = InstanceDivisors; + tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] = + InstanceDivisors; + } +} -StateTracker::StateTracker(Core::System& system) - : system{system}, invalidation_flags{MakeInvalidationFlags()} {} +void SetupDirtyVertexAttributes(Tables& tables) { + FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes); +} + +void SetupDirtyViewportSwizzles(Tables& tables) { + static constexpr size_t swizzle_offset = 6; + for (size_t index = 0; index < Regs::NumViewports; ++index) { + tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = + ViewportSwizzles; + } +} +} // Anonymous namespace -void StateTracker::Initialize() { - auto& dirty = system.GPU().Maxwell3D().dirty; - auto& tables = dirty.tables; - SetupDirtyRenderTargets(tables); +StateTracker::StateTracker(Tegra::GPU& gpu) + : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} { + auto& tables = gpu.Maxwell3D().dirty.tables; + SetupDirtyFlags(tables); SetupDirtyViewports(tables); SetupDirtyScissors(tables); SetupDirtyDepthBias(tables); @@ -153,11 +174,10 @@ void StateTracker::Initialize() { SetupDirtyFrontFace(tables); SetupDirtyStencilOp(tables); SetupDirtyStencilTestEnable(tables); -} - -void StateTracker::InvalidateCommandBufferState() { - system.GPU().Maxwell3D().dirty.flags |= invalidation_flags; - current_topology = INVALID_TOPOLOGY; + SetupDirtyBlending(tables); + SetupDirtyInstanceDivisors(tables); + SetupDirtyVertexAttributes(tables); + SetupDirtyViewportSwizzles(tables); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 13a6ce786..84e918a71 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -35,6 +35,11 @@ enum : u8 { StencilOp, StencilTestEnable, + Blending, + InstanceDivisors, + VertexAttributes, + ViewportSwizzles, + Last }; static_assert(Last <= std::numeric_limits<u8>::max()); @@ -45,11 +50,20 @@ class StateTracker { using Maxwell = Tegra::Engines::Maxwell3D::Regs; public: - explicit StateTracker(Core::System& system); + explicit StateTracker(Tegra::GPU& gpu); - void Initialize(); + void InvalidateCommandBufferState() { + flags |= invalidation_flags; + current_topology = INVALID_TOPOLOGY; + } - void InvalidateCommandBufferState(); + void InvalidateViewports() { + flags[Dirty::Viewports] = true; + } + + void InvalidateScissors() { + flags[Dirty::Scissors] = true; + } bool TouchViewports() { return Exchange(Dirty::Viewports, false); @@ -121,13 +135,12 @@ private: static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u); bool Exchange(std::size_t id, bool new_value) const noexcept { - auto& flags = system.GPU().Maxwell3D().dirty.flags; const bool is_dirty = flags[id]; flags[id] = new_value; return is_dirty; } - Core::System& system; + Tegra::Engines::Maxwell3D::DirtyState::Flags& flags; Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags; Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY; }; diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index a5526a3f5..a09fe084e 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -10,16 +10,19 @@ #include "common/alignment.h" #include "common/assert.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_stream_buffer.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { namespace { +constexpr VkBufferUsageFlags BUFFER_USAGE = + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; @@ -57,17 +60,16 @@ u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties, } // Anonymous namespace -VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler, - VkBufferUsageFlags usage) - : device{device}, scheduler{scheduler} { - CreateBuffers(usage); +VKStreamBuffer::VKStreamBuffer(const Device& device_, VKScheduler& scheduler_) + : device{device_}, scheduler{scheduler_} { + CreateBuffers(); ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); } VKStreamBuffer::~VKStreamBuffer() = default; -std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) { +std::pair<u8*, u64> VKStreamBuffer::Map(u64 size, u64 alignment) { ASSERT(size <= stream_buffer_size); mapped_size = size; @@ -77,7 +79,6 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) { WaitPendingOperations(offset); - bool invalidated = false; if (offset + size > stream_buffer_size) { // The buffer would overflow, save the amount of used watches and reset the state. invalidation_mark = current_watch_cursor; @@ -91,11 +92,9 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) { // Ensure that we don't wait for uncommitted fences. scheduler.Flush(); - - invalidated = true; } - return {memory.Map(offset, size), offset, invalidated}; + return std::make_pair(memory.Map(offset, size), offset); } void VKStreamBuffer::Unmap(u64 size) { @@ -111,23 +110,24 @@ void VKStreamBuffer::Unmap(u64 size) { } auto& watch = current_watches[current_watch_cursor++]; watch.upper_bound = offset; - watch.fence.Watch(scheduler.GetFence()); + watch.tick = scheduler.CurrentTick(); } -void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) { +void VKStreamBuffer::CreateBuffers() { const auto memory_properties = device.GetPhysical().GetMemoryProperties(); const u32 preferred_type = GetMemoryType(memory_properties); const u32 preferred_heap = memory_properties.memoryTypes[preferred_type].heapIndex; // Substract from the preferred heap size some bytes to avoid getting out of memory. const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; - const VkDeviceSize allocable_size = heap_size - 9 * 1024 * 1024; + // As per DXVK's example, using `heap_size / 2` + const VkDeviceSize allocable_size = heap_size / 2; buffer = device.GetLogical().CreateBuffer({ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, .size = std::min(PREFERRED_STREAM_BUFFER_SIZE, allocable_size), - .usage = usage, + .usage = BUFFER_USAGE, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -157,7 +157,7 @@ void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) { auto& watch = previous_watches[wait_cursor]; wait_bound = watch.upper_bound; - watch.fence.Wait(); + scheduler.Wait(watch.tick); ++wait_cursor; } } diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h index 689f0d276..2e9c8cb46 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.h +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h @@ -5,32 +5,29 @@ #pragma once #include <optional> -#include <tuple> +#include <utility> #include <vector> #include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; -class VKFence; +class Device; class VKFenceWatch; class VKScheduler; class VKStreamBuffer final { public: - explicit VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler, - VkBufferUsageFlags usage); + explicit VKStreamBuffer(const Device& device, VKScheduler& scheduler); ~VKStreamBuffer(); /** * Reserves a region of memory from the stream buffer. * @param size Size to reserve. - * @returns A tuple in the following order: Raw memory pointer (with offset added), buffer - * offset and a boolean that's true when buffer has been invalidated. + * @returns A pair of a raw memory pointer (with offset added), and the buffer offset */ - std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment); + std::pair<u8*, u64> Map(u64 size, u64 alignment); /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. void Unmap(u64 size); @@ -44,20 +41,20 @@ public: } private: - struct Watch final { - VKFenceWatch fence; + struct Watch { + u64 tick{}; u64 upper_bound{}; }; /// Creates Vulkan buffer handles committing the required the required memory. - void CreateBuffers(VkBufferUsageFlags usage); + void CreateBuffers(); /// Increases the amount of watches available. void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size); void WaitPendingOperations(u64 requested_upper_bound); - const VKDevice& device; ///< Vulkan device manager. + const Device& device; ///< Vulkan device manager. VKScheduler& scheduler; ///< Command scheduler. vk::Buffer buffer; ///< Mapped buffer. diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 6bfd2abae..0b63bd6c8 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -11,10 +11,10 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_swapchain.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -56,8 +56,11 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi } // Anonymous namespace -VKSwapchain::VKSwapchain(VkSurfaceKHR surface, const VKDevice& device) - : surface{surface}, device{device} {} +VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const Device& device_, VKScheduler& scheduler_, + u32 width, u32 height, bool srgb) + : surface{surface_}, device{device_}, scheduler{scheduler_} { + Create(width, height, srgb); +} VKSwapchain::~VKSwapchain() = default; @@ -75,21 +78,18 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) { CreateSemaphores(); CreateImageViews(); - fences.resize(image_count, nullptr); + resource_ticks.clear(); + resource_ticks.resize(image_count); } void VKSwapchain::AcquireNextImage() { device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index], {}, &image_index); - if (auto& fence = fences[image_index]; fence) { - fence->Wait(); - fence->Release(); - fence = nullptr; - } + scheduler.Wait(resource_ticks[image_index]); } -bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) { +bool VKSwapchain::Present(VkSemaphore render_semaphore) { const VkSemaphore present_semaphore{*present_semaphores[frame_index]}; const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore}; const auto present_queue{device.GetPresentQueue()}; @@ -123,8 +123,7 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) { break; } - ASSERT(fences[image_index] == nullptr); - fences[image_index] = &fence; + resource_ticks[image_index] = scheduler.CurrentTick(); frame_index = (frame_index + 1) % static_cast<u32>(image_count); return recreated; } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index a35d61345..a728511e0 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -7,7 +7,7 @@ #include <vector> #include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Layout { struct FramebufferLayout; @@ -15,12 +15,13 @@ struct FramebufferLayout; namespace Vulkan { -class VKDevice; -class VKFence; +class Device; +class VKScheduler; class VKSwapchain { public: - explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device); + explicit VKSwapchain(VkSurfaceKHR surface, const Device& device, VKScheduler& scheduler, + u32 width, u32 height, bool srgb); ~VKSwapchain(); /// Creates (or recreates) the swapchain with a given size. @@ -31,7 +32,7 @@ public: /// Presents the rendered image to the swapchain. Returns true when the swapchains had to be /// recreated. Takes responsability for the ownership of fence. - bool Present(VkSemaphore render_semaphore, VKFence& fence); + bool Present(VkSemaphore render_semaphore); /// Returns true when the framebuffer layout has changed. bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const; @@ -73,7 +74,8 @@ private: void Destroy(); const VkSurfaceKHR surface; - const VKDevice& device; + const Device& device; + VKScheduler& scheduler; vk::SwapchainKHR swapchain; @@ -81,7 +83,7 @@ private: std::vector<VkImage> images; std::vector<vk::ImageView> image_views; std::vector<vk::Framebuffer> framebuffers; - std::vector<VKFence*> fences; + std::vector<u64> resource_ticks; std::vector<vk::Semaphore> present_semaphores; u32 image_index{}; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 2c6f54101..22a1014a9 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -4,614 +4,1167 @@ #include <algorithm> #include <array> -#include <cstddef> -#include <cstring> -#include <memory> -#include <variant> +#include <span> #include <vector> -#include "common/assert.h" -#include "common/common_types.h" -#include "core/core.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/morton.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" -#include "video_core/renderer_vulkan/wrapper.h" -#include "video_core/surface.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -using VideoCore::MortonSwizzle; -using VideoCore::MortonSwizzleMode; - +using Tegra::Engines::Fermi2D; using Tegra::Texture::SwizzleSource; -using VideoCore::Surface::PixelFormat; -using VideoCore::Surface::SurfaceTarget; +using Tegra::Texture::TextureMipmapFilter; +using VideoCommon::BufferImageCopy; +using VideoCommon::ImageInfo; +using VideoCommon::ImageType; +using VideoCommon::SubresourceRange; +using VideoCore::Surface::IsPixelFormatASTC; namespace { -VkImageType SurfaceTargetToImage(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture1DArray: +constexpr std::array ATTACHMENT_REFERENCES{ + VkAttachmentReference{0, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{1, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{2, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{3, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{4, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{5, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{6, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{7, VK_IMAGE_LAYOUT_GENERAL}, + VkAttachmentReference{8, VK_IMAGE_LAYOUT_GENERAL}, +}; + +constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { + if (color == std::array<float, 4>{0, 0, 0, 0}) { + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + } else if (color == std::array<float, 4>{0, 0, 0, 1}) { + return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + } else if (color == std::array<float, 4>{1, 1, 1, 1}) { + return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } + if (color[0] + color[1] + color[2] > 1.35f) { + // If color elements are brighter than roughly 0.5 average, use white border + return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } else if (color[3] > 0.5f) { + return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + } else { + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + } +} + +[[nodiscard]] VkImageType ConvertImageType(const ImageType type) { + switch (type) { + case ImageType::e1D: return VK_IMAGE_TYPE_1D; - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: + case ImageType::e2D: + case ImageType::Linear: return VK_IMAGE_TYPE_2D; - case SurfaceTarget::Texture3D: + case ImageType::e3D: return VK_IMAGE_TYPE_3D; - case SurfaceTarget::TextureBuffer: - UNREACHABLE(); - return {}; + case ImageType::Buffer: + break; } - UNREACHABLE_MSG("Unknown texture target={}", static_cast<u32>(target)); + UNREACHABLE_MSG("Invalid image type={}", type); return {}; } -VkImageAspectFlags PixelFormatToImageAspect(PixelFormat pixel_format) { - if (pixel_format < PixelFormat::MaxColorFormat) { - return VK_IMAGE_ASPECT_COLOR_BIT; - } else if (pixel_format < PixelFormat::MaxDepthFormat) { - return VK_IMAGE_ASPECT_DEPTH_BIT; - } else if (pixel_format < PixelFormat::MaxDepthStencilFormat) { - return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - } else { - UNREACHABLE_MSG("Invalid pixel format={}", static_cast<int>(pixel_format)); - return VK_IMAGE_ASPECT_COLOR_BIT; +[[nodiscard]] VkSampleCountFlagBits ConvertSampleCount(u32 num_samples) { + switch (num_samples) { + case 1: + return VK_SAMPLE_COUNT_1_BIT; + case 2: + return VK_SAMPLE_COUNT_2_BIT; + case 4: + return VK_SAMPLE_COUNT_4_BIT; + case 8: + return VK_SAMPLE_COUNT_8_BIT; + case 16: + return VK_SAMPLE_COUNT_16_BIT; + default: + UNREACHABLE_MSG("Invalid number of samples={}", num_samples); + return VK_SAMPLE_COUNT_1_BIT; } } -VkImageViewType GetImageViewType(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return VK_IMAGE_VIEW_TYPE_1D; - case SurfaceTarget::Texture2D: - return VK_IMAGE_VIEW_TYPE_2D; - case SurfaceTarget::Texture3D: - return VK_IMAGE_VIEW_TYPE_3D; - case SurfaceTarget::Texture1DArray: - return VK_IMAGE_VIEW_TYPE_1D_ARRAY; - case SurfaceTarget::Texture2DArray: - return VK_IMAGE_VIEW_TYPE_2D_ARRAY; - case SurfaceTarget::TextureCubemap: - return VK_IMAGE_VIEW_TYPE_CUBE; - case SurfaceTarget::TextureCubeArray: - return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; - case SurfaceTarget::TextureBuffer: - break; +[[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info, + PixelFormat format) { + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT; + if (info.attachable) { + switch (VideoCore::Surface::GetFormatType(format)) { + case VideoCore::Surface::SurfaceType::ColorTexture: + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + break; + case VideoCore::Surface::SurfaceType::Depth: + case VideoCore::Surface::SurfaceType::DepthStencil: + usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + break; + default: + UNREACHABLE_MSG("Invalid surface type"); + } } - UNREACHABLE(); - return {}; + if (info.storage) { + usage |= VK_IMAGE_USAGE_STORAGE_BIT; + } + return usage; } -vk::Buffer CreateBuffer(const VKDevice& device, const SurfaceParams& params, - std::size_t host_memory_size) { - // TODO(Rodrigo): Move texture buffer creation to the buffer cache - return device.GetLogical().CreateBuffer({ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, +/// Returns the preferred format for a VkImage +[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) { + switch (format) { + case PixelFormat::A8B8G8R8_SRGB: + return PixelFormat::A8B8G8R8_UNORM; + default: + return format; + } +} + +[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { + const PixelFormat format = StorageFormat(info.format); + const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); + VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + if (info.type == ImageType::e2D && info.resources.layers >= 6 && + info.size.width == info.size.height) { + flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + } + if (info.type == ImageType::e3D) { + flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; + } + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + return VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, - .flags = 0, - .size = static_cast<VkDeviceSize>(host_memory_size), - .usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | - VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .flags = flags, + .imageType = ConvertImageType(info.type), + .format = format_info.format, + .extent{ + .width = info.size.width >> samples_x, + .height = info.size.height >> samples_y, + .depth = info.size.depth, + }, + .mipLevels = static_cast<u32>(info.resources.levels), + .arrayLayers = static_cast<u32>(info.resources.layers), + .samples = ConvertSampleCount(info.num_samples), + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = ImageUsageFlags(format_info, format), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, - }); -} - -VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device, - const SurfaceParams& params, VkBuffer buffer, - std::size_t host_memory_size) { - ASSERT(params.IsBuffer()); - - return { - .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .buffer = buffer, - .format = - MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format, - .offset = 0, - .range = static_cast<VkDeviceSize>(host_memory_size), + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; } -VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceParams& params) { - ASSERT(!params.IsBuffer()); - - const auto [format, attachable, storage] = - MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.pixel_format); +[[nodiscard]] vk::Image MakeImage(const Device& device, const ImageInfo& info) { + if (info.type == ImageType::Buffer) { + return vk::Image{}; + } + return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info)); +} - VkImageCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, +[[nodiscard]] vk::Buffer MakeBuffer(const Device& device, const ImageInfo& info) { + if (info.type != ImageType::Buffer) { + return vk::Buffer{}; + } + const size_t bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format); + return device.GetLogical().CreateBuffer(VkBufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .imageType = SurfaceTargetToImage(params.target), - .format = format, - .extent = {}, - .mipLevels = params.num_levels, - .arrayLayers = static_cast<u32>(params.GetNumLayers()), - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .size = info.size.width * bytes_per_block, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - }; - if (attachable) { - ci.usage |= params.IsPixelFormatZeta() ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT - : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - } - if (storage) { - ci.usage |= VK_IMAGE_USAGE_STORAGE_BIT; - } - - switch (params.target) { - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: - ci.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - [[fallthrough]]; - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture2DArray: - ci.extent = {params.width, params.height, 1}; - break; - case SurfaceTarget::Texture3D: - ci.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - ci.extent = {params.width, params.height, params.depth}; - break; - case SurfaceTarget::TextureBuffer: - UNREACHABLE(); - } - - return ci; + }); } -u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::SwizzleSource y_source, - Tegra::Texture::SwizzleSource z_source, Tegra::Texture::SwizzleSource w_source) { - return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) | - (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source); +[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { + switch (VideoCore::Surface::GetFormatType(format)) { + case VideoCore::Surface::SurfaceType::ColorTexture: + return VK_IMAGE_ASPECT_COLOR_BIT; + case VideoCore::Surface::SurfaceType::Depth: + return VK_IMAGE_ASPECT_DEPTH_BIT; + case VideoCore::Surface::SurfaceType::DepthStencil: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + default: + UNREACHABLE_MSG("Invalid surface type"); + return VkImageAspectFlags{}; + } } -} // Anonymous namespace - -CachedSurface::CachedSurface(Core::System& system, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - VKScheduler& scheduler, VKStagingBufferPool& staging_pool, - GPUVAddr gpu_addr, const SurfaceParams& params) - : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, system{system}, - device{device}, resource_manager{resource_manager}, - memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} { - if (params.IsBuffer()) { - buffer = CreateBuffer(device, params, host_memory_size); - commit = memory_manager.Commit(buffer, false); - - const auto buffer_view_ci = - GenerateBufferViewCreateInfo(device, params, *buffer, host_memory_size); - format = buffer_view_ci.format; - - buffer_view = device.GetLogical().CreateBufferView(buffer_view_ci); - } else { - const auto image_ci = GenerateImageCreateInfo(device, params); - format = image_ci.format; - - image.emplace(device, scheduler, image_ci, PixelFormatToImageAspect(params.pixel_format)); - commit = memory_manager.Commit(image->GetHandle(), false); +[[nodiscard]] VkImageAspectFlags ImageViewAspectMask(const VideoCommon::ImageViewInfo& info) { + if (info.IsRenderTarget()) { + return ImageAspectMask(info.format); } - - // TODO(Rodrigo): Move this to a virtual function. - u32 num_layers = 1; - if (params.is_layered || params.target == SurfaceTarget::Texture3D) { - num_layers = params.depth; + const bool is_first = info.Swizzle()[0] == SwizzleSource::R; + switch (info.format) { + case PixelFormat::D24_UNORM_S8_UINT: + case PixelFormat::D32_FLOAT_S8_UINT: + return is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT; + case PixelFormat::S8_UINT_D24_UNORM: + return is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; + case PixelFormat::D16_UNORM: + case PixelFormat::D32_FLOAT: + return VK_IMAGE_ASPECT_DEPTH_BIT; + default: + return VK_IMAGE_ASPECT_COLOR_BIT; } - main_view = CreateView(ViewParams(params.target, 0, num_layers, 0, params.num_levels)); } -CachedSurface::~CachedSurface() = default; - -void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) { - // To upload data we have to be outside of a renderpass - scheduler.RequestOutsideRenderPassOperationContext(); +[[nodiscard]] VkAttachmentDescription AttachmentDescription(const Device& device, + const ImageView* image_view) { + using MaxwellToVK::SurfaceFormat; + const PixelFormat pixel_format = image_view->format; + return VkAttachmentDescription{ + .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT, + .format = SurfaceFormat(device, FormatType::Optimal, true, pixel_format).format, + .samples = image_view->Samples(), + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, + .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; +} - if (params.IsBuffer()) { - UploadBuffer(staging_buffer); - } else { - UploadImage(staging_buffer); +[[nodiscard]] VkComponentSwizzle ComponentSwizzle(SwizzleSource swizzle) { + switch (swizzle) { + case SwizzleSource::Zero: + return VK_COMPONENT_SWIZZLE_ZERO; + case SwizzleSource::R: + return VK_COMPONENT_SWIZZLE_R; + case SwizzleSource::G: + return VK_COMPONENT_SWIZZLE_G; + case SwizzleSource::B: + return VK_COMPONENT_SWIZZLE_B; + case SwizzleSource::A: + return VK_COMPONENT_SWIZZLE_A; + case SwizzleSource::OneFloat: + case SwizzleSource::OneInt: + return VK_COMPONENT_SWIZZLE_ONE; } + UNREACHABLE_MSG("Invalid swizzle={}", swizzle); + return VK_COMPONENT_SWIZZLE_ZERO; } -void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { - UNIMPLEMENTED_IF(params.IsBuffer()); - - if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) { - LOG_WARNING(Render_Vulkan, "A1B5G5R5 flushing is stubbed"); +[[nodiscard]] VkImageViewType ImageViewType(VideoCommon::ImageViewType type) { + switch (type) { + case VideoCommon::ImageViewType::e1D: + return VK_IMAGE_VIEW_TYPE_1D; + case VideoCommon::ImageViewType::e2D: + return VK_IMAGE_VIEW_TYPE_2D; + case VideoCommon::ImageViewType::Cube: + return VK_IMAGE_VIEW_TYPE_CUBE; + case VideoCommon::ImageViewType::e3D: + return VK_IMAGE_VIEW_TYPE_3D; + case VideoCommon::ImageViewType::e1DArray: + return VK_IMAGE_VIEW_TYPE_1D_ARRAY; + case VideoCommon::ImageViewType::e2DArray: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + case VideoCommon::ImageViewType::CubeArray: + return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + case VideoCommon::ImageViewType::Rect: + LOG_WARNING(Render_Vulkan, "Unnormalized image view type not supported"); + return VK_IMAGE_VIEW_TYPE_2D; + case VideoCommon::ImageViewType::Buffer: + UNREACHABLE_MSG("Texture buffers can't be image views"); + return VK_IMAGE_VIEW_TYPE_1D; } + UNREACHABLE_MSG("Invalid image view type={}", type); + return VK_IMAGE_VIEW_TYPE_2D; +} - // We can't copy images to buffers inside a renderpass - scheduler.RequestOutsideRenderPassOperationContext(); +[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers( + VideoCommon::SubresourceLayers subresource, VkImageAspectFlags aspect_mask) { + return VkImageSubresourceLayers{ + .aspectMask = aspect_mask, + .mipLevel = static_cast<u32>(subresource.base_level), + .baseArrayLayer = static_cast<u32>(subresource.base_layer), + .layerCount = static_cast<u32>(subresource.num_layers), + }; +} - FullTransition(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); +[[nodiscard]] VkOffset3D MakeOffset3D(VideoCommon::Offset3D offset3d) { + return VkOffset3D{ + .x = offset3d.x, + .y = offset3d.y, + .z = offset3d.z, + }; +} - const auto& buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); - // TODO(Rodrigo): Do this in a single copy - for (u32 level = 0; level < params.num_levels; ++level) { - scheduler.Record([image = *image->GetHandle(), buffer = *buffer.handle, - copy = GetBufferImageCopy(level)](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy); - }); - } - scheduler.Finish(); +[[nodiscard]] VkExtent3D MakeExtent3D(VideoCommon::Extent3D extent3d) { + return VkExtent3D{ + .width = static_cast<u32>(extent3d.width), + .height = static_cast<u32>(extent3d.height), + .depth = static_cast<u32>(extent3d.depth), + }; +} - // TODO(Rodrigo): Use an intern buffer for staging buffers and avoid this unnecessary memcpy. - std::memcpy(staging_buffer.data(), buffer.commit->Map(host_memory_size), host_memory_size); +[[nodiscard]] VkImageCopy MakeImageCopy(const VideoCommon::ImageCopy& copy, + VkImageAspectFlags aspect_mask) noexcept { + return VkImageCopy{ + .srcSubresource = MakeImageSubresourceLayers(copy.src_subresource, aspect_mask), + .srcOffset = MakeOffset3D(copy.src_offset), + .dstSubresource = MakeImageSubresourceLayers(copy.dst_subresource, aspect_mask), + .dstOffset = MakeOffset3D(copy.dst_offset), + .extent = MakeExtent3D(copy.extent), + }; } -void CachedSurface::DecorateSurfaceName() { - // TODO(Rodrigo): Add name decorations +[[nodiscard]] std::vector<VkBufferCopy> TransformBufferCopies( + std::span<const VideoCommon::BufferCopy> copies, size_t buffer_offset) { + std::vector<VkBufferCopy> result(copies.size()); + std::ranges::transform( + copies, result.begin(), [buffer_offset](const VideoCommon::BufferCopy& copy) { + return VkBufferCopy{ + .srcOffset = static_cast<VkDeviceSize>(copy.src_offset + buffer_offset), + .dstOffset = static_cast<VkDeviceSize>(copy.dst_offset), + .size = static_cast<VkDeviceSize>(copy.size), + }; + }); + return result; } -View CachedSurface::CreateView(const ViewParams& params) { - // TODO(Rodrigo): Add name decorations - return views[params] = std::make_shared<CachedSurfaceView>(device, *this, params); +[[nodiscard]] std::vector<VkBufferImageCopy> TransformBufferImageCopies( + std::span<const BufferImageCopy> copies, size_t buffer_offset, VkImageAspectFlags aspect_mask) { + struct Maker { + VkBufferImageCopy operator()(const BufferImageCopy& copy) const { + return VkBufferImageCopy{ + .bufferOffset = copy.buffer_offset + buffer_offset, + .bufferRowLength = copy.buffer_row_length, + .bufferImageHeight = copy.buffer_image_height, + .imageSubresource = + { + .aspectMask = aspect_mask, + .mipLevel = static_cast<u32>(copy.image_subresource.base_level), + .baseArrayLayer = static_cast<u32>(copy.image_subresource.base_layer), + .layerCount = static_cast<u32>(copy.image_subresource.num_layers), + }, + .imageOffset = + { + .x = copy.image_offset.x, + .y = copy.image_offset.y, + .z = copy.image_offset.z, + }, + .imageExtent = + { + .width = copy.image_extent.width, + .height = copy.image_extent.height, + .depth = copy.image_extent.depth, + }, + }; + } + size_t buffer_offset; + VkImageAspectFlags aspect_mask; + }; + if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + std::vector<VkBufferImageCopy> result(copies.size() * 2); + std::ranges::transform(copies, result.begin(), + Maker{buffer_offset, VK_IMAGE_ASPECT_DEPTH_BIT}); + std::ranges::transform(copies, result.begin() + copies.size(), + Maker{buffer_offset, VK_IMAGE_ASPECT_STENCIL_BIT}); + return result; + } else { + std::vector<VkBufferImageCopy> result(copies.size()); + std::ranges::transform(copies, result.begin(), Maker{buffer_offset, aspect_mask}); + return result; + } } -void CachedSurface::UploadBuffer(const std::vector<u8>& staging_buffer) { - const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); - std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size); +[[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(VkImageAspectFlags aspect_mask, + const SubresourceRange& range) { + return VkImageSubresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = static_cast<u32>(range.base.level), + .levelCount = static_cast<u32>(range.extent.levels), + .baseArrayLayer = static_cast<u32>(range.base.layer), + .layerCount = static_cast<u32>(range.extent.layers), + }; +} - scheduler.Record([src_buffer = *src_buffer.handle, dst_buffer = *buffer, - size = host_memory_size](vk::CommandBuffer cmdbuf) { - VkBufferCopy copy; - copy.srcOffset = 0; - copy.dstOffset = 0; - copy.size = size; - cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy); +[[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(const ImageView* image_view) { + SubresourceRange range = image_view->range; + if (True(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) { + // Slice image views always affect a single layer, but their subresource range corresponds + // to the slice. Override the value to affect a single layer. + range.base.layer = 0; + range.extent.layers = 1; + } + return MakeSubresourceRange(ImageAspectMask(image_view->format), range); +} - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // They'll be ignored anyway - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = dst_buffer; - barrier.offset = 0; - barrier.size = size; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, - 0, {}, barrier, {}); - }); +[[nodiscard]] VkImageSubresourceLayers MakeSubresourceLayers(const ImageView* image_view) { + return VkImageSubresourceLayers{ + .aspectMask = ImageAspectMask(image_view->format), + .mipLevel = static_cast<u32>(image_view->range.base.level), + .baseArrayLayer = static_cast<u32>(image_view->range.base.layer), + .layerCount = static_cast<u32>(image_view->range.extent.layers), + }; } -void CachedSurface::UploadImage(const std::vector<u8>& staging_buffer) { - const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); - std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size); - - FullTransition(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - for (u32 level = 0; level < params.num_levels; ++level) { - const VkBufferImageCopy copy = GetBufferImageCopy(level); - if (image->GetAspectMask() == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { - scheduler.Record([buffer = *src_buffer.handle, image = *image->GetHandle(), - copy](vk::CommandBuffer cmdbuf) { - std::array<VkBufferImageCopy, 2> copies = {copy, copy}; - copies[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - copies[1].imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; - cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - copies); - }); - } else { - scheduler.Record([buffer = *src_buffer.handle, image = *image->GetHandle(), - copy](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy); - }); - } +[[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) { + switch (value) { + case SwizzleSource::G: + return SwizzleSource::R; + default: + return value; } } -VkBufferImageCopy CachedSurface::GetBufferImageCopy(u32 level) const { - return { - .bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted), - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = +void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image, + VkImageAspectFlags aspect_mask, bool is_initialized, + std::span<const VkBufferImageCopy> copies) { + static constexpr VkAccessFlags WRITE_ACCESS_FLAGS = + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + static constexpr VkAccessFlags READ_ACCESS_FLAGS = VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + const VkImageMemoryBarrier read_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = WRITE_ACCESS_FLAGS, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + const VkImageMemoryBarrier write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = WRITE_ACCESS_FLAGS | READ_ACCESS_FLAGS, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + read_barrier); + cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies); + // TODO: Move this to another API + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, + write_barrier); +} + +[[nodiscard]] VkImageBlit MakeImageBlit(const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + const VkImageSubresourceLayers& dst_layers, + const VkImageSubresourceLayers& src_layers) { + return VkImageBlit{ + .srcSubresource = src_layers, + .srcOffsets = { - .aspectMask = image->GetAspectMask(), - .mipLevel = level, - .baseArrayLayer = 0, - .layerCount = static_cast<u32>(params.GetNumLayers()), + { + .x = src_region[0].x, + .y = src_region[0].y, + .z = 0, + }, + { + .x = src_region[1].x, + .y = src_region[1].y, + .z = 1, + }, }, - .imageOffset = {.x = 0, .y = 0, .z = 0}, - .imageExtent = + .dstSubresource = dst_layers, + .dstOffsets = { - .width = params.GetMipWidth(level), - .height = params.GetMipHeight(level), - .depth = params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1U, + { + .x = dst_region[0].x, + .y = dst_region[0].y, + .z = 0, + }, + { + .x = dst_region[1].x, + .y = dst_region[1].y, + .z = 1, + }, }, }; } -VkImageSubresourceRange CachedSurface::GetImageSubresourceRange() const { - return {image->GetAspectMask(), 0, params.num_levels, 0, - static_cast<u32>(params.GetNumLayers())}; +[[nodiscard]] VkImageResolve MakeImageResolve(const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + const VkImageSubresourceLayers& dst_layers, + const VkImageSubresourceLayers& src_layers) { + return VkImageResolve{ + .srcSubresource = src_layers, + .srcOffset = + { + .x = src_region[0].x, + .y = src_region[0].y, + .z = 0, + }, + .dstSubresource = dst_layers, + .dstOffset = + { + .x = dst_region[0].x, + .y = dst_region[0].y, + .z = 0, + }, + .extent = + { + .width = static_cast<u32>(dst_region[1].x - dst_region[0].x), + .height = static_cast<u32>(dst_region[1].y - dst_region[0].y), + .depth = 1, + }, + }; } -CachedSurfaceView::CachedSurfaceView(const VKDevice& device, CachedSurface& surface, - const ViewParams& params) - : VideoCommon::ViewBase{params}, params{surface.GetSurfaceParams()}, - image{surface.GetImageHandle()}, buffer_view{surface.GetBufferViewHandle()}, - aspect_mask{surface.GetAspectMask()}, device{device}, surface{surface}, - base_level{params.base_level}, num_levels{params.num_levels}, - image_view_type{image ? GetImageViewType(params.target) : VK_IMAGE_VIEW_TYPE_1D} { - if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) { - base_layer = 0; - num_layers = 1; - base_slice = params.base_layer; - num_slices = params.num_layers; - } else { - base_layer = params.base_layer; - num_layers = params.num_layers; +struct RangedBarrierRange { + u32 min_mip = std::numeric_limits<u32>::max(); + u32 max_mip = std::numeric_limits<u32>::min(); + u32 min_layer = std::numeric_limits<u32>::max(); + u32 max_layer = std::numeric_limits<u32>::min(); + + void AddLayers(const VkImageSubresourceLayers& layers) { + min_mip = std::min(min_mip, layers.mipLevel); + max_mip = std::max(max_mip, layers.mipLevel + 1); + min_layer = std::min(min_layer, layers.baseArrayLayer); + max_layer = std::max(max_layer, layers.baseArrayLayer + layers.layerCount); + } + + VkImageSubresourceRange SubresourceRange(VkImageAspectFlags aspect_mask) const noexcept { + return VkImageSubresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = min_mip, + .levelCount = max_mip - min_mip, + .baseArrayLayer = min_layer, + .layerCount = max_layer - min_layer, + }; } +}; + +} // Anonymous namespace + +void TextureCacheRuntime::Finish() { + scheduler.Finish(); } -CachedSurfaceView::~CachedSurfaceView() = default; +StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size) { + return staging_buffer_pool.Request(size, MemoryUsage::Upload); +} -VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSource y_source, - SwizzleSource z_source, SwizzleSource w_source) { - const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source); - if (last_image_view && last_swizzle == new_swizzle) { - return last_image_view; - } - last_swizzle = new_swizzle; +StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size) { + return staging_buffer_pool.Request(size, MemoryUsage::Download); +} - const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle); - auto& image_view = entry->second; - if (!is_cache_miss) { - return last_image_view = *image_view; +void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) { + const VkImageAspectFlags aspect_mask = ImageAspectMask(src.format); + const bool is_dst_msaa = dst.Samples() != VK_SAMPLE_COUNT_1_BIT; + const bool is_src_msaa = src.Samples() != VK_SAMPLE_COUNT_1_BIT; + ASSERT(aspect_mask == ImageAspectMask(dst.format)); + if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT && !is_src_msaa && !is_dst_msaa) { + blit_image_helper.BlitColor(dst_framebuffer, src, dst_region, src_region, filter, + operation); + return; } - - std::array swizzle{MaxwellToVK::SwizzleSource(x_source), MaxwellToVK::SwizzleSource(y_source), - MaxwellToVK::SwizzleSource(z_source), MaxwellToVK::SwizzleSource(w_source)}; - if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) { - // A1B5G5R5 is implemented as A1R5G5B5, we have to change the swizzle here. - std::swap(swizzle[0], swizzle[2]); + if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + if (!device.IsBlitDepthStencilSupported()) { + UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); + blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), + dst_region, src_region, filter, operation); + return; + } } - - // Games can sample depth or stencil values on textures. This is decided by the swizzle value on - // hardware. To emulate this on Vulkan we specify it in the aspect. - VkImageAspectFlags aspect = aspect_mask; - if (aspect == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { - UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G); - const bool is_first = x_source == SwizzleSource::R; - switch (params.pixel_format) { - case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT: - case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT: - aspect = is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT; - break; - case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM: - aspect = is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; - break; - default: - aspect = VK_IMAGE_ASPECT_DEPTH_BIT; - UNIMPLEMENTED(); + ASSERT(src.ImageFormat() == dst.ImageFormat()); + ASSERT(!(is_dst_msaa && !is_src_msaa)); + ASSERT(operation == Fermi2D::Operation::SrcCopy); + + const VkImage dst_image = dst.ImageHandle(); + const VkImage src_image = src.ImageHandle(); + const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst); + const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src); + const bool is_resolve = is_src_msaa && !is_dst_msaa; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers, + aspect_mask, is_resolve](vk::CommandBuffer cmdbuf) { + const std::array read_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + }; + VkImageMemoryBarrier write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, nullptr, nullptr, read_barriers); + if (is_resolve) { + cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + MakeImageResolve(dst_region, src_region, dst_layers, src_layers)); + } else { + const bool is_linear = filter == Fermi2D::Filter::Bilinear; + const VkFilter vk_filter = is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + cmdbuf.BlitImage( + src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter); } + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, write_barrier); + }); +} - // Make sure we sample the first component - std::transform( - swizzle.begin(), swizzle.end(), swizzle.begin(), [](VkComponentSwizzle component) { - return component == VK_COMPONENT_SWIZZLE_G ? VK_COMPONENT_SWIZZLE_R : component; - }); +void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) { + switch (dst_view.format) { + case PixelFormat::R16_UNORM: + if (src_view.format == PixelFormat::D16_UNORM) { + return blit_image_helper.ConvertD16ToR16(dst, src_view); + } + break; + case PixelFormat::R32_FLOAT: + if (src_view.format == PixelFormat::D32_FLOAT) { + return blit_image_helper.ConvertD32ToR32(dst, src_view); + } + break; + case PixelFormat::D16_UNORM: + if (src_view.format == PixelFormat::R16_UNORM) { + return blit_image_helper.ConvertR16ToD16(dst, src_view); + } + break; + case PixelFormat::D32_FLOAT: + if (src_view.format == PixelFormat::R32_FLOAT) { + return blit_image_helper.ConvertR32ToD32(dst, src_view); + } + break; + default: + break; } + UNIMPLEMENTED_MSG("Unimplemented format copy from {} to {}", src_view.format, dst_view.format); +} - if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) { - ASSERT(base_slice == 0); - ASSERT(num_slices == params.depth); - } +void TextureCacheRuntime::CopyImage(Image& dst, Image& src, + std::span<const VideoCommon::ImageCopy> copies) { + std::vector<VkImageCopy> vk_copies(copies.size()); + const VkImageAspectFlags aspect_mask = dst.AspectMask(); + ASSERT(aspect_mask == src.AspectMask()); - image_view = device.GetLogical().CreateImageView({ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = surface.GetImageHandle(), - .viewType = image_view_type, - .format = surface.GetImage().GetFormat(), - .components = - { - .r = swizzle[0], - .g = swizzle[1], - .b = swizzle[2], - .a = swizzle[3], + std::ranges::transform(copies, vk_copies.begin(), [aspect_mask](const auto& copy) { + return MakeImageCopy(copy, aspect_mask); + }); + const VkImage dst_image = dst.Handle(); + const VkImage src_image = src.Handle(); + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([dst_image, src_image, aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) { + RangedBarrierRange dst_range; + RangedBarrierRange src_range; + for (const VkImageCopy& copy : vk_copies) { + dst_range.AddLayers(copy.dstSubresource); + src_range.AddLayers(copy.srcSubresource); + } + const std::array read_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src_image, + .subresourceRange = src_range.SubresourceRange(aspect_mask), }, - .subresourceRange = - { - .aspectMask = aspect, - .baseMipLevel = base_level, - .levelCount = num_levels, - .baseArrayLayer = base_layer, - .layerCount = num_layers, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange = dst_range.SubresourceRange(aspect_mask), }, + }; + const VkImageMemoryBarrier write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange = dst_range.SubresourceRange(aspect_mask), + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, {}, {}, read_barriers); + cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk_copies); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, write_barrier); }); - - return last_image_view = *image_view; } -VkImageView CachedSurfaceView::GetAttachment() { - if (render_target) { - return *render_target; +Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_, + VAddr cpu_addr_) + : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler}, + image(MakeImage(runtime.device, info)), buffer(MakeBuffer(runtime.device, info)), + aspect_mask(ImageAspectMask(info.format)) { + if (image) { + commit = runtime.memory_allocator.Commit(image, MemoryUsage::DeviceLocal); + } else { + commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal); + } + if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) { + flags |= VideoCommon::ImageFlagBits::Converted; + } + if (runtime.device.HasDebuggingToolAttached()) { + if (image) { + image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); + } else { + buffer.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); + } } +} - VkImageViewCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = surface.GetImageHandle(), - .viewType = VK_IMAGE_VIEW_TYPE_1D, - .format = surface.GetImage().GetFormat(), - .components = - { - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, +void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) { + // TODO: Move this to another API + scheduler->RequestOutsideRenderPassOperationContext(); + std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask); + const VkBuffer src_buffer = map.buffer; + const VkImage vk_image = *image; + const VkImageAspectFlags vk_aspect_mask = aspect_mask; + const bool is_initialized = std::exchange(initialized, true); + scheduler->Record([src_buffer, vk_image, vk_aspect_mask, is_initialized, + vk_copies](vk::CommandBuffer cmdbuf) { + CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies); + }); +} + +void Image::UploadMemory(const StagingBufferRef& map, + std::span<const VideoCommon::BufferCopy> copies) { + // TODO: Move this to another API + scheduler->RequestOutsideRenderPassOperationContext(); + std::vector vk_copies = TransformBufferCopies(copies, map.offset); + const VkBuffer src_buffer = map.buffer; + const VkBuffer dst_buffer = *buffer; + scheduler->Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) { + // TODO: Barriers + cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); + }); +} + +void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) { + std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask); + scheduler->Record([buffer = map.buffer, image = *image, aspect_mask = aspect_mask, + vk_copies](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier read_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, }, - .subresourceRange = - { + }; + const VkImageMemoryBarrier image_write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ .aspectMask = aspect_mask, - .baseMipLevel = base_level, - .levelCount = num_levels, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, - .layerCount = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, }, + }; + const VkMemoryBarrier memory_write_barrier{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, read_barrier); + cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, vk_copies); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, memory_write_barrier, nullptr, image_write_barrier); + }); +} + +ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, + ImageId image_id_, Image& image) + : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device}, + image_handle{image.Handle()}, image_format{image.info.format}, samples{ConvertSampleCount( + image.info.num_samples)} { + const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info); + std::array<SwizzleSource, 4> swizzle{ + SwizzleSource::R, + SwizzleSource::G, + SwizzleSource::B, + SwizzleSource::A, }; - if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) { - ci.viewType = num_slices > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; - ci.subresourceRange.baseArrayLayer = base_slice; - ci.subresourceRange.layerCount = num_slices; - } else { - ci.viewType = image_view_type; - ci.subresourceRange.baseArrayLayer = base_layer; - ci.subresourceRange.layerCount = num_layers; + if (!info.IsRenderTarget()) { + swizzle = info.Swizzle(); + if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); + } + } + const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + const VkFormat vk_format = format_info.format; + const VkImageViewUsageCreateInfo image_view_usage{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .pNext = nullptr, + .usage = ImageUsageFlags(format_info, format), + }; + const VkImageViewCreateInfo create_info{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = &image_view_usage, + .flags = 0, + .image = image.Handle(), + .viewType = VkImageViewType{}, + .format = vk_format, + .components{ + .r = ComponentSwizzle(swizzle[0]), + .g = ComponentSwizzle(swizzle[1]), + .b = ComponentSwizzle(swizzle[2]), + .a = ComponentSwizzle(swizzle[3]), + }, + .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), + }; + const auto create = [&](VideoCommon::ImageViewType view_type, std::optional<u32> num_layers) { + VkImageViewCreateInfo ci{create_info}; + ci.viewType = ImageViewType(view_type); + if (num_layers) { + ci.subresourceRange.layerCount = *num_layers; + } + vk::ImageView handle = device->GetLogical().CreateImageView(ci); + if (device->HasDebuggingToolAttached()) { + handle.SetObjectNameEXT(VideoCommon::Name(*this, view_type).c_str()); + } + image_views[static_cast<size_t>(view_type)] = std::move(handle); + }; + switch (info.type) { + case VideoCommon::ImageViewType::e1D: + case VideoCommon::ImageViewType::e1DArray: + create(VideoCommon::ImageViewType::e1D, 1); + create(VideoCommon::ImageViewType::e1DArray, std::nullopt); + render_target = Handle(VideoCommon::ImageViewType::e1DArray); + break; + case VideoCommon::ImageViewType::e2D: + case VideoCommon::ImageViewType::e2DArray: + create(VideoCommon::ImageViewType::e2D, 1); + create(VideoCommon::ImageViewType::e2DArray, std::nullopt); + render_target = Handle(VideoCommon::ImageViewType::e2DArray); + break; + case VideoCommon::ImageViewType::e3D: + create(VideoCommon::ImageViewType::e3D, std::nullopt); + render_target = Handle(VideoCommon::ImageViewType::e3D); + break; + case VideoCommon::ImageViewType::Cube: + case VideoCommon::ImageViewType::CubeArray: + create(VideoCommon::ImageViewType::Cube, 6); + create(VideoCommon::ImageViewType::CubeArray, std::nullopt); + break; + case VideoCommon::ImageViewType::Rect: + UNIMPLEMENTED(); + break; + case VideoCommon::ImageViewType::Buffer: + buffer_view = device->GetLogical().CreateBufferView(VkBufferViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .buffer = image.Buffer(), + .format = vk_format, + .offset = 0, // TODO: Redesign buffer cache to support this + .range = image.guest_size_bytes, + }); + break; } - render_target = device.GetLogical().CreateImageView(ci); - return *render_target; } -VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKResourceManager& resource_manager, - VKMemoryManager& memory_manager, VKScheduler& scheduler, - VKStagingBufferPool& staging_pool) - : TextureCache(system, rasterizer, device.IsOptimalAstcSupported()), device{device}, - resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler}, - staging_pool{staging_pool} {} - -VKTextureCache::~VKTextureCache() = default; +ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams& params) + : VideoCommon::ImageViewBase{params} {} -Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) { - return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager, - scheduler, staging_pool, gpu_addr, params); +VkImageView ImageView::DepthView() { + if (depth_view) { + return *depth_view; + } + depth_view = MakeDepthStencilView(VK_IMAGE_ASPECT_DEPTH_BIT); + return *depth_view; } -void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface, - const VideoCommon::CopyParams& copy_params) { - const bool src_3d = src_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D; - const bool dst_3d = dst_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D; - UNIMPLEMENTED_IF(src_3d); - - // The texture cache handles depth in OpenGL terms, we have to handle it as subresource and - // dimension respectively. - const u32 dst_base_layer = dst_3d ? 0 : copy_params.dest_z; - const u32 dst_offset_z = dst_3d ? copy_params.dest_z : 0; - - const u32 extent_z = dst_3d ? copy_params.depth : 1; - const u32 num_layers = dst_3d ? 1 : copy_params.depth; - - // We can't copy inside a renderpass - scheduler.RequestOutsideRenderPassOperationContext(); - - src_surface->Transition(copy_params.source_z, copy_params.depth, copy_params.source_level, 1, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - dst_surface->Transition(dst_base_layer, num_layers, copy_params.dest_level, 1, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - const VkImageCopy copy{ - .srcSubresource = - { - .aspectMask = src_surface->GetAspectMask(), - .mipLevel = copy_params.source_level, - .baseArrayLayer = copy_params.source_z, - .layerCount = num_layers, - }, - .srcOffset = - { - .x = static_cast<s32>(copy_params.source_x), - .y = static_cast<s32>(copy_params.source_y), - .z = 0, - }, - .dstSubresource = - { - .aspectMask = dst_surface->GetAspectMask(), - .mipLevel = copy_params.dest_level, - .baseArrayLayer = dst_base_layer, - .layerCount = num_layers, - }, - .dstOffset = - { - .x = static_cast<s32>(copy_params.dest_x), - .y = static_cast<s32>(copy_params.dest_y), - .z = static_cast<s32>(dst_offset_z), - }, - .extent = - { - .width = copy_params.width, - .height = copy_params.height, - .depth = extent_z, - }, - }; +VkImageView ImageView::StencilView() { + if (stencil_view) { + return *stencil_view; + } + stencil_view = MakeDepthStencilView(VK_IMAGE_ASPECT_STENCIL_BIT); + return *stencil_view; +} - const VkImage src_image = src_surface->GetImageHandle(); - const VkImage dst_image = dst_surface->GetImageHandle(); - scheduler.Record([src_image, dst_image, copy](vk::CommandBuffer cmdbuf) { - cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy); +vk::ImageView ImageView::MakeDepthStencilView(VkImageAspectFlags aspect_mask) { + return device->GetLogical().CreateImageView({ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = image_handle, + .viewType = ImageViewType(type), + .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format).format, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = MakeSubresourceRange(aspect_mask, range), }); } -void VKTextureCache::ImageBlit(View& src_view, View& dst_view, - const Tegra::Engines::Fermi2D::Config& copy_config) { - // We can't blit inside a renderpass - scheduler.RequestOutsideRenderPassOperationContext(); - - src_view->Transition(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_READ_BIT); - dst_view->Transition(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT); - - VkImageBlit blit; - blit.srcSubresource = src_view->GetImageSubresourceLayers(); - blit.srcOffsets[0].x = copy_config.src_rect.left; - blit.srcOffsets[0].y = copy_config.src_rect.top; - blit.srcOffsets[0].z = 0; - blit.srcOffsets[1].x = copy_config.src_rect.right; - blit.srcOffsets[1].y = copy_config.src_rect.bottom; - blit.srcOffsets[1].z = 1; - blit.dstSubresource = dst_view->GetImageSubresourceLayers(); - blit.dstOffsets[0].x = copy_config.dst_rect.left; - blit.dstOffsets[0].y = copy_config.dst_rect.top; - blit.dstOffsets[0].z = 0; - blit.dstOffsets[1].x = copy_config.dst_rect.right; - blit.dstOffsets[1].y = copy_config.dst_rect.bottom; - blit.dstOffsets[1].z = 1; - - const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; - - scheduler.Record([src_image = src_view->GetImage(), dst_image = dst_view->GetImage(), blit, - is_linear](vk::CommandBuffer cmdbuf) { - cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, blit, - is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); +Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) { + const auto& device = runtime.device; + const bool arbitrary_borders = runtime.device.IsExtCustomBorderColorSupported(); + const std::array<float, 4> color = tsc.BorderColor(); + // C++20 bit_cast + VkClearColorValue border_color; + std::memcpy(&border_color, &color, sizeof(color)); + const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, + .pNext = nullptr, + .customBorderColor = border_color, + .format = VK_FORMAT_UNDEFINED, + }; + const void* pnext = nullptr; + if (arbitrary_borders) { + pnext = &border_ci; + } + const VkSamplerReductionModeCreateInfoEXT reduction_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, + .pNext = pnext, + .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + }; + if (runtime.device.IsExtSamplerFilterMinmaxSupported()) { + pnext = &reduction_ci; + } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { + LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); + } + // Some games have samplers with garbage. Sanitize them here. + const float max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); + sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = pnext, + .flags = 0, + .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), + .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), + .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), + .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), + .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), + .mipLodBias = tsc.LodBias(), + .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), + .maxAnisotropy = max_anisotropy, + .compareEnable = tsc.depth_compare_enabled, + .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), + .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), + .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), + .borderColor = + arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color), + .unnormalizedCoordinates = VK_FALSE, }); } -void VKTextureCache::BufferCopy(Surface& src_surface, Surface& dst_surface) { - // Currently unimplemented. PBO copies should be dropped and we should use a render pass to - // convert from color to depth and viceversa. - LOG_WARNING(Render_Vulkan, "Unimplemented"); +Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, + ImageView* depth_buffer, const VideoCommon::RenderTargets& key) { + std::vector<VkAttachmentDescription> descriptions; + std::vector<VkImageView> attachments; + RenderPassKey renderpass_key{}; + s32 num_layers = 1; + + for (size_t index = 0; index < NUM_RT; ++index) { + const ImageView* const color_buffer = color_buffers[index]; + if (!color_buffer) { + renderpass_key.color_formats[index] = PixelFormat::Invalid; + continue; + } + descriptions.push_back(AttachmentDescription(runtime.device, color_buffer)); + attachments.push_back(color_buffer->RenderTarget()); + renderpass_key.color_formats[index] = color_buffer->format; + num_layers = std::max(num_layers, color_buffer->range.extent.layers); + images[num_images] = color_buffer->ImageHandle(); + image_ranges[num_images] = MakeSubresourceRange(color_buffer); + samples = color_buffer->Samples(); + ++num_images; + } + const size_t num_colors = attachments.size(); + const VkAttachmentReference* depth_attachment = + depth_buffer ? &ATTACHMENT_REFERENCES[num_colors] : nullptr; + if (depth_buffer) { + descriptions.push_back(AttachmentDescription(runtime.device, depth_buffer)); + attachments.push_back(depth_buffer->RenderTarget()); + renderpass_key.depth_format = depth_buffer->format; + num_layers = std::max(num_layers, depth_buffer->range.extent.layers); + images[num_images] = depth_buffer->ImageHandle(); + image_ranges[num_images] = MakeSubresourceRange(depth_buffer); + samples = depth_buffer->Samples(); + ++num_images; + } else { + renderpass_key.depth_format = PixelFormat::Invalid; + } + renderpass_key.samples = samples; + + const auto& device = runtime.device.GetLogical(); + const auto [cache_pair, is_new] = runtime.renderpass_cache.try_emplace(renderpass_key); + if (is_new) { + const VkSubpassDescription subpass{ + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = static_cast<u32>(num_colors), + .pColorAttachments = num_colors != 0 ? ATTACHMENT_REFERENCES.data() : nullptr, + .pResolveAttachments = nullptr, + .pDepthStencilAttachment = depth_attachment, + .preserveAttachmentCount = 0, + .pPreserveAttachments = nullptr, + }; + cache_pair->second = device.CreateRenderPass(VkRenderPassCreateInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .attachmentCount = static_cast<u32>(descriptions.size()), + .pAttachments = descriptions.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 0, + .pDependencies = nullptr, + }); + } + renderpass = *cache_pair->second; + render_area = VkExtent2D{ + .width = key.size.width, + .height = key.size.height, + }; + num_color_buffers = static_cast<u32>(num_colors); + framebuffer = device.CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = renderpass, + .attachmentCount = static_cast<u32>(attachments.size()), + .pAttachments = attachments.data(), + .width = key.size.width, + .height = key.size.height, + .layers = static_cast<u32>(std::max(num_layers, 1)), + }); + if (runtime.device.HasDebuggingToolAttached()) { + framebuffer.SetObjectNameEXT(VideoCommon::Name(key).c_str()); + } } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 807e26c8a..b08c23459 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -4,225 +4,253 @@ #pragma once -#include <memory> -#include <unordered_map> - -#include "common/common_types.h" -#include "video_core/renderer_vulkan/vk_image.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/wrapper.h" -#include "video_core/texture_cache/surface_base.h" -#include "video_core/texture_cache/texture_cache.h" - -namespace Core { -class System; -} +#include <compare> +#include <span> -namespace VideoCore { -class RasterizerInterface; -} +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class RasterizerVulkan; -class VKDevice; -class VKResourceManager; +using VideoCommon::ImageId; +using VideoCommon::NUM_RT; +using VideoCommon::Offset2D; +using VideoCommon::RenderTargets; +using VideoCore::Surface::PixelFormat; + +class BlitImageHelper; +class Device; +class Image; +class ImageView; +class Framebuffer; +class StagingBufferPool; class VKScheduler; -class VKStagingBufferPool; -class CachedSurfaceView; -class CachedSurface; +struct RenderPassKey { + constexpr auto operator<=>(const RenderPassKey&) const noexcept = default; -using Surface = std::shared_ptr<CachedSurface>; -using View = std::shared_ptr<CachedSurfaceView>; -using TextureCacheBase = VideoCommon::TextureCache<Surface, View>; + std::array<PixelFormat, NUM_RT> color_formats; + PixelFormat depth_format; + VkSampleCountFlagBits samples; +}; -using VideoCommon::SurfaceParams; -using VideoCommon::ViewParams; +} // namespace Vulkan -class CachedSurface final : public VideoCommon::SurfaceBase<View> { - friend CachedSurfaceView; +namespace std { +template <> +struct hash<Vulkan::RenderPassKey> { + [[nodiscard]] constexpr size_t operator()(const Vulkan::RenderPassKey& key) const noexcept { + size_t value = static_cast<size_t>(key.depth_format) << 48; + value ^= static_cast<size_t>(key.samples) << 52; + for (size_t i = 0; i < key.color_formats.size(); ++i) { + value ^= static_cast<size_t>(key.color_formats[i]) << (i * 6); + } + return value; + } +}; +} // namespace std -public: - explicit CachedSurface(Core::System& system, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - VKScheduler& scheduler, VKStagingBufferPool& staging_pool, - GPUVAddr gpu_addr, const SurfaceParams& params); - ~CachedSurface(); +namespace Vulkan { - void UploadTexture(const std::vector<u8>& staging_buffer) override; - void DownloadTexture(std::vector<u8>& staging_buffer) override; +struct TextureCacheRuntime { + const Device& device; + VKScheduler& scheduler; + MemoryAllocator& memory_allocator; + StagingBufferPool& staging_buffer_pool; + BlitImageHelper& blit_image_helper; + std::unordered_map<RenderPassKey, vk::RenderPass> renderpass_cache{}; - void FullTransition(VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access, - VkImageLayout new_layout) { - image->Transition(0, static_cast<u32>(params.GetNumLayers()), 0, params.num_levels, - new_stage_mask, new_access, new_layout); - } + void Finish(); - void Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, - VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access, - VkImageLayout new_layout) { - image->Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask, - new_access, new_layout); - } + [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size); - VKImage& GetImage() { - return *image; - } + [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size); - const VKImage& GetImage() const { - return *image; - } + void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, + const std::array<Offset2D, 2>& dst_region, + const std::array<Offset2D, 2>& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation); + + void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies); - VkImage GetImageHandle() const { - return *image->GetHandle(); + void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view); + + [[nodiscard]] bool CanAccelerateImageUpload(Image&) const noexcept { + return false; } - VkImageAspectFlags GetAspectMask() const { - return image->GetAspectMask(); + void AccelerateImageUpload(Image&, const StagingBufferRef&, + std::span<const VideoCommon::SwizzleParameters>) { + UNREACHABLE(); } - VkBufferView GetBufferViewHandle() const { - return *buffer_view; + void InsertUploadMemoryBarrier() {} + + bool HasBrokenTextureViewFormats() const noexcept { + // No known Vulkan driver has broken image views + return false; } +}; -protected: - void DecorateSurfaceName(); +class Image : public VideoCommon::ImageBase { +public: + explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, + VAddr cpu_addr); - View CreateView(const ViewParams& params) override; + void UploadMemory(const StagingBufferRef& map, + std::span<const VideoCommon::BufferImageCopy> copies); -private: - void UploadBuffer(const std::vector<u8>& staging_buffer); + void UploadMemory(const StagingBufferRef& map, std::span<const VideoCommon::BufferCopy> copies); - void UploadImage(const std::vector<u8>& staging_buffer); + void DownloadMemory(const StagingBufferRef& map, + std::span<const VideoCommon::BufferImageCopy> copies); - VkBufferImageCopy GetBufferImageCopy(u32 level) const; + [[nodiscard]] VkImage Handle() const noexcept { + return *image; + } - VkImageSubresourceRange GetImageSubresourceRange() const; + [[nodiscard]] VkBuffer Buffer() const noexcept { + return *buffer; + } - Core::System& system; - const VKDevice& device; - VKResourceManager& resource_manager; - VKMemoryManager& memory_manager; - VKScheduler& scheduler; - VKStagingBufferPool& staging_pool; + [[nodiscard]] VkImageCreateFlags AspectMask() const noexcept { + return aspect_mask; + } - std::optional<VKImage> image; +private: + VKScheduler* scheduler; + vk::Image image; vk::Buffer buffer; - vk::BufferView buffer_view; - VKMemoryCommit commit; - - VkFormat format = VK_FORMAT_UNDEFINED; + MemoryCommit commit; + VkImageAspectFlags aspect_mask = 0; + bool initialized = false; }; -class CachedSurfaceView final : public VideoCommon::ViewBase { +class ImageView : public VideoCommon::ImageViewBase { public: - explicit CachedSurfaceView(const VKDevice& device, CachedSurface& surface, - const ViewParams& params); - ~CachedSurfaceView(); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&); - VkImageView GetImageView(Tegra::Texture::SwizzleSource x_source, - Tegra::Texture::SwizzleSource y_source, - Tegra::Texture::SwizzleSource z_source, - Tegra::Texture::SwizzleSource w_source); + [[nodiscard]] VkImageView DepthView(); - VkImageView GetAttachment(); + [[nodiscard]] VkImageView StencilView(); - bool IsSameSurface(const CachedSurfaceView& rhs) const { - return &surface == &rhs.surface; + [[nodiscard]] VkImageView Handle(VideoCommon::ImageViewType query_type) const noexcept { + return *image_views[static_cast<size_t>(query_type)]; } - u32 GetWidth() const { - return params.GetMipWidth(base_level); + [[nodiscard]] VkBufferView BufferView() const noexcept { + return *buffer_view; } - u32 GetHeight() const { - return params.GetMipHeight(base_level); + [[nodiscard]] VkImage ImageHandle() const noexcept { + return image_handle; } - u32 GetNumLayers() const { - return num_layers; + [[nodiscard]] VkImageView RenderTarget() const noexcept { + return render_target; } - bool IsBufferView() const { - return buffer_view; + [[nodiscard]] PixelFormat ImageFormat() const noexcept { + return image_format; } - VkImage GetImage() const { - return image; + [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept { + return samples; } - VkBufferView GetBufferView() const { - return buffer_view; - } +private: + [[nodiscard]] vk::ImageView MakeDepthStencilView(VkImageAspectFlags aspect_mask); - VkImageSubresourceRange GetImageSubresourceRange() const { - return {aspect_mask, base_level, num_levels, base_layer, num_layers}; - } + const Device* device = nullptr; + std::array<vk::ImageView, VideoCommon::NUM_IMAGE_VIEW_TYPES> image_views; + vk::ImageView depth_view; + vk::ImageView stencil_view; + vk::BufferView buffer_view; + VkImage image_handle = VK_NULL_HANDLE; + VkImageView render_target = VK_NULL_HANDLE; + PixelFormat image_format = PixelFormat::Invalid; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; +}; - VkImageSubresourceLayers GetImageSubresourceLayers() const { - return {surface.GetAspectMask(), base_level, base_layer, num_layers}; - } +class ImageAlloc : public VideoCommon::ImageAllocBase {}; - void Transition(VkImageLayout new_layout, VkPipelineStageFlags new_stage_mask, - VkAccessFlags new_access) const { - surface.Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask, - new_access, new_layout); - } +class Sampler { +public: + explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); - void MarkAsModified(u64 tick) { - surface.MarkAsModified(true, tick); + [[nodiscard]] VkSampler Handle() const noexcept { + return *sampler; } private: - // Store a copy of these values to avoid double dereference when reading them - const SurfaceParams params; - const VkImage image; - const VkBufferView buffer_view; - const VkImageAspectFlags aspect_mask; - - const VKDevice& device; - CachedSurface& surface; - const u32 base_level; - const u32 num_levels; - const VkImageViewType image_view_type; - u32 base_layer = 0; - u32 num_layers = 0; - u32 base_slice = 0; - u32 num_slices = 0; - - VkImageView last_image_view = nullptr; - u32 last_swizzle = 0; - - vk::ImageView render_target; - std::unordered_map<u32, vk::ImageView> view_cache; + vk::Sampler sampler; }; -class VKTextureCache final : public TextureCacheBase { +class Framebuffer { public: - explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKResourceManager& resource_manager, - VKMemoryManager& memory_manager, VKScheduler& scheduler, - VKStagingBufferPool& staging_pool); - ~VKTextureCache(); + explicit Framebuffer(TextureCacheRuntime&, std::span<ImageView*, NUM_RT> color_buffers, + ImageView* depth_buffer, const VideoCommon::RenderTargets& key); -private: - Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override; + [[nodiscard]] VkFramebuffer Handle() const noexcept { + return *framebuffer; + } - void ImageCopy(Surface& src_surface, Surface& dst_surface, - const VideoCommon::CopyParams& copy_params) override; + [[nodiscard]] VkRenderPass RenderPass() const noexcept { + return renderpass; + } - void ImageBlit(View& src_view, View& dst_view, - const Tegra::Engines::Fermi2D::Config& copy_config) override; + [[nodiscard]] VkExtent2D RenderArea() const noexcept { + return render_area; + } - void BufferCopy(Surface& src_surface, Surface& dst_surface) override; + [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept { + return samples; + } - const VKDevice& device; - VKResourceManager& resource_manager; - VKMemoryManager& memory_manager; - VKScheduler& scheduler; - VKStagingBufferPool& staging_pool; + [[nodiscard]] u32 NumColorBuffers() const noexcept { + return num_color_buffers; + } + + [[nodiscard]] u32 NumImages() const noexcept { + return num_images; + } + + [[nodiscard]] const std::array<VkImage, 9>& Images() const noexcept { + return images; + } + + [[nodiscard]] const std::array<VkImageSubresourceRange, 9>& ImageRanges() const noexcept { + return image_ranges; + } + +private: + vk::Framebuffer framebuffer; + VkRenderPass renderpass{}; + VkExtent2D render_area{}; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; + u32 num_color_buffers = 0; + u32 num_images = 0; + std::array<VkImage, 9> images{}; + std::array<VkImageSubresourceRange, 9> image_ranges{}; }; +struct TextureCacheParams { + static constexpr bool ENABLE_VALIDATION = true; + static constexpr bool FRAMEBUFFER_BLITS = false; + static constexpr bool HAS_EMULATED_COPIES = false; + + using Runtime = Vulkan::TextureCacheRuntime; + using Image = Vulkan::Image; + using ImageAlloc = Vulkan::ImageAlloc; + using ImageView = Vulkan::ImageView; + using Sampler = Vulkan::Sampler; + using Framebuffer = Vulkan::Framebuffer; +}; + +using TextureCache = VideoCommon::TextureCache<TextureCacheParams>; + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp index 351c048d2..f99273c6a 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -7,15 +7,15 @@ #include "common/assert.h" #include "common/logging/log.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler) - : device{device}, scheduler{scheduler} {} +VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_) + : device{device_}, scheduler{scheduler_} {} VKUpdateDescriptorQueue::~VKUpdateDescriptorQueue() = default; diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 945320c72..e214f7195 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -8,11 +8,11 @@ #include <boost/container/static_vector.hpp> #include "common/common_types.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { -class VKDevice; +class Device; class VKScheduler; struct DescriptorUpdateEntry { @@ -31,7 +31,7 @@ struct DescriptorUpdateEntry { class VKUpdateDescriptorQueue final { public: - explicit VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler); + explicit VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_); ~VKUpdateDescriptorQueue(); void TickFrame(); @@ -40,32 +40,36 @@ public: void Send(VkDescriptorUpdateTemplateKHR update_template, VkDescriptorSet set); - void AddSampledImage(VkSampler sampler, VkImageView image_view) { - payload.emplace_back(VkDescriptorImageInfo{sampler, image_view, {}}); + void AddSampledImage(VkImageView image_view, VkSampler sampler) { + payload.emplace_back(VkDescriptorImageInfo{ + .sampler = sampler, + .imageView = image_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }); } void AddImage(VkImageView image_view) { - payload.emplace_back(VkDescriptorImageInfo{{}, image_view, {}}); + payload.emplace_back(VkDescriptorImageInfo{ + .sampler = VK_NULL_HANDLE, + .imageView = image_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }); } - void AddBuffer(VkBuffer buffer, u64 offset, std::size_t size) { - payload.emplace_back(VkDescriptorBufferInfo{buffer, offset, size}); + void AddBuffer(VkBuffer buffer, u64 offset, size_t size) { + payload.emplace_back(VkDescriptorBufferInfo{ + .buffer = buffer, + .offset = offset, + .range = size, + }); } void AddTexelBuffer(VkBufferView texel_buffer) { payload.emplace_back(texel_buffer); } - VkImageLayout* LastImageLayout() { - return &payload.back().image.imageLayout; - } - - const VkImageLayout* LastImageLayout() const { - return &payload.back().image.imageLayout; - } - private: - const VKDevice& device; + const Device& device; VKScheduler& scheduler; const DescriptorUpdateEntry* upload_start = nullptr; diff --git a/src/video_core/sampler_cache.cpp b/src/video_core/sampler_cache.cpp deleted file mode 100644 index 53c7ef12d..000000000 --- a/src/video_core/sampler_cache.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/cityhash.h" -#include "common/common_types.h" -#include "video_core/sampler_cache.h" - -namespace VideoCommon { - -std::size_t SamplerCacheKey::Hash() const { - static_assert(sizeof(raw) % sizeof(u64) == 0); - return static_cast<std::size_t>( - Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64))); -} - -bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const { - return raw == rhs.raw; -} - -} // namespace VideoCommon diff --git a/src/video_core/sampler_cache.h b/src/video_core/sampler_cache.h deleted file mode 100644 index cbe3ad071..000000000 --- a/src/video_core/sampler_cache.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <unordered_map> - -#include "video_core/textures/texture.h" - -namespace VideoCommon { - -struct SamplerCacheKey final : public Tegra::Texture::TSCEntry { - std::size_t Hash() const; - - bool operator==(const SamplerCacheKey& rhs) const; - - bool operator!=(const SamplerCacheKey& rhs) const { - return !operator==(rhs); - } -}; - -} // namespace VideoCommon - -namespace std { - -template <> -struct hash<VideoCommon::SamplerCacheKey> { - std::size_t operator()(const VideoCommon::SamplerCacheKey& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace VideoCommon { - -template <typename SamplerType, typename SamplerStorageType> -class SamplerCache { -public: - SamplerType GetSampler(const Tegra::Texture::TSCEntry& tsc) { - const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc}); - auto& sampler = entry->second; - if (is_cache_miss) { - sampler = CreateSampler(tsc); - } - return ToSamplerType(sampler); - } - -protected: - virtual SamplerStorageType CreateSampler(const Tegra::Texture::TSCEntry& tsc) const = 0; - - virtual SamplerType ToSamplerType(const SamplerStorageType& sampler) const = 0; - -private: - std::unordered_map<SamplerCacheKey, SamplerStorageType> cache; -}; - -} // namespace VideoCommon
\ No newline at end of file diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp index 3f96d9076..db11144c7 100644 --- a/src/video_core/shader/ast.cpp +++ b/src/video_core/shader/ast.cpp @@ -212,16 +212,15 @@ public: } void operator()(const ExprPredicate& expr) { - inner += "P" + std::to_string(expr.predicate); + inner += fmt::format("P{}", expr.predicate); } void operator()(const ExprCondCode& expr) { - u32 cc = static_cast<u32>(expr.cc); - inner += "CC" + std::to_string(cc); + inner += fmt::format("CC{}", expr.cc); } void operator()(const ExprVar& expr) { - inner += "V" + std::to_string(expr.var_index); + inner += fmt::format("V{}", expr.var_index); } void operator()(const ExprBoolean& expr) { @@ -229,7 +228,7 @@ public: } void operator()(const ExprGprEqual& expr) { - inner += "( gpr_" + std::to_string(expr.gpr) + " == " + std::to_string(expr.value) + ')'; + inner += fmt::format("(gpr_{} == {})", expr.gpr, expr.value); } const std::string& GetResult() const { @@ -374,8 +373,8 @@ std::string ASTManager::Print() const { return printer.GetResult(); } -ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation) - : full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {}; +ASTManager::ASTManager(bool do_full_decompile, bool disable_else_derivation_) + : full_decompile{do_full_decompile}, disable_else_derivation{disable_else_derivation_} {} ASTManager::~ASTManager() { Clear(); diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h index cca13bcde..dc49b369e 100644 --- a/src/video_core/shader/ast.h +++ b/src/video_core/shader/ast.h @@ -76,7 +76,7 @@ public: class ASTIfThen { public: - explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {} + explicit ASTIfThen(Expr condition_) : condition{std::move(condition_)} {} Expr condition; ASTZipper nodes{}; }; @@ -88,63 +88,68 @@ public: class ASTBlockEncoded { public: - explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {} + explicit ASTBlockEncoded(u32 start_, u32 _) : start{start_}, end{_} {} u32 start; u32 end; }; class ASTBlockDecoded { public: - explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {} + explicit ASTBlockDecoded(NodeBlock&& new_nodes_) : nodes(std::move(new_nodes_)) {} NodeBlock nodes; }; class ASTVarSet { public: - explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {} + explicit ASTVarSet(u32 index_, Expr condition_) + : index{index_}, condition{std::move(condition_)} {} + u32 index; Expr condition; }; class ASTLabel { public: - explicit ASTLabel(u32 index) : index{index} {} + explicit ASTLabel(u32 index_) : index{index_} {} u32 index; bool unused{}; }; class ASTGoto { public: - explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {} + explicit ASTGoto(Expr condition_, u32 label_) + : condition{std::move(condition_)}, label{label_} {} + Expr condition; u32 label; }; class ASTDoWhile { public: - explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {} + explicit ASTDoWhile(Expr condition_) : condition{std::move(condition_)} {} Expr condition; ASTZipper nodes{}; }; class ASTReturn { public: - explicit ASTReturn(Expr condition, bool kills) - : condition{std::move(condition)}, kills{kills} {} + explicit ASTReturn(Expr condition_, bool kills_) + : condition{std::move(condition_)}, kills{kills_} {} + Expr condition; bool kills; }; class ASTBreak { public: - explicit ASTBreak(Expr condition) : condition{std::move(condition)} {} + explicit ASTBreak(Expr condition_) : condition{std::move(condition_)} {} Expr condition; }; class ASTBase { public: - explicit ASTBase(ASTNode parent, ASTData data) - : data{std::move(data)}, parent{std::move(parent)} {} + explicit ASTBase(ASTNode parent_, ASTData data_) + : data{std::move(data_)}, parent{std::move(parent_)} {} template <class U, class... Args> static ASTNode Make(ASTNode parent, Args&&... args) { @@ -199,55 +204,48 @@ public: } std::optional<u32> GetGotoLabel() const { - auto inner = std::get_if<ASTGoto>(&data); - if (inner) { + if (const auto* inner = std::get_if<ASTGoto>(&data)) { return {inner->label}; } - return {}; + return std::nullopt; } Expr GetGotoCondition() const { - auto inner = std::get_if<ASTGoto>(&data); - if (inner) { + if (const auto* inner = std::get_if<ASTGoto>(&data)) { return inner->condition; } return nullptr; } void MarkLabelUnused() { - auto inner = std::get_if<ASTLabel>(&data); - if (inner) { + if (auto* inner = std::get_if<ASTLabel>(&data)) { inner->unused = true; } } bool IsLabelUnused() const { - auto inner = std::get_if<ASTLabel>(&data); - if (inner) { + if (const auto* inner = std::get_if<ASTLabel>(&data)) { return inner->unused; } return true; } std::optional<u32> GetLabelIndex() const { - auto inner = std::get_if<ASTLabel>(&data); - if (inner) { + if (const auto* inner = std::get_if<ASTLabel>(&data)) { return {inner->index}; } - return {}; + return std::nullopt; } Expr GetIfCondition() const { - auto inner = std::get_if<ASTIfThen>(&data); - if (inner) { + if (const auto* inner = std::get_if<ASTIfThen>(&data)) { return inner->condition; } return nullptr; } void SetGotoCondition(Expr new_condition) { - auto inner = std::get_if<ASTGoto>(&data); - if (inner) { + if (auto* inner = std::get_if<ASTGoto>(&data)) { inner->condition = std::move(new_condition); } } @@ -307,7 +305,7 @@ private: class ASTManager final { public: - ASTManager(bool full_decompile, bool disable_else_derivation); + explicit ASTManager(bool do_full_decompile, bool disable_else_derivation_); ~ASTManager(); ASTManager(const ASTManager& o) = delete; diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp index f815584f7..02adcf9c7 100644 --- a/src/video_core/shader/async_shaders.cpp +++ b/src/video_core/shader/async_shaders.cpp @@ -13,21 +13,22 @@ namespace VideoCommon::Shader { -AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window) : emu_window(emu_window) {} +AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window_) : emu_window(emu_window_) {} AsyncShaders::~AsyncShaders() { KillWorkers(); } void AsyncShaders::AllocateWorkers() { - // Max worker threads we should allow - constexpr u32 MAX_THREADS = 4; - // Deduce how many threads we can use - const u32 threads_used = std::thread::hardware_concurrency() / 4; - // Always allow at least 1 thread regardless of our settings - const auto max_worker_count = std::max(1U, threads_used); - // Don't use more than MAX_THREADS - const auto num_workers = std::min(max_worker_count, MAX_THREADS); + // Use at least one thread + u32 num_workers = 1; + + // Deduce how many more threads we can use + const u32 thread_count = std::thread::hardware_concurrency(); + if (thread_count >= 8) { + // Increase async workers by 1 for every 2 threads >= 8 + num_workers += 1 + (thread_count - 8) / 2; + } // If we already have workers queued, ignore if (num_workers == worker_threads.size()) { @@ -42,8 +43,8 @@ void AsyncShaders::AllocateWorkers() { // Create workers for (std::size_t i = 0; i < num_workers; i++) { context_list.push_back(emu_window.CreateSharedContext()); - worker_threads.push_back( - std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get())); + worker_threads.emplace_back(&AsyncShaders::ShaderCompilerThread, this, + context_list[i].get()); } } @@ -63,6 +64,7 @@ void AsyncShaders::FreeWorkers() { void AsyncShaders::KillWorkers() { is_thread_exiting.store(true); + cv.notify_all(); for (auto& thread : worker_threads) { thread.detach(); } @@ -73,11 +75,11 @@ void AsyncShaders::KillWorkers() { worker_threads.clear(); } -bool AsyncShaders::HasWorkQueued() { +bool AsyncShaders::HasWorkQueued() const { return !pending_queue.empty(); } -bool AsyncShaders::HasCompletedWork() { +bool AsyncShaders::HasCompletedWork() const { std::shared_lock lock{completed_mutex}; return !finished_work.empty(); } @@ -102,11 +104,10 @@ bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const { } std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() { - std::vector<AsyncShaders::Result> results; + std::vector<Result> results; { std::unique_lock lock{completed_mutex}; - results.assign(std::make_move_iterator(finished_work.begin()), - std::make_move_iterator(finished_work.end())); + results = std::move(finished_work); finished_work.clear(); } return results; @@ -115,11 +116,10 @@ std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() { void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type, u64 uid, std::vector<u64> code, std::vector<u64> code_b, - u32 main_offset, - VideoCommon::Shader::CompilerSettings compiler_settings, - const VideoCommon::Shader::Registry& registry, - VAddr cpu_addr) { - WorkerParams params{ + u32 main_offset, CompilerSettings compiler_settings, + const Registry& registry, VAddr cpu_addr) { + std::unique_lock lock(queue_mutex); + pending_queue.push({ .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL, .device = &device, .shader_type = shader_type, @@ -130,35 +130,48 @@ void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device, .compiler_settings = compiler_settings, .registry = registry, .cpu_address = cpu_addr, - }; - std::unique_lock lock(queue_mutex); - pending_queue.push(std::move(params)); + .pp_cache = nullptr, + .vk_device = nullptr, + .scheduler = nullptr, + .descriptor_pool = nullptr, + .update_descriptor_queue = nullptr, + .bindings{}, + .program{}, + .key{}, + .num_color_buffers = 0, + }); cv.notify_one(); } void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, - const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler, + const Vulkan::Device& device, Vulkan::VKScheduler& scheduler, Vulkan::VKDescriptorPool& descriptor_pool, Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue, - Vulkan::VKRenderPassCache& renderpass_cache, std::vector<VkDescriptorSetLayoutBinding> bindings, Vulkan::SPIRVProgram program, - Vulkan::GraphicsPipelineCacheKey key) { - WorkerParams params{ + Vulkan::GraphicsPipelineCacheKey key, u32 num_color_buffers) { + std::unique_lock lock(queue_mutex); + pending_queue.push({ .backend = Backend::Vulkan, + .device = nullptr, + .shader_type{}, + .uid = 0, + .code{}, + .code_b{}, + .main_offset = 0, + .compiler_settings{}, + .registry{}, + .cpu_address = 0, .pp_cache = pp_cache, .vk_device = &device, .scheduler = &scheduler, .descriptor_pool = &descriptor_pool, .update_descriptor_queue = &update_descriptor_queue, - .renderpass_cache = &renderpass_cache, - .bindings = bindings, - .program = program, + .bindings = std::move(bindings), + .program = std::move(program), .key = key, - }; - - std::unique_lock lock(queue_mutex); - pending_queue.push(std::move(params)); + .num_color_buffers = num_color_buffers, + }); cv.notify_one(); } @@ -210,8 +223,8 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context } else if (work.backend == Backend::Vulkan) { auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>( *work.vk_device, *work.scheduler, *work.descriptor_pool, - *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings, - work.program); + *work.update_descriptor_queue, work.key, work.bindings, work.program, + work.num_color_buffers); work.pp_cache->EmplacePipeline(std::move(pipeline)); } diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h index d5ae814d5..7fdff6e56 100644 --- a/src/video_core/shader/async_shaders.h +++ b/src/video_core/shader/async_shaders.h @@ -5,19 +5,19 @@ #pragma once #include <condition_variable> -#include <deque> #include <memory> #include <shared_mutex> #include <thread> -#include "common/bit_field.h" + +#include <glad/glad.h> + #include "common/common_types.h" #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" -#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/vulkan_common/vulkan_device.h" namespace Core::Frontend { class EmuWindow; @@ -57,7 +57,7 @@ public: Tegra::Engines::ShaderType shader_type; }; - explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window); + explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window_); ~AsyncShaders(); /// Start up shader worker threads @@ -70,34 +70,34 @@ public: void KillWorkers(); /// Check to see if any shaders have actually been compiled - bool HasCompletedWork(); + [[nodiscard]] bool HasCompletedWork() const; /// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build /// every shader async as some shaders are only built and executed once. We try to "guess" which /// shader would be used only once - bool IsShaderAsync(const Tegra::GPU& gpu) const; + [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const; /// Pulls completed compiled shaders - std::vector<Result> GetCompletedWork(); + [[nodiscard]] std::vector<Result> GetCompletedWork(); void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type, u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset, - VideoCommon::Shader::CompilerSettings compiler_settings, - const VideoCommon::Shader::Registry& registry, VAddr cpu_addr); + CompilerSettings compiler_settings, const Registry& registry, + VAddr cpu_addr); - void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device, + void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::Device& device, Vulkan::VKScheduler& scheduler, Vulkan::VKDescriptorPool& descriptor_pool, Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue, - Vulkan::VKRenderPassCache& renderpass_cache, std::vector<VkDescriptorSetLayoutBinding> bindings, - Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key); + Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key, + u32 num_color_buffers); private: void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); /// Check our worker queue to see if we have any work queued already - bool HasWorkQueued(); + [[nodiscard]] bool HasWorkQueued() const; struct WorkerParams { Backend backend; @@ -108,30 +108,30 @@ private: std::vector<u64> code; std::vector<u64> code_b; u32 main_offset; - VideoCommon::Shader::CompilerSettings compiler_settings; - std::optional<VideoCommon::Shader::Registry> registry; + CompilerSettings compiler_settings; + std::optional<Registry> registry; VAddr cpu_address; // For Vulkan Vulkan::VKPipelineCache* pp_cache; - const Vulkan::VKDevice* vk_device; + const Vulkan::Device* vk_device; Vulkan::VKScheduler* scheduler; Vulkan::VKDescriptorPool* descriptor_pool; Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue; - Vulkan::VKRenderPassCache* renderpass_cache; std::vector<VkDescriptorSetLayoutBinding> bindings; Vulkan::SPIRVProgram program; Vulkan::GraphicsPipelineCacheKey key; + u32 num_color_buffers; }; std::condition_variable cv; - std::mutex queue_mutex; - std::shared_mutex completed_mutex; + mutable std::mutex queue_mutex; + mutable std::shared_mutex completed_mutex; std::atomic<bool> is_thread_exiting{}; std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list; std::vector<std::thread> worker_threads; std::queue<WorkerParams> pending_queue; - std::vector<AsyncShaders::Result> finished_work; + std::vector<Result> finished_work; Core::Frontend::EmuWindow& emu_window; }; diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp index 336397cdb..43d965f2f 100644 --- a/src/video_core/shader/control_flow.cpp +++ b/src/video_core/shader/control_flow.cpp @@ -66,8 +66,8 @@ struct BlockInfo { }; struct CFGRebuildState { - explicit CFGRebuildState(const ProgramCode& program_code, u32 start, Registry& registry) - : program_code{program_code}, registry{registry}, start{start} {} + explicit CFGRebuildState(const ProgramCode& program_code_, u32 start_, Registry& registry_) + : program_code{program_code_}, registry{registry_}, start{start_} {} const ProgramCode& program_code; Registry& registry; @@ -241,10 +241,10 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address) ParseInfo parse_info{}; SingleBranch single_branch{}; - const auto insert_label = [](CFGRebuildState& state, u32 address) { - const auto pair = state.labels.emplace(address); + const auto insert_label = [](CFGRebuildState& rebuild_state, u32 label_address) { + const auto pair = rebuild_state.labels.emplace(label_address); if (pair.second) { - state.inspect_queries.push_back(address); + rebuild_state.inspect_queries.push_back(label_address); } }; @@ -257,7 +257,7 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address) single_branch.ignore = false; break; } - if (state.registered.count(offset) != 0) { + if (state.registered.contains(offset)) { single_branch.address = offset; single_branch.ignore = true; break; @@ -547,13 +547,13 @@ bool TryQuery(CFGRebuildState& state) { gather_labels(q2.ssy_stack, state.ssy_labels, block); gather_labels(q2.pbk_stack, state.pbk_labels, block); if (std::holds_alternative<SingleBranch>(*block.branch)) { - const auto branch = std::get_if<SingleBranch>(block.branch.get()); + auto* branch = std::get_if<SingleBranch>(block.branch.get()); if (!branch->condition.IsUnconditional()) { q2.address = block.end + 1; state.queries.push_back(q2); } - Query conditional_query{q2}; + auto& conditional_query = state.queries.emplace_back(q2); if (branch->is_sync) { if (branch->address == unassigned_branch) { branch->address = conditional_query.ssy_stack.top(); @@ -567,21 +567,21 @@ bool TryQuery(CFGRebuildState& state) { conditional_query.pbk_stack.pop(); } conditional_query.address = branch->address; - state.queries.push_back(std::move(conditional_query)); return true; } - const auto multi_branch = std::get_if<MultiBranch>(block.branch.get()); + + const auto* multi_branch = std::get_if<MultiBranch>(block.branch.get()); for (const auto& branch_case : multi_branch->branches) { - Query conditional_query{q2}; + auto& conditional_query = state.queries.emplace_back(q2); conditional_query.address = branch_case.address; - state.queries.push_back(std::move(conditional_query)); } + return true; } void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) { - const auto get_expr = ([&](const Condition& cond) -> Expr { - Expr result{}; + const auto get_expr = [](const Condition& cond) -> Expr { + Expr result; if (cond.cc != ConditionCode::T) { result = MakeExpr<ExprCondCode>(cond.cc); } @@ -594,10 +594,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) { } Expr extra = MakeExpr<ExprPredicate>(pred); if (negate) { - extra = MakeExpr<ExprNot>(extra); + extra = MakeExpr<ExprNot>(std::move(extra)); } if (result) { - return MakeExpr<ExprAnd>(extra, result); + return MakeExpr<ExprAnd>(std::move(extra), std::move(result)); } return extra; } @@ -605,9 +605,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) { return result; } return MakeExpr<ExprBoolean>(true); - }); + }; + if (std::holds_alternative<SingleBranch>(*branch_info)) { - const auto branch = std::get_if<SingleBranch>(branch_info.get()); + const auto* branch = std::get_if<SingleBranch>(branch_info.get()); if (branch->address < 0) { if (branch->kill) { mm.InsertReturn(get_expr(branch->condition), true); @@ -619,7 +620,7 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) { mm.InsertGoto(get_expr(branch->condition), branch->address); return; } - const auto multi_branch = std::get_if<MultiBranch>(branch_info.get()); + const auto* multi_branch = std::get_if<MultiBranch>(branch_info.get()); for (const auto& branch_case : multi_branch->branches) { mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value), branch_case.address); @@ -631,12 +632,12 @@ void DecompileShader(CFGRebuildState& state) { for (auto label : state.labels) { state.manager->DeclareLabel(label); } - for (auto& block : state.block_info) { - if (state.labels.count(block.start) != 0) { + for (const auto& block : state.block_info) { + if (state.labels.contains(block.start)) { state.manager->InsertLabel(block.start); } const bool ignore = BlockBranchIsIgnored(block.branch); - u32 end = ignore ? block.end + 1 : block.end; + const u32 end = ignore ? block.end + 1 : block.end; state.manager->InsertBlock(block.start, end); if (!ignore) { InsertBranch(*state.manager, block.branch); @@ -736,7 +737,7 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, auto back = result_out->blocks.begin(); auto next = std::next(back); while (next != result_out->blocks.end()) { - if (state.labels.count(next->start) == 0 && next->start == back->end + 1) { + if (!state.labels.contains(next->start) && next->start == back->end + 1) { back->end = next->end; next = result_out->blocks.erase(next); continue; diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h index 62a3510d8..37bf96492 100644 --- a/src/video_core/shader/control_flow.h +++ b/src/video_core/shader/control_flow.h @@ -42,10 +42,10 @@ struct Condition { class SingleBranch { public: SingleBranch() = default; - SingleBranch(Condition condition, s32 address, bool kill, bool is_sync, bool is_brk, - bool ignore) - : condition{condition}, address{address}, kill{kill}, is_sync{is_sync}, is_brk{is_brk}, - ignore{ignore} {} + explicit SingleBranch(Condition condition_, s32 address_, bool kill_, bool is_sync_, + bool is_brk_, bool ignore_) + : condition{condition_}, address{address_}, kill{kill_}, is_sync{is_sync_}, is_brk{is_brk_}, + ignore{ignore_} {} bool operator==(const SingleBranch& b) const { return std::tie(condition, address, kill, is_sync, is_brk, ignore) == @@ -65,15 +65,15 @@ public: }; struct CaseBranch { - CaseBranch(u32 cmp_value, u32 address) : cmp_value{cmp_value}, address{address} {} + explicit CaseBranch(u32 cmp_value_, u32 address_) : cmp_value{cmp_value_}, address{address_} {} u32 cmp_value; u32 address; }; class MultiBranch { public: - MultiBranch(u32 gpr, std::vector<CaseBranch>&& branches) - : gpr{gpr}, branches{std::move(branches)} {} + explicit MultiBranch(u32 gpr_, std::vector<CaseBranch>&& branches_) + : gpr{gpr_}, branches{std::move(branches_)} {} u32 gpr{}; std::vector<CaseBranch> branches{}; diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index eeac328a6..6576d1208 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -25,7 +25,7 @@ using Tegra::Shader::OpCode; namespace { void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver, - const std::list<Sampler>& used_samplers) { + const std::list<SamplerEntry>& used_samplers) { if (gpu_driver.IsTextureHandlerSizeKnown() || used_samplers.size() <= 1) { return; } @@ -43,9 +43,9 @@ void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver, } } -std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce, +std::optional<u32> TryDeduceSamplerSize(const SamplerEntry& sampler_to_deduce, VideoCore::GuestDriverProfile& gpu_driver, - const std::list<Sampler>& used_samplers) { + const std::list<SamplerEntry>& used_samplers) { const u32 base_offset = sampler_to_deduce.offset; u32 max_offset{std::numeric_limits<u32>::max()}; for (const auto& sampler : used_samplers) { @@ -66,7 +66,7 @@ std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce, class ASTDecoder { public: - ASTDecoder(ShaderIR& ir) : ir(ir) {} + explicit ASTDecoder(ShaderIR& ir_) : ir(ir_) {} void operator()(ASTProgram& ast) { ASTNode current = ast.nodes.GetFirst(); @@ -153,8 +153,8 @@ void ShaderIR::Decode() { const auto& blocks = shader_info.blocks; NodeBlock current_block; u32 current_label = static_cast<u32>(exit_branch); - for (auto& block : blocks) { - if (shader_info.labels.count(block.start) != 0) { + for (const auto& block : blocks) { + if (shader_info.labels.contains(block.start)) { insert_block(current_block, current_label); current_block.clear(); current_label = block.start; diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp index 4db329fa5..15eb700e7 100644 --- a/src/video_core/shader/decode/arithmetic.cpp +++ b/src/video_core/shader/decode/arithmetic.cpp @@ -110,8 +110,7 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) { case SubOp::Sqrt: return Operation(OperationCode::FSqrt, PRECISE, op_a); default: - UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}", - static_cast<unsigned>(instr.sub_op.Value())); + UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}", instr.sub_op.Value()); return Immediate(0); } }(); @@ -137,7 +136,8 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::FCMP_RR: - case OpCode::Id::FCMP_RC: { + case OpCode::Id::FCMP_RC: + case OpCode::Id::FCMP_IMMR: { UNIMPLEMENTED_IF(instr.fcmp.ftz == 0); Node op_c = GetRegister(instr.gpr39); Node comp = GetPredicateComparisonFloat(instr.fcmp.cond, std::move(op_c), Immediate(0.0f)); diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp index a276aee44..88103fede 100644 --- a/src/video_core/shader/decode/arithmetic_half.cpp +++ b/src/video_core/shader/decode/arithmetic_half.cpp @@ -53,6 +53,9 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) { absolute_a = ((instr.value >> 44) & 1) != 0; absolute_b = ((instr.value >> 54) & 1) != 0; break; + default: + UNREACHABLE(); + break; } Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a); diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp index 73155966f..7b5bb7003 100644 --- a/src/video_core/shader/decode/arithmetic_integer.cpp +++ b/src/video_core/shader/decode/arithmetic_integer.cpp @@ -83,7 +83,7 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { case IAdd3Height::UpperHalfWord: return BitfieldExtract(value, 16, 16); default: - UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", static_cast<u32>(height)); + UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", height); return Immediate(0); } }; @@ -258,7 +258,7 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { case OpCode::Id::LEA_IMM: case OpCode::Id::LEA_RZ: case OpCode::Id::LEA_HI: { - auto [op_a, op_b, op_c] = [&]() -> std::tuple<Node, Node, Node> { + auto [op_a_, op_b_, op_c_] = [&]() -> std::tuple<Node, Node, Node> { switch (opcode->get().GetId()) { case OpCode::Id::LEA_R2: { return {GetRegister(instr.gpr20), GetRegister(instr.gpr39), @@ -294,8 +294,9 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex), "Unhandled LEA Predicate"); - Node value = Operation(OperationCode::ILogicalShiftLeft, std::move(op_a), std::move(op_c)); - value = Operation(OperationCode::IAdd, std::move(op_b), std::move(value)); + Node value = + Operation(OperationCode::ILogicalShiftLeft, std::move(op_a_), std::move(op_c_)); + value = Operation(OperationCode::IAdd, std::move(op_b_), std::move(value)); SetRegister(bb, instr.gpr0, std::move(value)); break; diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp index 73880db0e..73580277a 100644 --- a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp +++ b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp @@ -28,23 +28,26 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) { case OpCode::Id::IADD32I: { UNIMPLEMENTED_IF_MSG(instr.iadd32i.saturate, "IADD32I saturation is not implemented"); - op_a = GetOperandAbsNegInteger(op_a, false, instr.iadd32i.negate_a, true); + op_a = GetOperandAbsNegInteger(std::move(op_a), false, instr.iadd32i.negate_a != 0, true); - const Node value = Operation(OperationCode::IAdd, PRECISE, op_a, op_b); + Node value = Operation(OperationCode::IAdd, PRECISE, std::move(op_a), std::move(op_b)); - SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc); - SetRegister(bb, instr.gpr0, value); + SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc != 0); + SetRegister(bb, instr.gpr0, std::move(value)); break; } case OpCode::Id::LOP32I: { - if (instr.alu.lop32i.invert_a) - op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_a); + if (instr.alu.lop32i.invert_a) { + op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_a)); + } - if (instr.alu.lop32i.invert_b) - op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_b); + if (instr.alu.lop32i.invert_b) { + op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_b)); + } - WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, op_a, op_b, - PredicateResultMode::None, Pred::UnusedIndex, instr.op_32.generates_cc); + WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, std::move(op_a), + std::move(op_b), PredicateResultMode::None, Pred::UnusedIndex, + instr.op_32.generates_cc != 0); break; } default: @@ -58,18 +61,18 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) { void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation logic_op, Node op_a, Node op_b, PredicateResultMode predicate_mode, Pred predicate, bool sets_cc) { - const Node result = [&]() { + Node result = [&] { switch (logic_op) { case LogicOperation::And: - return Operation(OperationCode::IBitwiseAnd, PRECISE, op_a, op_b); + return Operation(OperationCode::IBitwiseAnd, PRECISE, std::move(op_a), std::move(op_b)); case LogicOperation::Or: - return Operation(OperationCode::IBitwiseOr, PRECISE, op_a, op_b); + return Operation(OperationCode::IBitwiseOr, PRECISE, std::move(op_a), std::move(op_b)); case LogicOperation::Xor: - return Operation(OperationCode::IBitwiseXor, PRECISE, op_a, op_b); + return Operation(OperationCode::IBitwiseXor, PRECISE, std::move(op_a), std::move(op_b)); case LogicOperation::PassB: return op_b; default: - UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(logic_op)); + UNIMPLEMENTED_MSG("Unimplemented logic operation={}", logic_op); return Immediate(0); } }(); @@ -84,13 +87,12 @@ void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation return; case PredicateResultMode::NotZero: { // Set the predicate to true if the result is not zero. - const Node compare = Operation(OperationCode::LogicalINotEqual, result, Immediate(0)); - SetPredicate(bb, static_cast<u64>(predicate), compare); + Node compare = Operation(OperationCode::LogicalINotEqual, std::move(result), Immediate(0)); + SetPredicate(bb, static_cast<u64>(predicate), std::move(compare)); break; } default: - UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}", - static_cast<u32>(predicate_mode)); + UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}", predicate_mode); } } diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp index b9989c88c..fea7a54df 100644 --- a/src/video_core/shader/decode/conversion.cpp +++ b/src/video_core/shader/decode/conversion.cpp @@ -244,7 +244,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { return Operation(OperationCode::FTrunc, value); default: UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}", - static_cast<u32>(instr.conversion.f2f.rounding.Value())); + instr.conversion.f2f.rounding.Value()); return value; } }(); @@ -300,7 +300,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { return Operation(OperationCode::FTrunc, PRECISE, value); default: UNIMPLEMENTED_MSG("Unimplemented F2I rounding mode {}", - static_cast<u32>(instr.conversion.f2i.rounding.Value())); + instr.conversion.f2i.rounding.Value()); return Immediate(0); } }(); diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp index b2e88fa20..fa83108cd 100644 --- a/src/video_core/shader/decode/half_set.cpp +++ b/src/video_core/shader/decode/half_set.cpp @@ -22,13 +22,13 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); - PredCondition cond; - bool bf; - bool ftz; - bool neg_a; - bool abs_a; - bool neg_b; - bool abs_b; + PredCondition cond{}; + bool bf = false; + bool ftz = false; + bool neg_a = false; + bool abs_a = false; + bool neg_b = false; + bool abs_b = false; switch (opcode->get().GetId()) { case OpCode::Id::HSET2_C: case OpCode::Id::HSET2_IMM: diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index e75ca4fdb..5470e8cf4 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -119,6 +119,8 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor, return descriptor.r_type; } break; + default: + break; } UNIMPLEMENTED_MSG("Texture format not implemented={}", format); return ComponentType::FLOAT; @@ -210,19 +212,20 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) { return 0; case TextureFormat::R8G24: if (component == 0) { - return 8; + return 24; } if (component == 1) { - return 24; + return 8; } return 0; case TextureFormat::R8G8: return (component == 0 || component == 1) ? 8 : 0; case TextureFormat::G4R4: return (component == 0 || component == 1) ? 4 : 0; + default: + UNIMPLEMENTED_MSG("Texture format not implemented={}", format); + return 0; } - UNIMPLEMENTED_MSG("Texture format not implemented={}", format); - return 0; } std::size_t GetImageComponentMask(TextureFormat format) { @@ -257,9 +260,10 @@ std::size_t GetImageComponentMask(TextureFormat format) { case TextureFormat::R8: case TextureFormat::R1: return std::size_t{R}; + default: + UNIMPLEMENTED_MSG("Texture format not implemented={}", format); + return std::size_t{R | G | B | A}; } - UNIMPLEMENTED_MSG("Texture format not implemented={}", format); - return std::size_t{R | G | B | A}; } std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { @@ -354,9 +358,9 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { instr.suldst.GetStoreDataLayout() != StoreType::Bits64); auto descriptor = [this, instr] { - std::optional<Tegra::Engines::SamplerDescriptor> descriptor; + std::optional<Tegra::Engines::SamplerDescriptor> sampler_descriptor; if (instr.suldst.is_immediate) { - descriptor = + sampler_descriptor = registry.ObtainBoundSampler(static_cast<u32>(instr.image.index.Value())); } else { const Node image_register = GetRegister(instr.gpr39); @@ -364,12 +368,12 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { static_cast<s64>(global_code.size())); const auto buffer = std::get<1>(result); const auto offset = std::get<2>(result); - descriptor = registry.ObtainBindlessSampler(buffer, offset); + sampler_descriptor = registry.ObtainBindlessSampler(buffer, offset); } - if (!descriptor) { + if (!sampler_descriptor) { UNREACHABLE_MSG("Failed to obtain image descriptor"); } - return *descriptor; + return *sampler_descriptor; }(); const auto comp_mask = GetImageComponentMask(descriptor.format); @@ -463,7 +467,10 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { return OperationCode::AtomicImageXor; case Tegra::Shader::ImageAtomicOperation::Exch: return OperationCode::AtomicImageExchange; + default: + break; } + break; default: break; } @@ -490,11 +497,12 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { return pc; } -Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { +ImageEntry& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { const auto offset = static_cast<u32>(image.index.Value()); - const auto it = std::find_if(std::begin(used_images), std::end(used_images), - [offset](const Image& entry) { return entry.offset == offset; }); + const auto it = + std::find_if(std::begin(used_images), std::end(used_images), + [offset](const ImageEntry& entry) { return entry.offset == offset; }); if (it != std::end(used_images)) { ASSERT(!it->is_bindless && it->type == type); return *it; @@ -504,7 +512,7 @@ Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType t return used_images.emplace_back(next_index, offset, type); } -Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { +ImageEntry& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { const Node image_register = GetRegister(reg); const auto result = TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size())); @@ -513,7 +521,7 @@ Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::Im const auto offset = std::get<2>(result); const auto it = std::find_if(std::begin(used_images), std::end(used_images), - [buffer, offset](const Image& entry) { + [buffer, offset](const ImageEntry& entry) { return entry.buffer == buffer && entry.offset == offset; }); if (it != std::end(used_images)) { diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp index e2bba88dd..50f4e7d35 100644 --- a/src/video_core/shader/decode/memory.cpp +++ b/src/video_core/shader/decode/memory.cpp @@ -47,7 +47,7 @@ OperationCode GetAtomOperation(AtomicOp op) { case AtomicOp::Exch: return OperationCode::AtomicIExchange; default: - UNIMPLEMENTED_MSG("op={}", static_cast<int>(op)); + UNIMPLEMENTED_MSG("op={}", op); return OperationCode::AtomicIAdd; } } @@ -83,7 +83,7 @@ u32 GetMemorySize(Tegra::Shader::UniformType uniform_type) { case Tegra::Shader::UniformType::UnsignedQuad: return 128; default: - UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type)); + UNIMPLEMENTED_MSG("Unimplemented size={}!", uniform_type); return 32; } } @@ -175,12 +175,12 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { break; } default: - UNIMPLEMENTED_MSG("Unhandled type: {}", static_cast<unsigned>(instr.ld_c.type.Value())); + UNIMPLEMENTED_MSG("Unhandled type: {}", instr.ld_c.type.Value()); } break; } case OpCode::Id::LD_L: - LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown)); + LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", instr.ld_l.unknown); [[fallthrough]]; case OpCode::Id::LD_S: { const auto GetAddress = [&](s32 offset) { @@ -224,7 +224,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } default: UNIMPLEMENTED_MSG("{} Unhandled type: {}", opcode->get().GetName(), - static_cast<u32>(instr.ldst_sl.type.Value())); + instr.ldst_sl.type.Value()); } break; } @@ -306,8 +306,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::ST_L: - LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}", - static_cast<u64>(instr.st_l.cache_management.Value())); + LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}", instr.st_l.cache_management.Value()); [[fallthrough]]; case OpCode::Id::ST_S: { const auto GetAddress = [&](s32 offset) { @@ -340,7 +339,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } default: UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(), - static_cast<u32>(instr.ldst_sl.type.Value())); + instr.ldst_sl.type.Value()); } break; } @@ -387,7 +386,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } case OpCode::Id::RED: { UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32, "type={}", - static_cast<int>(instr.red.type.Value())); + instr.red.type.Value()); const auto [real_address, base_address, descriptor] = TrackGlobalMemory(bb, instr, true, true); if (!real_address || !base_address) { @@ -403,12 +402,12 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.atom.operation == AtomicOp::Inc || instr.atom.operation == AtomicOp::Dec || instr.atom.operation == AtomicOp::SafeAdd, - "operation={}", static_cast<int>(instr.atom.operation.Value())); + "operation={}", instr.atom.operation.Value()); UNIMPLEMENTED_IF_MSG(instr.atom.type == GlobalAtomicType::S64 || instr.atom.type == GlobalAtomicType::U64 || instr.atom.type == GlobalAtomicType::F16x2_FTZ_RN || instr.atom.type == GlobalAtomicType::F32_FTZ_RN, - "type={}", static_cast<int>(instr.atom.type.Value())); + "type={}", instr.atom.type.Value()); const auto [real_address, base_address, descriptor] = TrackGlobalMemory(bb, instr, true, true); @@ -428,10 +427,10 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { case OpCode::Id::ATOMS: { UNIMPLEMENTED_IF_MSG(instr.atoms.operation == AtomicOp::Inc || instr.atoms.operation == AtomicOp::Dec, - "operation={}", static_cast<int>(instr.atoms.operation.Value())); + "operation={}", instr.atoms.operation.Value()); UNIMPLEMENTED_IF_MSG(instr.atoms.type == AtomicType::S64 || instr.atoms.type == AtomicType::U64, - "type={}", static_cast<int>(instr.atoms.type.Value())); + "type={}", instr.atoms.type.Value()); const bool is_signed = instr.atoms.type == AtomicType::S32 || instr.atoms.type == AtomicType::S64; const s32 offset = instr.atoms.GetImmediateOffset(); diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index 29a7cfbfe..5f88537bc 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -34,14 +34,13 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::EXIT: { - const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; - UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "EXIT condition code used: {}", - static_cast<u32>(cc)); + const ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "EXIT condition code used: {}", cc); switch (instr.flow.cond) { case Tegra::Shader::FlowCondition::Always: bb.push_back(Operation(OperationCode::Exit)); - if (instr.pred.pred_index == static_cast<u64>(Tegra::Shader::Pred::UnusedIndex)) { + if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { // If this is an unconditional exit then just end processing here, // otherwise we have to account for the possibility of the condition // not being met, so continue processing the next instruction. @@ -56,17 +55,15 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { break; default: - UNIMPLEMENTED_MSG("Unhandled flow condition: {}", - static_cast<u32>(instr.flow.cond.Value())); + UNIMPLEMENTED_MSG("Unhandled flow condition: {}", instr.flow.cond.Value()); } break; } case OpCode::Id::KIL: { UNIMPLEMENTED_IF(instr.flow.cond != Tegra::Shader::FlowCondition::Always); - const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; - UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "KIL condition code used: {}", - static_cast<u32>(cc)); + const ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "KIL condition code used: {}", cc); bb.push_back(Operation(OperationCode::Discard)); break; @@ -79,6 +76,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { case SystemVariable::InvocationId: return Operation(OperationCode::InvocationId); case SystemVariable::Ydirection: + uses_y_negate = true; return Operation(OperationCode::YNegate); case SystemVariable::InvocationInfo: LOG_WARNING(HW_GPU, "S2R instruction with InvocationInfo is incomplete"); @@ -90,11 +88,11 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_MSG("S2R WscaleFactorZ is not implemented"); return Immediate(0U); case SystemVariable::Tid: { - Node value = Immediate(0); - value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9); - value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdY), 16, 9); - value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdZ), 26, 5); - return value; + Node val = Immediate(0); + val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdX), 0, 9); + val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdY), 16, 9); + val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdZ), 26, 5); + return val; } case SystemVariable::TidX: return Operation(OperationCode::LocalInvocationIdX); @@ -130,8 +128,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { return Immediate(0u); } default: - UNIMPLEMENTED_MSG("Unhandled system move: {}", - static_cast<u32>(instr.sys20.Value())); + UNIMPLEMENTED_MSG("Unhandled system move: {}", instr.sys20.Value()); return Immediate(0u); } }(); @@ -181,8 +178,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { } const Node branch = Operation(OperationCode::BranchIndirect, operand); - const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; - if (cc != Tegra::Shader::ConditionCode::T) { + const ConditionCode cc = instr.flow_condition_code; + if (cc != ConditionCode::T) { bb.push_back(Conditional(GetConditionCode(cc), {branch})); } else { bb.push_back(branch); @@ -218,9 +215,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::SYNC: { - const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; - UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}", - static_cast<u32>(cc)); + const ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "SYNC condition code used: {}", cc); if (decompiled) { break; @@ -231,9 +227,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::BRK: { - const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; - UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}", - static_cast<u32>(cc)); + const ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "BRK condition code used: {}", cc); if (decompiled) { break; } @@ -306,7 +301,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { case Tegra::Shader::MembarType::GL: return OperationCode::MemoryBarrierGlobal; default: - UNIMPLEMENTED_MSG("MEMBAR type={}", static_cast<int>(instr.membar.type.Value())); + UNIMPLEMENTED_MSG("MEMBAR type={}", instr.membar.type.Value()); return OperationCode::MemoryBarrierGlobal; } }(); diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp index d4ffa8014..a53819c15 100644 --- a/src/video_core/shader/decode/shift.cpp +++ b/src/video_core/shader/decode/shift.cpp @@ -125,7 +125,7 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { case OpCode::Id::SHF_LEFT_IMM: { UNIMPLEMENTED_IF(instr.generates_cc); UNIMPLEMENTED_IF_MSG(instr.shf.xmode != ShfXmode::None, "xmode={}", - static_cast<int>(instr.shf.xmode.Value())); + instr.shf.xmode.Value()); if (instr.is_b_imm) { op_b = Immediate(static_cast<u32>(instr.shf.immediate)); diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index 29ebf65ba..833fa2a39 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp @@ -34,7 +34,7 @@ static std::size_t GetCoordCount(TextureType texture_type) { case TextureType::TextureCube: return 3; default: - UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type)); + UNIMPLEMENTED_MSG("Unhandled texture type: {}", texture_type); return 0; } } @@ -141,7 +141,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { SamplerInfo info; info.is_shadow = is_depth_compare; - const std::optional<Sampler> sampler = GetSampler(instr.sampler, info); + const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, info); Node4 values; for (u32 element = 0; element < values.size(); ++element) { @@ -173,9 +173,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { SamplerInfo info; info.type = texture_type; info.is_array = is_array; - const std::optional<Sampler> sampler = is_bindless - ? GetBindlessSampler(base_reg, info, index_var) - : GetSampler(instr.sampler, info); + const std::optional<SamplerEntry> sampler = + is_bindless ? GetBindlessSampler(base_reg, info, index_var) + : GetSampler(instr.sampler, info); Node4 values; if (!sampler) { std::generate(values.begin(), values.end(), [this] { return Immediate(0); }); @@ -217,9 +217,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { [[fallthrough]]; case OpCode::Id::TXQ: { Node index_var; - const std::optional<Sampler> sampler = is_bindless - ? GetBindlessSampler(instr.gpr8, {}, index_var) - : GetSampler(instr.sampler, {}); + const std::optional<SamplerEntry> sampler = + is_bindless ? GetBindlessSampler(instr.gpr8, {}, index_var) + : GetSampler(instr.sampler, {}); if (!sampler) { u32 indexer = 0; @@ -255,8 +255,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { break; } default: - UNIMPLEMENTED_MSG("Unhandled texture query type: {}", - static_cast<u32>(instr.txq.query_type.Value())); + UNIMPLEMENTED_MSG("Unhandled texture query type: {}", instr.txq.query_type.Value()); } break; } @@ -273,7 +272,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { info.type = texture_type; info.is_array = is_array; Node index_var; - const std::optional<Sampler> sampler = + const std::optional<SamplerEntry> sampler = is_bindless ? GetBindlessSampler(instr.gpr20, info, index_var) : GetSampler(instr.sampler, info); @@ -292,33 +291,36 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { break; } - std::vector<Node> coords; - - // TODO: Add coordinates for different samplers once other texture types are implemented. - switch (texture_type) { - case TextureType::Texture1D: - coords.push_back(GetRegister(instr.gpr8)); - break; - case TextureType::Texture2D: - coords.push_back(GetRegister(instr.gpr8.Value() + 0)); - coords.push_back(GetRegister(instr.gpr8.Value() + 1)); - break; - default: - UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<int>(texture_type)); + const u64 base_index = is_array ? 1 : 0; + const u64 num_components = [texture_type] { + switch (texture_type) { + case TextureType::Texture1D: + return 1; + case TextureType::Texture2D: + return 2; + case TextureType::TextureCube: + return 3; + default: + UNIMPLEMENTED_MSG("Unhandled texture type {}", texture_type); + return 2; + } + }(); + // TODO: What's the array component used for? - // Fallback to interpreting as a 2D texture for now - coords.push_back(GetRegister(instr.gpr8.Value() + 0)); - coords.push_back(GetRegister(instr.gpr8.Value() + 1)); + std::vector<Node> coords; + coords.reserve(num_components); + for (u64 component = 0; component < num_components; ++component) { + coords.push_back(GetRegister(instr.gpr8.Value() + base_index + component)); } + u32 indexer = 0; for (u32 element = 0; element < 2; ++element) { if (!instr.tmml.IsComponentEnabled(element)) { continue; } - auto params = coords; MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element, index_var}; - const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); - SetTemporary(bb, indexer++, value); + Node value = Operation(OperationCode::TextureQueryLod, meta, coords); + SetTemporary(bb, indexer++, std::move(value)); } for (u32 i = 0; i < indexer; ++i) { SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); @@ -377,14 +379,15 @@ ShaderIR::SamplerInfo ShaderIR::GetSamplerInfo( return info; } -std::optional<Sampler> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler, - SamplerInfo sampler_info) { +std::optional<SamplerEntry> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler, + SamplerInfo sampler_info) { const u32 offset = static_cast<u32>(sampler.index.Value()); const auto info = GetSamplerInfo(sampler_info, registry.ObtainBoundSampler(offset)); // If this sampler has already been used, return the existing mapping. - const auto it = std::find_if(used_samplers.begin(), used_samplers.end(), - [offset](const Sampler& entry) { return entry.offset == offset; }); + const auto it = + std::find_if(used_samplers.begin(), used_samplers.end(), + [offset](const SamplerEntry& entry) { return entry.offset == offset; }); if (it != used_samplers.end()) { ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array && it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer); @@ -397,8 +400,8 @@ std::optional<Sampler> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler, *info.is_shadow, *info.is_buffer, false); } -std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info, - Node& index_var) { +std::optional<SamplerEntry> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, + SamplerInfo info, Node& index_var) { const Node sampler_register = GetRegister(reg); const auto [base_node, tracked_sampler_info] = TrackBindlessSampler(sampler_register, global_code, static_cast<s64>(global_code.size())); @@ -414,7 +417,7 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, // If this sampler has already been used, return the existing mapping. const auto it = std::find_if(used_samplers.begin(), used_samplers.end(), - [buffer, offset](const Sampler& entry) { + [buffer, offset](const SamplerEntry& entry) { return entry.buffer == buffer && entry.offset == offset; }); if (it != used_samplers.end()) { @@ -434,11 +437,12 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, info = GetSamplerInfo(info, registry.ObtainSeparateSampler(indices, offsets)); // Try to use an already created sampler if it exists - const auto it = std::find_if( - used_samplers.begin(), used_samplers.end(), [indices, offsets](const Sampler& entry) { - return offsets == std::pair{entry.offset, entry.secondary_offset} && - indices == std::pair{entry.buffer, entry.secondary_buffer}; - }); + const auto it = + std::find_if(used_samplers.begin(), used_samplers.end(), + [indices, offsets](const SamplerEntry& entry) { + return offsets == std::pair{entry.offset, entry.secondary_offset} && + indices == std::pair{entry.buffer, entry.secondary_buffer}; + }); if (it != used_samplers.end()) { ASSERT(it->is_separated && it->type == info.type && it->is_array == info.is_array && it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer); @@ -458,7 +462,7 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, // If this sampler has already been used, return the existing mapping. const auto it = std::find_if( used_samplers.begin(), used_samplers.end(), - [base_offset](const Sampler& entry) { return entry.offset == base_offset; }); + [base_offset](const SamplerEntry& entry) { return entry.offset == base_offset; }); if (it != used_samplers.end()) { ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array && it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer && @@ -553,7 +557,6 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, const bool is_shadow = depth_compare != nullptr; const bool is_bindless = bindless_reg.has_value(); - UNIMPLEMENTED_IF(texture_type == TextureType::TextureCube && is_array && is_shadow); ASSERT_MSG(texture_type != TextureType::Texture3D || !is_array || !is_shadow, "Illegal texture type"); @@ -564,9 +567,9 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, info.is_buffer = false; Node index_var; - const std::optional<Sampler> sampler = is_bindless - ? GetBindlessSampler(*bindless_reg, info, index_var) - : GetSampler(instr.sampler, info); + const std::optional<SamplerEntry> sampler = + is_bindless ? GetBindlessSampler(*bindless_reg, info, index_var) + : GetSampler(instr.sampler, info); if (!sampler) { return {Immediate(0), Immediate(0), Immediate(0), Immediate(0)}; } @@ -593,7 +596,7 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, lod = GetRegister(instr.gpr20.Value() + bias_offset); break; default: - UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode)); + UNIMPLEMENTED_MSG("Unimplemented process mode={}", process_mode); break; } @@ -723,7 +726,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de info.is_shadow = depth_compare; Node index_var; - const std::optional<Sampler> sampler = + const std::optional<SamplerEntry> sampler = is_bindless ? GetBindlessSampler(parameter_register++, info, index_var) : GetSampler(instr.sampler, info); Node4 values; @@ -763,7 +766,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { const auto texture_type{instr.tld.texture_type}; - const bool is_array{instr.tld.is_array}; + const bool is_array{instr.tld.is_array != 0}; const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; const std::size_t coord_count{GetCoordCount(texture_type)}; @@ -782,7 +785,7 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { // const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr}; // const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr}; - const std::optional<Sampler> sampler = GetSampler(instr.sampler, {}); + const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, {}); Node4 values; for (u32 element = 0; element < values.size(); ++element) { @@ -799,7 +802,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is info.type = texture_type; info.is_array = is_array; info.is_shadow = false; - const std::optional<Sampler> sampler = GetSampler(instr.sampler, info); + const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, info); const std::size_t type_coord_count = GetCoordCount(texture_type); const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; diff --git a/src/video_core/shader/decode/warp.cpp b/src/video_core/shader/decode/warp.cpp index 11b77f795..37433d783 100644 --- a/src/video_core/shader/decode/warp.cpp +++ b/src/video_core/shader/decode/warp.cpp @@ -27,7 +27,7 @@ OperationCode GetOperationCode(VoteOperation vote_op) { case VoteOperation::Eq: return OperationCode::VoteEqual; default: - UNREACHABLE_MSG("Invalid vote operation={}", static_cast<u64>(vote_op)); + UNREACHABLE_MSG("Invalid vote operation={}", vote_op); return OperationCode::VoteAll; } } diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h index 4e8264367..cda284c72 100644 --- a/src/video_core/shader/expr.h +++ b/src/video_core/shader/expr.h @@ -76,7 +76,7 @@ public: class ExprPredicate final { public: - explicit ExprPredicate(u32 predicate) : predicate{predicate} {} + explicit ExprPredicate(u32 predicate_) : predicate{predicate_} {} bool operator==(const ExprPredicate& b) const { return predicate == b.predicate; @@ -91,7 +91,7 @@ public: class ExprCondCode final { public: - explicit ExprCondCode(ConditionCode cc) : cc{cc} {} + explicit ExprCondCode(ConditionCode condition_code) : cc{condition_code} {} bool operator==(const ExprCondCode& b) const { return cc == b.cc; @@ -121,7 +121,7 @@ public: class ExprGprEqual final { public: - ExprGprEqual(u32 gpr, u32 value) : gpr{gpr}, value{value} {} + explicit ExprGprEqual(u32 gpr_, u32 value_) : gpr{gpr_}, value{value_} {} bool operator==(const ExprGprEqual& b) const { return gpr == b.gpr && value == b.value; diff --git a/src/video_core/shader/memory_util.cpp b/src/video_core/shader/memory_util.cpp index 5071c83ca..e18ccba8e 100644 --- a/src/video_core/shader/memory_util.cpp +++ b/src/video_core/shader/memory_util.cpp @@ -16,11 +16,10 @@ namespace VideoCommon::Shader { -GPUVAddr GetShaderAddress(Core::System& system, +GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Engines::Maxwell3D::Regs::ShaderProgram program) { - const auto& gpu{system.GPU().Maxwell3D()}; - const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]}; - return gpu.regs.code_address.CodeAddress() + shader_config.offset; + const auto& shader_config{maxwell3d.regs.shader_config[static_cast<std::size_t>(program)]}; + return maxwell3d.regs.code_address.CodeAddress() + shader_config.offset; } bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { diff --git a/src/video_core/shader/memory_util.h b/src/video_core/shader/memory_util.h index be90d24fd..4624d38e6 100644 --- a/src/video_core/shader/memory_util.h +++ b/src/video_core/shader/memory_util.h @@ -11,10 +11,6 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/shader_type.h" -namespace Core { -class System; -} - namespace Tegra { class MemoryManager; } @@ -27,7 +23,7 @@ constexpr u32 STAGE_MAIN_OFFSET = 10; constexpr u32 KERNEL_MAIN_OFFSET = 0; /// Gets the address for the specified shader stage program -GPUVAddr GetShaderAddress(Core::System& system, +GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Engines::Maxwell3D::Regs::ShaderProgram program); /// Gets if the current instruction offset is a scheduler instruction diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 8f230d57a..b54d33763 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -282,26 +282,27 @@ struct SeparateSamplerNode; using TrackSamplerData = std::variant<BindlessSamplerNode, SeparateSamplerNode, ArraySamplerNode>; using TrackSampler = std::shared_ptr<TrackSamplerData>; -struct Sampler { +struct SamplerEntry { /// Bound samplers constructor - constexpr explicit Sampler(u32 index, u32 offset, Tegra::Shader::TextureType type, - bool is_array, bool is_shadow, bool is_buffer, bool is_indexed) - : index{index}, offset{offset}, type{type}, is_array{is_array}, is_shadow{is_shadow}, - is_buffer{is_buffer}, is_indexed{is_indexed} {} + explicit SamplerEntry(u32 index_, u32 offset_, Tegra::Shader::TextureType type_, bool is_array_, + bool is_shadow_, bool is_buffer_, bool is_indexed_) + : index{index_}, offset{offset_}, type{type_}, is_array{is_array_}, is_shadow{is_shadow_}, + is_buffer{is_buffer_}, is_indexed{is_indexed_} {} /// Separate sampler constructor - constexpr explicit Sampler(u32 index, std::pair<u32, u32> offsets, std::pair<u32, u32> buffers, - Tegra::Shader::TextureType type, bool is_array, bool is_shadow, - bool is_buffer) - : index{index}, offset{offsets.first}, secondary_offset{offsets.second}, - buffer{buffers.first}, secondary_buffer{buffers.second}, type{type}, is_array{is_array}, - is_shadow{is_shadow}, is_buffer{is_buffer}, is_separated{true} {} + explicit SamplerEntry(u32 index_, std::pair<u32, u32> offsets, std::pair<u32, u32> buffers, + Tegra::Shader::TextureType type_, bool is_array_, bool is_shadow_, + bool is_buffer_) + : index{index_}, offset{offsets.first}, secondary_offset{offsets.second}, + buffer{buffers.first}, secondary_buffer{buffers.second}, type{type_}, is_array{is_array_}, + is_shadow{is_shadow_}, is_buffer{is_buffer_}, is_separated{true} {} /// Bindless samplers constructor - constexpr explicit Sampler(u32 index, u32 offset, u32 buffer, Tegra::Shader::TextureType type, - bool is_array, bool is_shadow, bool is_buffer, bool is_indexed) - : index{index}, offset{offset}, buffer{buffer}, type{type}, is_array{is_array}, - is_shadow{is_shadow}, is_buffer{is_buffer}, is_bindless{true}, is_indexed{is_indexed} {} + explicit SamplerEntry(u32 index_, u32 offset_, u32 buffer_, Tegra::Shader::TextureType type_, + bool is_array_, bool is_shadow_, bool is_buffer_, bool is_indexed_) + : index{index_}, offset{offset_}, buffer{buffer_}, type{type_}, is_array{is_array_}, + is_shadow{is_shadow_}, is_buffer{is_buffer_}, is_bindless{true}, is_indexed{is_indexed_} { + } u32 index = 0; ///< Emulated index given for the this sampler. u32 offset = 0; ///< Offset in the const buffer from where the sampler is being read. @@ -338,15 +339,15 @@ struct BindlessSamplerNode { u32 offset; }; -struct Image { +struct ImageEntry { public: /// Bound images constructor - constexpr explicit Image(u32 index, u32 offset, Tegra::Shader::ImageType type) - : index{index}, offset{offset}, type{type} {} + explicit ImageEntry(u32 index_, u32 offset_, Tegra::Shader::ImageType type_) + : index{index_}, offset{offset_}, type{type_} {} /// Bindless samplers constructor - constexpr explicit Image(u32 index, u32 offset, u32 buffer, Tegra::Shader::ImageType type) - : index{index}, offset{offset}, buffer{buffer}, type{type}, is_bindless{true} {} + explicit ImageEntry(u32 index_, u32 offset_, u32 buffer_, Tegra::Shader::ImageType type_) + : index{index_}, offset{offset_}, buffer{buffer_}, type{type_}, is_bindless{true} {} void MarkWrite() { is_written = true; @@ -377,7 +378,7 @@ struct GlobalMemoryBase { u32 cbuf_index = 0; u32 cbuf_offset = 0; - bool operator<(const GlobalMemoryBase& rhs) const { + [[nodiscard]] bool operator<(const GlobalMemoryBase& rhs) const { return std::tie(cbuf_index, cbuf_offset) < std::tie(rhs.cbuf_index, rhs.cbuf_offset); } }; @@ -389,7 +390,7 @@ struct MetaArithmetic { /// Parameters describing a texture sampler struct MetaTexture { - Sampler sampler; + SamplerEntry sampler; Node array; Node depth_compare; std::vector<Node> aoffi; @@ -403,7 +404,7 @@ struct MetaTexture { }; struct MetaImage { - const Image& image; + const ImageEntry& image; std::vector<Node> values; u32 element{}; }; @@ -414,7 +415,7 @@ using Meta = class AmendNode { public: - std::optional<std::size_t> GetAmendIndex() const { + [[nodiscard]] std::optional<std::size_t> GetAmendIndex() const { if (amend_index == amend_null_index) { return std::nullopt; } @@ -437,34 +438,34 @@ private: /// Holds any kind of operation that can be done in the IR class OperationNode final : public AmendNode { public: - explicit OperationNode(OperationCode code) : OperationNode(code, Meta{}) {} + explicit OperationNode(OperationCode code_) : OperationNode(code_, Meta{}) {} - explicit OperationNode(OperationCode code, Meta meta) - : OperationNode(code, std::move(meta), std::vector<Node>{}) {} + explicit OperationNode(OperationCode code_, Meta meta_) + : OperationNode(code_, std::move(meta_), std::vector<Node>{}) {} - explicit OperationNode(OperationCode code, std::vector<Node> operands) - : OperationNode(code, Meta{}, std::move(operands)) {} + explicit OperationNode(OperationCode code_, std::vector<Node> operands_) + : OperationNode(code_, Meta{}, std::move(operands_)) {} - explicit OperationNode(OperationCode code, Meta meta, std::vector<Node> operands) - : code{code}, meta{std::move(meta)}, operands{std::move(operands)} {} + explicit OperationNode(OperationCode code_, Meta meta_, std::vector<Node> operands_) + : code{code_}, meta{std::move(meta_)}, operands{std::move(operands_)} {} template <typename... Args> - explicit OperationNode(OperationCode code, Meta meta, Args&&... operands) - : code{code}, meta{std::move(meta)}, operands{operands...} {} + explicit OperationNode(OperationCode code_, Meta meta_, Args&&... operands_) + : code{code_}, meta{std::move(meta_)}, operands{operands_...} {} - OperationCode GetCode() const { + [[nodiscard]] OperationCode GetCode() const { return code; } - const Meta& GetMeta() const { + [[nodiscard]] const Meta& GetMeta() const { return meta; } - std::size_t GetOperandsCount() const { + [[nodiscard]] std::size_t GetOperandsCount() const { return operands.size(); } - const Node& operator[](std::size_t operand_index) const { + [[nodiscard]] const Node& operator[](std::size_t operand_index) const { return operands.at(operand_index); } @@ -477,14 +478,14 @@ private: /// Encloses inside any kind of node that returns a boolean conditionally-executed code class ConditionalNode final : public AmendNode { public: - explicit ConditionalNode(Node condition, std::vector<Node>&& code) - : condition{std::move(condition)}, code{std::move(code)} {} + explicit ConditionalNode(Node condition_, std::vector<Node>&& code_) + : condition{std::move(condition_)}, code{std::move(code_)} {} - const Node& GetCondition() const { + [[nodiscard]] const Node& GetCondition() const { return condition; } - const std::vector<Node>& GetCode() const { + [[nodiscard]] const std::vector<Node>& GetCode() const { return code; } @@ -496,9 +497,9 @@ private: /// A general purpose register class GprNode final { public: - explicit constexpr GprNode(Tegra::Shader::Register index) : index{index} {} + explicit constexpr GprNode(Tegra::Shader::Register index_) : index{index_} {} - u32 GetIndex() const { + [[nodiscard]] constexpr u32 GetIndex() const { return static_cast<u32>(index); } @@ -509,9 +510,9 @@ private: /// A custom variable class CustomVarNode final { public: - explicit constexpr CustomVarNode(u32 index) : index{index} {} + explicit constexpr CustomVarNode(u32 index_) : index{index_} {} - constexpr u32 GetIndex() const { + [[nodiscard]] constexpr u32 GetIndex() const { return index; } @@ -522,9 +523,9 @@ private: /// A 32-bits value that represents an immediate value class ImmediateNode final { public: - explicit constexpr ImmediateNode(u32 value) : value{value} {} + explicit constexpr ImmediateNode(u32 value_) : value{value_} {} - u32 GetValue() const { + [[nodiscard]] constexpr u32 GetValue() const { return value; } @@ -535,9 +536,9 @@ private: /// One of Maxwell's internal flags class InternalFlagNode final { public: - explicit constexpr InternalFlagNode(InternalFlag flag) : flag{flag} {} + explicit constexpr InternalFlagNode(InternalFlag flag_) : flag{flag_} {} - InternalFlag GetFlag() const { + [[nodiscard]] constexpr InternalFlag GetFlag() const { return flag; } @@ -548,14 +549,14 @@ private: /// A predicate register, it can be negated without additional nodes class PredicateNode final { public: - explicit constexpr PredicateNode(Tegra::Shader::Pred index, bool negated) - : index{index}, negated{negated} {} + explicit constexpr PredicateNode(Tegra::Shader::Pred index_, bool negated_) + : index{index_}, negated{negated_} {} - Tegra::Shader::Pred GetIndex() const { + [[nodiscard]] constexpr Tegra::Shader::Pred GetIndex() const { return index; } - bool IsNegated() const { + [[nodiscard]] constexpr bool IsNegated() const { return negated; } @@ -568,30 +569,30 @@ private: class AbufNode final { public: // Initialize for standard attributes (index is explicit). - explicit AbufNode(Tegra::Shader::Attribute::Index index, u32 element, Node buffer = {}) - : buffer{std::move(buffer)}, index{index}, element{element} {} + explicit AbufNode(Tegra::Shader::Attribute::Index index_, u32 element_, Node buffer_ = {}) + : buffer{std::move(buffer_)}, index{index_}, element{element_} {} // Initialize for physical attributes (index is a variable value). - explicit AbufNode(Node physical_address, Node buffer = {}) - : physical_address{std::move(physical_address)}, buffer{std::move(buffer)} {} + explicit AbufNode(Node physical_address_, Node buffer_ = {}) + : physical_address{std::move(physical_address_)}, buffer{std::move(buffer_)} {} - Tegra::Shader::Attribute::Index GetIndex() const { + [[nodiscard]] Tegra::Shader::Attribute::Index GetIndex() const { return index; } - u32 GetElement() const { + [[nodiscard]] u32 GetElement() const { return element; } - const Node& GetBuffer() const { + [[nodiscard]] const Node& GetBuffer() const { return buffer; } - bool IsPhysicalBuffer() const { + [[nodiscard]] bool IsPhysicalBuffer() const { return static_cast<bool>(physical_address); } - const Node& GetPhysicalAddress() const { + [[nodiscard]] const Node& GetPhysicalAddress() const { return physical_address; } @@ -605,9 +606,9 @@ private: /// Patch memory (used to communicate tessellation stages). class PatchNode final { public: - explicit PatchNode(u32 offset) : offset{offset} {} + explicit constexpr PatchNode(u32 offset_) : offset{offset_} {} - u32 GetOffset() const { + [[nodiscard]] constexpr u32 GetOffset() const { return offset; } @@ -618,13 +619,13 @@ private: /// Constant buffer node, usually mapped to uniform buffers in GLSL class CbufNode final { public: - explicit CbufNode(u32 index, Node offset) : index{index}, offset{std::move(offset)} {} + explicit CbufNode(u32 index_, Node offset_) : index{index_}, offset{std::move(offset_)} {} - u32 GetIndex() const { + [[nodiscard]] u32 GetIndex() const { return index; } - const Node& GetOffset() const { + [[nodiscard]] const Node& GetOffset() const { return offset; } @@ -636,9 +637,9 @@ private: /// Local memory node class LmemNode final { public: - explicit LmemNode(Node address) : address{std::move(address)} {} + explicit LmemNode(Node address_) : address{std::move(address_)} {} - const Node& GetAddress() const { + [[nodiscard]] const Node& GetAddress() const { return address; } @@ -649,9 +650,9 @@ private: /// Shared memory node class SmemNode final { public: - explicit SmemNode(Node address) : address{std::move(address)} {} + explicit SmemNode(Node address_) : address{std::move(address_)} {} - const Node& GetAddress() const { + [[nodiscard]] const Node& GetAddress() const { return address; } @@ -662,19 +663,19 @@ private: /// Global memory node class GmemNode final { public: - explicit GmemNode(Node real_address, Node base_address, const GlobalMemoryBase& descriptor) - : real_address{std::move(real_address)}, base_address{std::move(base_address)}, - descriptor{descriptor} {} + explicit GmemNode(Node real_address_, Node base_address_, const GlobalMemoryBase& descriptor_) + : real_address{std::move(real_address_)}, base_address{std::move(base_address_)}, + descriptor{descriptor_} {} - const Node& GetRealAddress() const { + [[nodiscard]] const Node& GetRealAddress() const { return real_address; } - const Node& GetBaseAddress() const { + [[nodiscard]] const Node& GetBaseAddress() const { return base_address; } - const GlobalMemoryBase& GetDescriptor() const { + [[nodiscard]] const GlobalMemoryBase& GetDescriptor() const { return descriptor; } @@ -687,9 +688,9 @@ private: /// Commentary, can be dropped class CommentNode final { public: - explicit CommentNode(std::string text) : text{std::move(text)} {} + explicit CommentNode(std::string text_) : text{std::move(text_)} {} - const std::string& GetText() const { + [[nodiscard]] const std::string& GetText() const { return text; } diff --git a/src/video_core/shader/node_helper.cpp b/src/video_core/shader/node_helper.cpp index 7bf4ff387..6a5b6940d 100644 --- a/src/video_core/shader/node_helper.cpp +++ b/src/video_core/shader/node_helper.cpp @@ -107,7 +107,7 @@ OperationCode SignedToUnsignedCode(OperationCode operation_code, bool is_signed) UNREACHABLE_MSG("Can't apply absolute to an unsigned integer"); return {}; default: - UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code)); + UNREACHABLE_MSG("Unknown signed operation with code={}", operation_code); return {}; } } diff --git a/src/video_core/shader/registry.cpp b/src/video_core/shader/registry.cpp index cdf274e54..148d91fcb 100644 --- a/src/video_core/shader/registry.cpp +++ b/src/video_core/shader/registry.cpp @@ -24,44 +24,45 @@ GraphicsInfo MakeGraphicsInfo(ShaderType shader_stage, ConstBufferEngineInterfac if (shader_stage == ShaderType::Compute) { return {}; } - auto& graphics = static_cast<Tegra::Engines::Maxwell3D&>(engine); - - GraphicsInfo info; - info.tfb_layouts = graphics.regs.tfb_layouts; - info.tfb_varying_locs = graphics.regs.tfb_varying_locs; - info.primitive_topology = graphics.regs.draw.topology; - info.tessellation_primitive = graphics.regs.tess_mode.prim; - info.tessellation_spacing = graphics.regs.tess_mode.spacing; - info.tfb_enabled = graphics.regs.tfb_enabled; - info.tessellation_clockwise = graphics.regs.tess_mode.cw; - return info; + + auto& graphics = dynamic_cast<Tegra::Engines::Maxwell3D&>(engine); + + return { + .tfb_layouts = graphics.regs.tfb_layouts, + .tfb_varying_locs = graphics.regs.tfb_varying_locs, + .primitive_topology = graphics.regs.draw.topology, + .tessellation_primitive = graphics.regs.tess_mode.prim, + .tessellation_spacing = graphics.regs.tess_mode.spacing, + .tfb_enabled = graphics.regs.tfb_enabled != 0, + .tessellation_clockwise = graphics.regs.tess_mode.cw.Value() != 0, + }; } ComputeInfo MakeComputeInfo(ShaderType shader_stage, ConstBufferEngineInterface& engine) { if (shader_stage != ShaderType::Compute) { return {}; } - auto& compute = static_cast<Tegra::Engines::KeplerCompute&>(engine); + + auto& compute = dynamic_cast<Tegra::Engines::KeplerCompute&>(engine); const auto& launch = compute.launch_description; - ComputeInfo info; - info.workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z}; - info.local_memory_size_in_words = launch.local_pos_alloc; - info.shared_memory_size_in_words = launch.shared_alloc; - return info; + return { + .workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z}, + .shared_memory_size_in_words = launch.shared_alloc, + .local_memory_size_in_words = launch.local_pos_alloc, + }; } } // Anonymous namespace -Registry::Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info) +Registry::Registry(ShaderType shader_stage, const SerializedRegistryInfo& info) : stage{shader_stage}, stored_guest_driver_profile{info.guest_driver_profile}, bound_buffer{info.bound_buffer}, graphics_info{info.graphics}, compute_info{info.compute} {} -Registry::Registry(Tegra::Engines::ShaderType shader_stage, - Tegra::Engines::ConstBufferEngineInterface& engine) - : stage{shader_stage}, engine{&engine}, bound_buffer{engine.GetBoundBuffer()}, - graphics_info{MakeGraphicsInfo(shader_stage, engine)}, compute_info{MakeComputeInfo( - shader_stage, engine)} {} +Registry::Registry(ShaderType shader_stage, ConstBufferEngineInterface& engine_) + : stage{shader_stage}, engine{&engine_}, bound_buffer{engine_.GetBoundBuffer()}, + graphics_info{MakeGraphicsInfo(shader_stage, engine_)}, compute_info{MakeComputeInfo( + shader_stage, engine_)} {} Registry::~Registry() = default; @@ -113,8 +114,7 @@ std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainSeparateSampler return value; } -std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer, - u32 offset) { +std::optional<SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer, u32 offset) { const std::pair key = {buffer, offset}; const auto iter = bindless_samplers.find(key); if (iter != bindless_samplers.end()) { diff --git a/src/video_core/shader/registry.h b/src/video_core/shader/registry.h index 231206765..4bebefdde 100644 --- a/src/video_core/shader/registry.h +++ b/src/video_core/shader/registry.h @@ -94,7 +94,7 @@ public: explicit Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info); explicit Registry(Tegra::Engines::ShaderType shader_stage, - Tegra::Engines::ConstBufferEngineInterface& engine); + Tegra::Engines::ConstBufferEngineInterface& engine_); ~Registry(); diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp index 29d794b34..a4987ffc6 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -25,9 +25,10 @@ using Tegra::Shader::PredCondition; using Tegra::Shader::PredOperation; using Tegra::Shader::Register; -ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings, - Registry& registry) - : program_code{program_code}, main_offset{main_offset}, settings{settings}, registry{registry} { +ShaderIR::ShaderIR(const ProgramCode& program_code_, u32 main_offset_, CompilerSettings settings_, + Registry& registry_) + : program_code{program_code_}, main_offset{main_offset_}, settings{settings_}, registry{ + registry_} { Decode(); PostDecode(); } @@ -170,7 +171,7 @@ Node ShaderIR::ConvertIntegerSize(Node value, Register::Size size, bool is_signe // Default - do nothing return value; default: - UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size)); + UNREACHABLE_MSG("Unimplemented conversion size: {}", size); return value; } } @@ -335,15 +336,15 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) { return operation_table[index]; } -Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const { +Node ShaderIR::GetConditionCode(ConditionCode cc) const { switch (cc) { - case Tegra::Shader::ConditionCode::NEU: + case ConditionCode::NEU: return GetInternalFlag(InternalFlag::Zero, true); - case Tegra::Shader::ConditionCode::FCSM_TR: + case ConditionCode::FCSM_TR: UNIMPLEMENTED_MSG("EXIT.FCSM_TR is not implemented"); return MakeNode<PredicateNode>(Pred::NeverExecute, false); default: - UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc)); + UNIMPLEMENTED_MSG("Unimplemented condition code: {}", cc); return MakeNode<PredicateNode>(Pred::NeverExecute, false); } } @@ -451,8 +452,8 @@ void ShaderIR::MarkAttributeUsage(Attribute::Index index, u64 element) { } std::size_t ShaderIR::DeclareAmend(Node new_amend) { - const std::size_t id = amend_code.size(); - amend_code.push_back(new_amend); + const auto id = amend_code.size(); + amend_code.push_back(std::move(new_amend)); return id; } diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 3a98b2104..1cd7c14d7 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -29,8 +29,8 @@ struct ShaderBlock; constexpr u32 MAX_PROGRAM_LENGTH = 0x1000; struct ConstBuffer { - constexpr explicit ConstBuffer(u32 max_offset, bool is_indirect) - : max_offset{max_offset}, is_indirect{is_indirect} {} + constexpr explicit ConstBuffer(u32 max_offset_, bool is_indirect_) + : max_offset{max_offset_}, is_indirect{is_indirect_} {} constexpr ConstBuffer() = default; @@ -66,8 +66,8 @@ struct GlobalMemoryUsage { class ShaderIR final { public: - explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings, - Registry& registry); + explicit ShaderIR(const ProgramCode& program_code_, u32 main_offset_, + CompilerSettings settings_, Registry& registry_); ~ShaderIR(); const std::map<u32, NodeBlock>& GetBasicBlocks() const { @@ -94,11 +94,11 @@ public: return used_cbufs; } - const std::list<Sampler>& GetSamplers() const { + const std::list<SamplerEntry>& GetSamplers() const { return used_samplers; } - const std::list<Image>& GetImages() const { + const std::list<ImageEntry>& GetImages() const { return used_images; } @@ -139,6 +139,10 @@ public: return uses_legacy_varyings; } + bool UsesYNegate() const { + return uses_y_negate; + } + bool UsesWarps() const { return uses_warps; } @@ -334,17 +338,17 @@ private: std::optional<Tegra::Engines::SamplerDescriptor> sampler); /// Accesses a texture sampler. - std::optional<Sampler> GetSampler(Tegra::Shader::Sampler sampler, SamplerInfo info); + std::optional<SamplerEntry> GetSampler(Tegra::Shader::Sampler sampler, SamplerInfo info); /// Accesses a texture sampler for a bindless texture. - std::optional<Sampler> GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info, - Node& index_var); + std::optional<SamplerEntry> GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info, + Node& index_var); /// Accesses an image. - Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); + ImageEntry& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); /// Access a bindless image sampler. - Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); + ImageEntry& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); @@ -454,8 +458,8 @@ private: std::set<Tegra::Shader::Attribute::Index> used_input_attributes; std::set<Tegra::Shader::Attribute::Index> used_output_attributes; std::map<u32, ConstBuffer> used_cbufs; - std::list<Sampler> used_samplers; - std::list<Image> used_images; + std::list<SamplerEntry> used_samplers; + std::list<ImageEntry> used_images; std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; bool uses_layer{}; @@ -465,6 +469,7 @@ private: bool uses_instance_id{}; bool uses_vertex_id{}; bool uses_legacy_varyings{}; + bool uses_y_negate{}; bool uses_warps{}; bool uses_indexed_samplers{}; diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp index d5ed81442..6be3ea92b 100644 --- a/src/video_core/shader/track.cpp +++ b/src/video_core/shader/track.cpp @@ -205,12 +205,12 @@ std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, const auto result = TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1); const auto& found = result.first; if (!found) { - return {}; + return std::nullopt; } if (const auto immediate = std::get_if<ImmediateNode>(&*found)) { return immediate->GetValue(); } - return {}; + return std::nullopt; } std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code, diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 1688267bb..6308aef94 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -28,7 +28,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t case Tegra::Texture::TextureType::Texture2DArray: return SurfaceTarget::Texture2DArray; default: - LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type)); + LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", texture_type); UNREACHABLE(); return SurfaceTarget::Texture2D; } @@ -47,7 +47,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) { case SurfaceTarget::TextureCubeArray: return true; default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target)); + LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target); UNREACHABLE(); return false; } @@ -66,7 +66,7 @@ bool SurfaceTargetIsArray(SurfaceTarget target) { case SurfaceTarget::TextureCubeArray: return true; default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target)); + LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target); UNREACHABLE(); return false; } @@ -85,7 +85,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) { case Tegra::DepthFormat::D32_FLOAT_S8X24_UINT: return PixelFormat::D32_FLOAT_S8_UINT; default: - UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); + UNIMPLEMENTED_MSG("Unimplemented format={}", format); return PixelFormat::S8_UINT_D24_UNORM; } } @@ -183,7 +183,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) case Tegra::RenderTargetFormat::R8_UINT: return PixelFormat::R8_UINT; default: - UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<int>(format)); + UNIMPLEMENTED_MSG("Unimplemented format={}", format); return PixelFormat::A8B8G8R8_UNORM; } } @@ -197,7 +197,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM: return PixelFormat::B8G8R8A8_UNORM; default: - UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); + UNIMPLEMENTED_MSG("Unimplemented format={}", format); return PixelFormat::A8B8G8R8_UNORM; } } @@ -280,7 +280,7 @@ bool IsPixelFormatSRGB(PixelFormat format) { } std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { - return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)}; + return {DefaultBlockWidth(format), DefaultBlockHeight(format)}; } } // namespace VideoCore::Surface diff --git a/src/video_core/surface.h b/src/video_core/surface.h index cfd12fa61..c40ab89d0 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -120,7 +120,7 @@ enum class PixelFormat { Max = MaxDepthStencilFormat, Invalid = 255, }; -static constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max); +constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max); enum class SurfaceType { ColorTexture = 0, @@ -140,117 +140,7 @@ enum class SurfaceTarget { TextureCubeArray, }; -constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{ - 0, // A8B8G8R8_UNORM - 0, // A8B8G8R8_SNORM - 0, // A8B8G8R8_SINT - 0, // A8B8G8R8_UINT - 0, // R5G6B5_UNORM - 0, // B5G6R5_UNORM - 0, // A1R5G5B5_UNORM - 0, // A2B10G10R10_UNORM - 0, // A2B10G10R10_UINT - 0, // A1B5G5R5_UNORM - 0, // R8_UNORM - 0, // R8_SNORM - 0, // R8_SINT - 0, // R8_UINT - 0, // R16G16B16A16_FLOAT - 0, // R16G16B16A16_UNORM - 0, // R16G16B16A16_SNORM - 0, // R16G16B16A16_SINT - 0, // R16G16B16A16_UINT - 0, // B10G11R11_FLOAT - 0, // R32G32B32A32_UINT - 2, // BC1_RGBA_UNORM - 2, // BC2_UNORM - 2, // BC3_UNORM - 2, // BC4_UNORM - 2, // BC4_SNORM - 2, // BC5_UNORM - 2, // BC5_SNORM - 2, // BC7_UNORM - 2, // BC6H_UFLOAT - 2, // BC6H_SFLOAT - 2, // ASTC_2D_4X4_UNORM - 0, // B8G8R8A8_UNORM - 0, // R32G32B32A32_FLOAT - 0, // R32G32B32A32_SINT - 0, // R32G32_FLOAT - 0, // R32G32_SINT - 0, // R32_FLOAT - 0, // R16_FLOAT - 0, // R16_UNORM - 0, // R16_SNORM - 0, // R16_UINT - 0, // R16_SINT - 0, // R16G16_UNORM - 0, // R16G16_FLOAT - 0, // R16G16_UINT - 0, // R16G16_SINT - 0, // R16G16_SNORM - 0, // R32G32B32_FLOAT - 0, // A8B8G8R8_SRGB - 0, // R8G8_UNORM - 0, // R8G8_SNORM - 0, // R8G8_SINT - 0, // R8G8_UINT - 0, // R32G32_UINT - 0, // R16G16B16X16_FLOAT - 0, // R32_UINT - 0, // R32_SINT - 2, // ASTC_2D_8X8_UNORM - 2, // ASTC_2D_8X5_UNORM - 2, // ASTC_2D_5X4_UNORM - 0, // B8G8R8A8_SRGB - 2, // BC1_RGBA_SRGB - 2, // BC2_SRGB - 2, // BC3_SRGB - 2, // BC7_SRGB - 0, // A4B4G4R4_UNORM - 2, // ASTC_2D_4X4_SRGB - 2, // ASTC_2D_8X8_SRGB - 2, // ASTC_2D_8X5_SRGB - 2, // ASTC_2D_5X4_SRGB - 2, // ASTC_2D_5X5_UNORM - 2, // ASTC_2D_5X5_SRGB - 2, // ASTC_2D_10X8_UNORM - 2, // ASTC_2D_10X8_SRGB - 2, // ASTC_2D_6X6_UNORM - 2, // ASTC_2D_6X6_SRGB - 2, // ASTC_2D_10X10_UNORM - 2, // ASTC_2D_10X10_SRGB - 2, // ASTC_2D_12X12_UNORM - 2, // ASTC_2D_12X12_SRGB - 2, // ASTC_2D_8X6_UNORM - 2, // ASTC_2D_8X6_SRGB - 2, // ASTC_2D_6X5_UNORM - 2, // ASTC_2D_6X5_SRGB - 0, // E5B9G9R9_FLOAT - 0, // D32_FLOAT - 0, // D16_UNORM - 0, // D24_UNORM_S8_UINT - 0, // S8_UINT_D24_UNORM - 0, // D32_FLOAT_S8_UINT -}}; - -/** - * Gets the compression factor for the specified PixelFormat. This applies to just the - * "compressed width" and "compressed height", not the overall compression factor of a - * compressed image. This is used for maintaining proper surface sizes for compressed - * texture formats. - */ -inline constexpr u32 GetCompressionFactorShift(PixelFormat format) { - DEBUG_ASSERT(format != PixelFormat::Invalid); - DEBUG_ASSERT(static_cast<std::size_t>(format) < compression_factor_shift_table.size()); - return compression_factor_shift_table[static_cast<std::size_t>(format)]; -} - -inline constexpr u32 GetCompressionFactor(PixelFormat format) { - return 1U << GetCompressionFactorShift(format); -} - -constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ +constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ 1, // A8B8G8R8_UNORM 1, // A8B8G8R8_SNORM 1, // A8B8G8R8_SINT @@ -344,15 +234,12 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ 1, // D32_FLOAT_S8_UINT }}; -static constexpr u32 GetDefaultBlockWidth(PixelFormat format) { - if (format == PixelFormat::Invalid) - return 0; - - ASSERT(static_cast<std::size_t>(format) < block_width_table.size()); - return block_width_table[static_cast<std::size_t>(format)]; +constexpr u32 DefaultBlockWidth(PixelFormat format) { + ASSERT(static_cast<std::size_t>(format) < BLOCK_WIDTH_TABLE.size()); + return BLOCK_WIDTH_TABLE[static_cast<std::size_t>(format)]; } -constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ +constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ 1, // A8B8G8R8_UNORM 1, // A8B8G8R8_SNORM 1, // A8B8G8R8_SINT @@ -446,15 +333,12 @@ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ 1, // D32_FLOAT_S8_UINT }}; -static constexpr u32 GetDefaultBlockHeight(PixelFormat format) { - if (format == PixelFormat::Invalid) - return 0; - - ASSERT(static_cast<std::size_t>(format) < block_height_table.size()); - return block_height_table[static_cast<std::size_t>(format)]; +constexpr u32 DefaultBlockHeight(PixelFormat format) { + ASSERT(static_cast<std::size_t>(format) < BLOCK_HEIGHT_TABLE.size()); + return BLOCK_HEIGHT_TABLE[static_cast<std::size_t>(format)]; } -constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ +constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ 32, // A8B8G8R8_UNORM 32, // A8B8G8R8_SNORM 32, // A8B8G8R8_SINT @@ -548,20 +432,14 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ 64, // D32_FLOAT_S8_UINT }}; -static constexpr u32 GetFormatBpp(PixelFormat format) { - if (format == PixelFormat::Invalid) - return 0; - - ASSERT(static_cast<std::size_t>(format) < bpp_table.size()); - return bpp_table[static_cast<std::size_t>(format)]; +constexpr u32 BitsPerBlock(PixelFormat format) { + ASSERT(static_cast<std::size_t>(format) < BITS_PER_BLOCK_TABLE.size()); + return BITS_PER_BLOCK_TABLE[static_cast<std::size_t>(format)]; } /// Returns the sizer in bytes of the specified pixel format -static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) { - if (pixel_format == PixelFormat::Invalid) { - return 0; - } - return GetFormatBpp(pixel_format) / CHAR_BIT; +constexpr u32 BytesPerBlock(PixelFormat pixel_format) { + return BitsPerBlock(pixel_format) / CHAR_BIT; } SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type); diff --git a/src/video_core/texture_cache/accelerated_swizzle.cpp b/src/video_core/texture_cache/accelerated_swizzle.cpp new file mode 100644 index 000000000..15585caeb --- /dev/null +++ b/src/video_core/texture_cache/accelerated_swizzle.cpp @@ -0,0 +1,70 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <bit> + +#include "common/alignment.h" +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/accelerated_swizzle.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/decoders.h" + +namespace VideoCommon::Accelerated { + +using Tegra::Texture::GOB_SIZE_SHIFT; +using Tegra::Texture::GOB_SIZE_X; +using Tegra::Texture::GOB_SIZE_X_SHIFT; +using Tegra::Texture::GOB_SIZE_Y_SHIFT; +using VideoCore::Surface::BytesPerBlock; + +BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(const SwizzleParameters& swizzle, + const ImageInfo& info) { + const Extent3D block = swizzle.block; + const Extent3D num_tiles = swizzle.num_tiles; + const u32 bytes_per_block = BytesPerBlock(info.format); + const u32 stride_alignment = CalculateLevelStrideAlignment(info, swizzle.level); + const u32 stride = Common::AlignUpLog2(num_tiles.width, stride_alignment) * bytes_per_block; + const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); + return BlockLinearSwizzle2DParams{ + .origin{0, 0, 0}, + .destination{0, 0, 0}, + .bytes_per_block_log2 = static_cast<u32>(std::countr_zero(bytes_per_block)), + .layer_stride = info.layer_stride, + .block_size = gobs_in_x << (GOB_SIZE_SHIFT + block.height + block.depth), + .x_shift = GOB_SIZE_SHIFT + block.height + block.depth, + .block_height = block.height, + .block_height_mask = (1U << block.height) - 1, + }; +} + +BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams(const SwizzleParameters& swizzle, + const ImageInfo& info) { + const Extent3D block = swizzle.block; + const Extent3D num_tiles = swizzle.num_tiles; + const u32 bytes_per_block = BytesPerBlock(info.format); + const u32 stride_alignment = CalculateLevelStrideAlignment(info, swizzle.level); + const u32 stride = Common::AlignUpLog2(num_tiles.width, stride_alignment) * bytes_per_block; + + const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) >> GOB_SIZE_X_SHIFT; + const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block.height + block.depth); + const u32 slice_size = + Common::DivCeilLog2(num_tiles.height, block.height + GOB_SIZE_Y_SHIFT) * block_size; + return BlockLinearSwizzle3DParams{ + .origin{0, 0, 0}, + .destination{0, 0, 0}, + .bytes_per_block_log2 = static_cast<u32>(std::countr_zero(bytes_per_block)), + .slice_size = slice_size, + .block_size = block_size, + .x_shift = GOB_SIZE_SHIFT + block.height + block.depth, + .block_height = block.height, + .block_height_mask = (1U << block.height) - 1, + .block_depth = block.depth, + .block_depth_mask = (1U << block.depth) - 1, + }; +} + +} // namespace VideoCommon::Accelerated
\ No newline at end of file diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h new file mode 100644 index 000000000..6ec5c78c4 --- /dev/null +++ b/src/video_core/texture_cache/accelerated_swizzle.h @@ -0,0 +1,45 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_types.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon::Accelerated { + +struct BlockLinearSwizzle2DParams { + std::array<u32, 3> origin; + std::array<s32, 3> destination; + u32 bytes_per_block_log2; + u32 layer_stride; + u32 block_size; + u32 x_shift; + u32 block_height; + u32 block_height_mask; +}; + +struct BlockLinearSwizzle3DParams { + std::array<u32, 3> origin; + std::array<s32, 3> destination; + u32 bytes_per_block_log2; + u32 slice_size; + u32 block_size; + u32 x_shift; + u32 block_height; + u32 block_height_mask; + u32 block_depth; + u32 block_depth_mask; +}; + +[[nodiscard]] BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams( + const SwizzleParameters& swizzle, const ImageInfo& info); + +[[nodiscard]] BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams( + const SwizzleParameters& swizzle, const ImageInfo& info); + +} // namespace VideoCommon::Accelerated diff --git a/src/video_core/texture_cache/copy_params.h b/src/video_core/texture_cache/copy_params.h deleted file mode 100644 index 9c21a0649..000000000 --- a/src/video_core/texture_cache/copy_params.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace VideoCommon { - -struct CopyParams { - constexpr CopyParams(u32 source_x, u32 source_y, u32 source_z, u32 dest_x, u32 dest_y, - u32 dest_z, u32 source_level, u32 dest_level, u32 width, u32 height, - u32 depth) - : source_x{source_x}, source_y{source_y}, source_z{source_z}, dest_x{dest_x}, - dest_y{dest_y}, dest_z{dest_z}, source_level{source_level}, - dest_level{dest_level}, width{width}, height{height}, depth{depth} {} - - constexpr CopyParams(u32 width, u32 height, u32 depth, u32 level) - : source_x{}, source_y{}, source_z{}, dest_x{}, dest_y{}, dest_z{}, source_level{level}, - dest_level{level}, width{width}, height{height}, depth{depth} {} - - u32 source_x; - u32 source_y; - u32 source_z; - u32 dest_x; - u32 dest_y; - u32 dest_z; - u32 source_level; - u32 dest_level; - u32 width; - u32 height; - u32 depth; -}; - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/decode_bc4.cpp b/src/video_core/texture_cache/decode_bc4.cpp new file mode 100644 index 000000000..017327975 --- /dev/null +++ b/src/video_core/texture_cache/decode_bc4.cpp @@ -0,0 +1,97 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <span> + +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/texture_cache/decode_bc4.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_rgtc.txt +[[nodiscard]] constexpr u32 DecompressBlock(u64 bits, u32 x, u32 y) { + const u32 code_offset = 16 + 3 * (4 * y + x); + const u32 code = (bits >> code_offset) & 7; + const u32 red0 = (bits >> 0) & 0xff; + const u32 red1 = (bits >> 8) & 0xff; + if (red0 > red1) { + switch (code) { + case 0: + return red0; + case 1: + return red1; + case 2: + return (6 * red0 + 1 * red1) / 7; + case 3: + return (5 * red0 + 2 * red1) / 7; + case 4: + return (4 * red0 + 3 * red1) / 7; + case 5: + return (3 * red0 + 4 * red1) / 7; + case 6: + return (2 * red0 + 5 * red1) / 7; + case 7: + return (1 * red0 + 6 * red1) / 7; + } + } else { + switch (code) { + case 0: + return red0; + case 1: + return red1; + case 2: + return (4 * red0 + 1 * red1) / 5; + case 3: + return (3 * red0 + 2 * red1) / 5; + case 4: + return (2 * red0 + 3 * red1) / 5; + case 5: + return (1 * red0 + 4 * red1) / 5; + case 6: + return 0; + case 7: + return 0xff; + } + } + return 0; +} + +void DecompressBC4(std::span<const u8> input, Extent3D extent, std::span<u8> output) { + UNIMPLEMENTED_IF_MSG(extent.width % 4 != 0, "Unaligned width={}", extent.width); + UNIMPLEMENTED_IF_MSG(extent.height % 4 != 0, "Unaligned height={}", extent.height); + static constexpr u32 BLOCK_SIZE = 4; + size_t input_offset = 0; + for (u32 slice = 0; slice < extent.depth; ++slice) { + for (u32 block_y = 0; block_y < extent.height / 4; ++block_y) { + for (u32 block_x = 0; block_x < extent.width / 4; ++block_x) { + u64 bits; + std::memcpy(&bits, &input[input_offset], sizeof(bits)); + input_offset += sizeof(bits); + + for (u32 y = 0; y < BLOCK_SIZE; ++y) { + for (u32 x = 0; x < BLOCK_SIZE; ++x) { + const u32 linear_z = slice; + const u32 linear_y = block_y * BLOCK_SIZE + y; + const u32 linear_x = block_x * BLOCK_SIZE + x; + const u32 offset_z = linear_z * extent.width * extent.height; + const u32 offset_y = linear_y * extent.width; + const u32 offset_x = linear_x; + const u32 output_offset = (offset_z + offset_y + offset_x) * 4ULL; + const u32 color = DecompressBlock(bits, x, y); + output[output_offset + 0] = static_cast<u8>(color); + output[output_offset + 1] = 0; + output[output_offset + 2] = 0; + output[output_offset + 3] = 0xff; + } + } + } + } + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/decode_bc4.h b/src/video_core/texture_cache/decode_bc4.h new file mode 100644 index 000000000..63fb23508 --- /dev/null +++ b/src/video_core/texture_cache/decode_bc4.h @@ -0,0 +1,16 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <span> + +#include "common/common_types.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +void DecompressBC4(std::span<const u8> data, Extent3D extent, std::span<u8> output); + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/descriptor_table.h b/src/video_core/texture_cache/descriptor_table.h new file mode 100644 index 000000000..3a03b786f --- /dev/null +++ b/src/video_core/texture_cache/descriptor_table.h @@ -0,0 +1,82 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <vector> + +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "common/logging/log.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" + +namespace VideoCommon { + +template <typename Descriptor> +class DescriptorTable { +public: + explicit DescriptorTable(Tegra::MemoryManager& gpu_memory_) : gpu_memory{gpu_memory_} {} + + [[nodiscard]] bool Synchornize(GPUVAddr gpu_addr, u32 limit) { + [[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) { + return false; + } + Refresh(gpu_addr, limit); + return true; + } + + void Invalidate() noexcept { + std::ranges::fill(read_descriptors, 0); + } + + [[nodiscard]] std::pair<Descriptor, bool> Read(u32 index) { + DEBUG_ASSERT(index <= current_limit); + const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(Descriptor); + std::pair<Descriptor, bool> result; + gpu_memory.ReadBlockUnsafe(gpu_addr, &result.first, sizeof(Descriptor)); + if (IsDescriptorRead(index)) { + result.second = result.first != descriptors[index]; + } else { + MarkDescriptorAsRead(index); + result.second = true; + } + if (result.second) { + descriptors[index] = result.first; + } + return result; + } + + [[nodiscard]] u32 Limit() const noexcept { + return current_limit; + } + +private: + void Refresh(GPUVAddr gpu_addr, u32 limit) { + current_gpu_addr = gpu_addr; + current_limit = limit; + + const size_t num_descriptors = static_cast<size_t>(limit) + 1; + read_descriptors.clear(); + read_descriptors.resize(Common::DivCeil(num_descriptors, 64U), 0); + descriptors.resize(num_descriptors); + } + + void MarkDescriptorAsRead(u32 index) noexcept { + read_descriptors[index / 64] |= 1ULL << (index % 64); + } + + [[nodiscard]] bool IsDescriptorRead(u32 index) const noexcept { + return (read_descriptors[index / 64] & (1ULL << (index % 64))) != 0; + } + + Tegra::MemoryManager& gpu_memory; + GPUVAddr current_gpu_addr{}; + u32 current_limit{}; + std::vector<u64> read_descriptors; + std::vector<Descriptor> descriptors; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 7d5a75648..ddfb726fe 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <array> #include "common/common_types.h" #include "common/logging/log.h" #include "video_core/texture_cache/format_lookup_table.h" @@ -20,198 +19,207 @@ constexpr auto UNORM = ComponentType::UNORM; constexpr auto SINT = ComponentType::SINT; constexpr auto UINT = ComponentType::UINT; constexpr auto FLOAT = ComponentType::FLOAT; -constexpr bool C = false; // Normal color -constexpr bool S = true; // Srgb - -struct Table { - constexpr Table(TextureFormat texture_format, bool is_srgb, ComponentType red_component, - ComponentType green_component, ComponentType blue_component, - ComponentType alpha_component, PixelFormat pixel_format) - : texture_format{texture_format}, pixel_format{pixel_format}, red_component{red_component}, - green_component{green_component}, blue_component{blue_component}, - alpha_component{alpha_component}, is_srgb{is_srgb} {} - - TextureFormat texture_format; - PixelFormat pixel_format; - ComponentType red_component; - ComponentType green_component; - ComponentType blue_component; - ComponentType alpha_component; - bool is_srgb; -}; -constexpr std::array<Table, 86> DefinitionTable = {{ - {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_UNORM}, - {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::A8B8G8R8_SNORM}, - {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::A8B8G8R8_UINT}, - {TextureFormat::A8R8G8B8, C, SINT, SINT, SINT, SINT, PixelFormat::A8B8G8R8_SINT}, - {TextureFormat::A8R8G8B8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_SRGB}, - - {TextureFormat::B5G6R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::B5G6R5_UNORM}, - - {TextureFormat::A2B10G10R10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A2B10G10R10_UNORM}, - {TextureFormat::A2B10G10R10, C, UINT, UINT, UINT, UINT, PixelFormat::A2B10G10R10_UINT}, - - {TextureFormat::A1B5G5R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A1B5G5R5_UNORM}, - - {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A4B4G4R4_UNORM}, - - {TextureFormat::R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8_UNORM}, - {TextureFormat::R8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8_SNORM}, - {TextureFormat::R8, C, UINT, UINT, UINT, UINT, PixelFormat::R8_UINT}, - {TextureFormat::R8, C, SINT, SINT, SINT, SINT, PixelFormat::R8_SINT}, - - {TextureFormat::R8G8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8G8_UNORM}, - {TextureFormat::R8G8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8G8_SNORM}, - {TextureFormat::R8G8, C, UINT, UINT, UINT, UINT, PixelFormat::R8G8_UINT}, - {TextureFormat::R8G8, C, SINT, SINT, SINT, SINT, PixelFormat::R8G8_SINT}, - - {TextureFormat::R16G16B16A16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16B16A16_SNORM}, - {TextureFormat::R16G16B16A16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16B16A16_UNORM}, - {TextureFormat::R16G16B16A16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16B16A16_FLOAT}, - {TextureFormat::R16G16B16A16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16B16A16_UINT}, - {TextureFormat::R16G16B16A16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16B16A16_SINT}, - - {TextureFormat::R16G16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16_FLOAT}, - {TextureFormat::R16G16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16_UNORM}, - {TextureFormat::R16G16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16_SNORM}, - {TextureFormat::R16G16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16_UINT}, - {TextureFormat::R16G16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16_SINT}, - - {TextureFormat::R16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16_FLOAT}, - {TextureFormat::R16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16_UNORM}, - {TextureFormat::R16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16_SNORM}, - {TextureFormat::R16, C, UINT, UINT, UINT, UINT, PixelFormat::R16_UINT}, - {TextureFormat::R16, C, SINT, SINT, SINT, SINT, PixelFormat::R16_SINT}, - - {TextureFormat::B10G11R11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::B10G11R11_FLOAT}, - - {TextureFormat::R32G32B32A32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32A32_FLOAT}, - {TextureFormat::R32G32B32A32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32B32A32_UINT}, - {TextureFormat::R32G32B32A32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32B32A32_SINT}, - - {TextureFormat::R32G32B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32_FLOAT}, - - {TextureFormat::R32G32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32_FLOAT}, - {TextureFormat::R32G32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32_UINT}, - {TextureFormat::R32G32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32_SINT}, - - {TextureFormat::R32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32_FLOAT}, - {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32_UINT}, - {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32_SINT}, - - {TextureFormat::E5B9G9R9, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9_FLOAT}, - - {TextureFormat::D32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::D32_FLOAT}, - {TextureFormat::D16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::D16_UNORM}, - {TextureFormat::S8D24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM}, - {TextureFormat::R8G24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM}, - {TextureFormat::D32S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::D32_FLOAT_S8_UINT}, - - {TextureFormat::BC1_RGBA, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_UNORM}, - {TextureFormat::BC1_RGBA, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_SRGB}, - - {TextureFormat::BC2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_UNORM}, - {TextureFormat::BC2, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_SRGB}, - - {TextureFormat::BC3, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_UNORM}, - {TextureFormat::BC3, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_SRGB}, - - {TextureFormat::BC4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC4_UNORM}, - {TextureFormat::BC4, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC4_SNORM}, - - {TextureFormat::BC5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC5_UNORM}, - {TextureFormat::BC5, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC5_SNORM}, - - {TextureFormat::BC7, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_UNORM}, - {TextureFormat::BC7, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_SRGB}, - - {TextureFormat::BC6H_SFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_SFLOAT}, - {TextureFormat::BC6H_UFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_UFLOAT}, - - {TextureFormat::ASTC_2D_4X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_UNORM}, - {TextureFormat::ASTC_2D_4X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_SRGB}, - - {TextureFormat::ASTC_2D_5X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_UNORM}, - {TextureFormat::ASTC_2D_5X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_SRGB}, - - {TextureFormat::ASTC_2D_5X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_UNORM}, - {TextureFormat::ASTC_2D_5X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_SRGB}, - - {TextureFormat::ASTC_2D_8X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_UNORM}, - {TextureFormat::ASTC_2D_8X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_SRGB}, - - {TextureFormat::ASTC_2D_8X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_UNORM}, - {TextureFormat::ASTC_2D_8X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_SRGB}, - - {TextureFormat::ASTC_2D_10X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_UNORM}, - {TextureFormat::ASTC_2D_10X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_SRGB}, - - {TextureFormat::ASTC_2D_6X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_UNORM}, - {TextureFormat::ASTC_2D_6X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_SRGB}, - - {TextureFormat::ASTC_2D_10X10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_UNORM}, - {TextureFormat::ASTC_2D_10X10, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_SRGB}, - - {TextureFormat::ASTC_2D_12X12, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_UNORM}, - {TextureFormat::ASTC_2D_12X12, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_SRGB}, - - {TextureFormat::ASTC_2D_8X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_UNORM}, - {TextureFormat::ASTC_2D_8X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_SRGB}, +constexpr bool LINEAR = false; +constexpr bool SRGB = true; + +constexpr u32 Hash(TextureFormat format, ComponentType red_component, ComponentType green_component, + ComponentType blue_component, ComponentType alpha_component, bool is_srgb) { + u32 hash = is_srgb ? 1 : 0; + hash |= static_cast<u32>(red_component) << 1; + hash |= static_cast<u32>(green_component) << 4; + hash |= static_cast<u32>(blue_component) << 7; + hash |= static_cast<u32>(alpha_component) << 10; + hash |= static_cast<u32>(format) << 13; + return hash; +} - {TextureFormat::ASTC_2D_6X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_UNORM}, - {TextureFormat::ASTC_2D_6X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_SRGB}, -}}; +constexpr u32 Hash(TextureFormat format, ComponentType component, bool is_srgb = LINEAR) { + return Hash(format, component, component, component, component, is_srgb); +} } // Anonymous namespace -FormatLookupTable::FormatLookupTable() { - table.fill(static_cast<u8>(PixelFormat::Invalid)); - - for (const auto& entry : DefinitionTable) { - table[CalculateIndex(entry.texture_format, entry.is_srgb != 0, entry.red_component, - entry.green_component, entry.blue_component, entry.alpha_component)] = - static_cast<u8>(entry.pixel_format); - } -} - -PixelFormat FormatLookupTable::GetPixelFormat(TextureFormat format, bool is_srgb, - ComponentType red_component, - ComponentType green_component, - ComponentType blue_component, - ComponentType alpha_component) const noexcept { - const auto pixel_format = static_cast<PixelFormat>(table[CalculateIndex( - format, is_srgb, red_component, green_component, blue_component, alpha_component)]); - // [[likely]] - if (pixel_format != PixelFormat::Invalid) { - return pixel_format; +PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, ComponentType green, + ComponentType blue, ComponentType alpha, + bool is_srgb) noexcept { + switch (Hash(format, red, green, blue, alpha, is_srgb)) { + case Hash(TextureFormat::A8R8G8B8, UNORM): + return PixelFormat::A8B8G8R8_UNORM; + case Hash(TextureFormat::A8R8G8B8, SNORM): + return PixelFormat::A8B8G8R8_SNORM; + case Hash(TextureFormat::A8R8G8B8, UINT): + return PixelFormat::A8B8G8R8_UINT; + case Hash(TextureFormat::A8R8G8B8, SINT): + return PixelFormat::A8B8G8R8_SINT; + case Hash(TextureFormat::A8R8G8B8, UNORM, SRGB): + return PixelFormat::A8B8G8R8_SRGB; + case Hash(TextureFormat::B5G6R5, UNORM): + return PixelFormat::B5G6R5_UNORM; + case Hash(TextureFormat::A2B10G10R10, UNORM): + return PixelFormat::A2B10G10R10_UNORM; + case Hash(TextureFormat::A2B10G10R10, UINT): + return PixelFormat::A2B10G10R10_UINT; + case Hash(TextureFormat::A1B5G5R5, UNORM): + return PixelFormat::A1B5G5R5_UNORM; + case Hash(TextureFormat::A4B4G4R4, UNORM): + return PixelFormat::A4B4G4R4_UNORM; + case Hash(TextureFormat::R8, UNORM): + return PixelFormat::R8_UNORM; + case Hash(TextureFormat::R8, SNORM): + return PixelFormat::R8_SNORM; + case Hash(TextureFormat::R8, UINT): + return PixelFormat::R8_UINT; + case Hash(TextureFormat::R8, SINT): + return PixelFormat::R8_SINT; + case Hash(TextureFormat::R8G8, UNORM): + return PixelFormat::R8G8_UNORM; + case Hash(TextureFormat::R8G8, SNORM): + return PixelFormat::R8G8_SNORM; + case Hash(TextureFormat::R8G8, UINT): + return PixelFormat::R8G8_UINT; + case Hash(TextureFormat::R8G8, SINT): + return PixelFormat::R8G8_SINT; + case Hash(TextureFormat::R16G16B16A16, FLOAT): + return PixelFormat::R16G16B16A16_FLOAT; + case Hash(TextureFormat::R16G16B16A16, UNORM): + return PixelFormat::R16G16B16A16_UNORM; + case Hash(TextureFormat::R16G16B16A16, SNORM): + return PixelFormat::R16G16B16A16_SNORM; + case Hash(TextureFormat::R16G16B16A16, UINT): + return PixelFormat::R16G16B16A16_UINT; + case Hash(TextureFormat::R16G16B16A16, SINT): + return PixelFormat::R16G16B16A16_SINT; + case Hash(TextureFormat::R16G16, FLOAT): + return PixelFormat::R16G16_FLOAT; + case Hash(TextureFormat::R16G16, UNORM): + return PixelFormat::R16G16_UNORM; + case Hash(TextureFormat::R16G16, SNORM): + return PixelFormat::R16G16_SNORM; + case Hash(TextureFormat::R16G16, UINT): + return PixelFormat::R16G16_UINT; + case Hash(TextureFormat::R16G16, SINT): + return PixelFormat::R16G16_SINT; + case Hash(TextureFormat::R16, FLOAT): + return PixelFormat::R16_FLOAT; + case Hash(TextureFormat::R16, UNORM): + return PixelFormat::R16_UNORM; + case Hash(TextureFormat::R16, SNORM): + return PixelFormat::R16_SNORM; + case Hash(TextureFormat::R16, UINT): + return PixelFormat::R16_UINT; + case Hash(TextureFormat::R16, SINT): + return PixelFormat::R16_SINT; + case Hash(TextureFormat::B10G11R11, FLOAT): + return PixelFormat::B10G11R11_FLOAT; + case Hash(TextureFormat::R32G32B32A32, FLOAT): + return PixelFormat::R32G32B32A32_FLOAT; + case Hash(TextureFormat::R32G32B32A32, UINT): + return PixelFormat::R32G32B32A32_UINT; + case Hash(TextureFormat::R32G32B32A32, SINT): + return PixelFormat::R32G32B32A32_SINT; + case Hash(TextureFormat::R32G32B32, FLOAT): + return PixelFormat::R32G32B32_FLOAT; + case Hash(TextureFormat::R32G32, FLOAT): + return PixelFormat::R32G32_FLOAT; + case Hash(TextureFormat::R32G32, UINT): + return PixelFormat::R32G32_UINT; + case Hash(TextureFormat::R32G32, SINT): + return PixelFormat::R32G32_SINT; + case Hash(TextureFormat::R32, FLOAT): + return PixelFormat::R32_FLOAT; + case Hash(TextureFormat::R32, UINT): + return PixelFormat::R32_UINT; + case Hash(TextureFormat::R32, SINT): + return PixelFormat::R32_SINT; + case Hash(TextureFormat::E5B9G9R9, FLOAT): + return PixelFormat::E5B9G9R9_FLOAT; + case Hash(TextureFormat::D32, FLOAT): + return PixelFormat::D32_FLOAT; + case Hash(TextureFormat::D16, UNORM): + return PixelFormat::D16_UNORM; + case Hash(TextureFormat::S8D24, UINT, UNORM, UNORM, UNORM, LINEAR): + return PixelFormat::S8_UINT_D24_UNORM; + case Hash(TextureFormat::R8G24, UINT, UNORM, UNORM, UNORM, LINEAR): + return PixelFormat::S8_UINT_D24_UNORM; + case Hash(TextureFormat::D32S8, FLOAT, UINT, UNORM, UNORM, LINEAR): + return PixelFormat::D32_FLOAT_S8_UINT; + case Hash(TextureFormat::BC1_RGBA, UNORM, LINEAR): + return PixelFormat::BC1_RGBA_UNORM; + case Hash(TextureFormat::BC1_RGBA, UNORM, SRGB): + return PixelFormat::BC1_RGBA_SRGB; + case Hash(TextureFormat::BC2, UNORM, LINEAR): + return PixelFormat::BC2_UNORM; + case Hash(TextureFormat::BC2, UNORM, SRGB): + return PixelFormat::BC2_SRGB; + case Hash(TextureFormat::BC3, UNORM, LINEAR): + return PixelFormat::BC3_UNORM; + case Hash(TextureFormat::BC3, UNORM, SRGB): + return PixelFormat::BC3_SRGB; + case Hash(TextureFormat::BC4, UNORM): + return PixelFormat::BC4_UNORM; + case Hash(TextureFormat::BC4, SNORM): + return PixelFormat::BC4_SNORM; + case Hash(TextureFormat::BC5, UNORM): + return PixelFormat::BC5_UNORM; + case Hash(TextureFormat::BC5, SNORM): + return PixelFormat::BC5_SNORM; + case Hash(TextureFormat::BC7, UNORM, LINEAR): + return PixelFormat::BC7_UNORM; + case Hash(TextureFormat::BC7, UNORM, SRGB): + return PixelFormat::BC7_SRGB; + case Hash(TextureFormat::BC6H_SFLOAT, FLOAT): + return PixelFormat::BC6H_SFLOAT; + case Hash(TextureFormat::BC6H_UFLOAT, FLOAT): + return PixelFormat::BC6H_UFLOAT; + case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR): + return PixelFormat::ASTC_2D_4X4_UNORM; + case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB): + return PixelFormat::ASTC_2D_4X4_SRGB; + case Hash(TextureFormat::ASTC_2D_5X4, UNORM, LINEAR): + return PixelFormat::ASTC_2D_5X4_UNORM; + case Hash(TextureFormat::ASTC_2D_5X4, UNORM, SRGB): + return PixelFormat::ASTC_2D_5X4_SRGB; + case Hash(TextureFormat::ASTC_2D_5X5, UNORM, LINEAR): + return PixelFormat::ASTC_2D_5X5_UNORM; + case Hash(TextureFormat::ASTC_2D_5X5, UNORM, SRGB): + return PixelFormat::ASTC_2D_5X5_SRGB; + case Hash(TextureFormat::ASTC_2D_8X8, UNORM, LINEAR): + return PixelFormat::ASTC_2D_8X8_UNORM; + case Hash(TextureFormat::ASTC_2D_8X8, UNORM, SRGB): + return PixelFormat::ASTC_2D_8X8_SRGB; + case Hash(TextureFormat::ASTC_2D_8X5, UNORM, LINEAR): + return PixelFormat::ASTC_2D_8X5_UNORM; + case Hash(TextureFormat::ASTC_2D_8X5, UNORM, SRGB): + return PixelFormat::ASTC_2D_8X5_SRGB; + case Hash(TextureFormat::ASTC_2D_10X8, UNORM, LINEAR): + return PixelFormat::ASTC_2D_10X8_UNORM; + case Hash(TextureFormat::ASTC_2D_10X8, UNORM, SRGB): + return PixelFormat::ASTC_2D_10X8_SRGB; + case Hash(TextureFormat::ASTC_2D_6X6, UNORM, LINEAR): + return PixelFormat::ASTC_2D_6X6_UNORM; + case Hash(TextureFormat::ASTC_2D_6X6, UNORM, SRGB): + return PixelFormat::ASTC_2D_6X6_SRGB; + case Hash(TextureFormat::ASTC_2D_10X10, UNORM, LINEAR): + return PixelFormat::ASTC_2D_10X10_UNORM; + case Hash(TextureFormat::ASTC_2D_10X10, UNORM, SRGB): + return PixelFormat::ASTC_2D_10X10_SRGB; + case Hash(TextureFormat::ASTC_2D_12X12, UNORM, LINEAR): + return PixelFormat::ASTC_2D_12X12_UNORM; + case Hash(TextureFormat::ASTC_2D_12X12, UNORM, SRGB): + return PixelFormat::ASTC_2D_12X12_SRGB; + case Hash(TextureFormat::ASTC_2D_8X6, UNORM, LINEAR): + return PixelFormat::ASTC_2D_8X6_UNORM; + case Hash(TextureFormat::ASTC_2D_8X6, UNORM, SRGB): + return PixelFormat::ASTC_2D_8X6_SRGB; + case Hash(TextureFormat::ASTC_2D_6X5, UNORM, LINEAR): + return PixelFormat::ASTC_2D_6X5_UNORM; + case Hash(TextureFormat::ASTC_2D_6X5, UNORM, SRGB): + return PixelFormat::ASTC_2D_6X5_SRGB; } UNIMPLEMENTED_MSG("texture format={} srgb={} components={{{} {} {} {}}}", - static_cast<int>(format), is_srgb, static_cast<int>(red_component), - static_cast<int>(green_component), static_cast<int>(blue_component), - static_cast<int>(alpha_component)); + static_cast<int>(format), is_srgb, static_cast<int>(red), + static_cast<int>(green), static_cast<int>(blue), static_cast<int>(alpha)); return PixelFormat::A8B8G8R8_UNORM; } -void FormatLookupTable::Set(TextureFormat format, bool is_srgb, ComponentType red_component, - ComponentType green_component, ComponentType blue_component, - ComponentType alpha_component, PixelFormat pixel_format) {} - -std::size_t FormatLookupTable::CalculateIndex(TextureFormat format, bool is_srgb, - ComponentType red_component, - ComponentType green_component, - ComponentType blue_component, - ComponentType alpha_component) noexcept { - const auto format_index = static_cast<std::size_t>(format); - const auto red_index = static_cast<std::size_t>(red_component); - const auto green_index = static_cast<std::size_t>(green_component); - const auto blue_index = static_cast<std::size_t>(blue_component); - const auto alpha_index = static_cast<std::size_t>(alpha_component); - const std::size_t srgb_index = is_srgb ? 1 : 0; - - return format_index * PerFormat + - srgb_index * PerComponent * PerComponent * PerComponent * PerComponent + - alpha_index * PerComponent * PerComponent * PerComponent + - blue_index * PerComponent * PerComponent + green_index * PerComponent + red_index; -} - } // namespace VideoCommon diff --git a/src/video_core/texture_cache/format_lookup_table.h b/src/video_core/texture_cache/format_lookup_table.h index aa77e0a5a..729533999 100644 --- a/src/video_core/texture_cache/format_lookup_table.h +++ b/src/video_core/texture_cache/format_lookup_table.h @@ -4,48 +4,14 @@ #pragma once -#include <array> -#include <limits> #include "video_core/surface.h" #include "video_core/textures/texture.h" namespace VideoCommon { -class FormatLookupTable { -public: - explicit FormatLookupTable(); - - VideoCore::Surface::PixelFormat GetPixelFormat( - Tegra::Texture::TextureFormat format, bool is_srgb, - Tegra::Texture::ComponentType red_component, Tegra::Texture::ComponentType green_component, - Tegra::Texture::ComponentType blue_component, - Tegra::Texture::ComponentType alpha_component) const noexcept; - -private: - static_assert(VideoCore::Surface::MaxPixelFormat <= std::numeric_limits<u8>::max()); - - static constexpr std::size_t NumTextureFormats = 128; - - static constexpr std::size_t PerComponent = 8; - static constexpr std::size_t PerComponents2 = PerComponent * PerComponent; - static constexpr std::size_t PerComponents3 = PerComponents2 * PerComponent; - static constexpr std::size_t PerComponents4 = PerComponents3 * PerComponent; - static constexpr std::size_t PerFormat = PerComponents4 * 2; - - static std::size_t CalculateIndex(Tegra::Texture::TextureFormat format, bool is_srgb, - Tegra::Texture::ComponentType red_component, - Tegra::Texture::ComponentType green_component, - Tegra::Texture::ComponentType blue_component, - Tegra::Texture::ComponentType alpha_component) noexcept; - - void Set(Tegra::Texture::TextureFormat format, bool is_srgb, - Tegra::Texture::ComponentType red_component, - Tegra::Texture::ComponentType green_component, - Tegra::Texture::ComponentType blue_component, - Tegra::Texture::ComponentType alpha_component, - VideoCore::Surface::PixelFormat pixel_format); - - std::array<u8, NumTextureFormats * PerFormat> table; -}; +VideoCore::Surface::PixelFormat PixelFormatFromTextureInfo( + Tegra::Texture::TextureFormat format, Tegra::Texture::ComponentType red_component, + Tegra::Texture::ComponentType green_component, Tegra::Texture::ComponentType blue_component, + Tegra::Texture::ComponentType alpha_component, bool is_srgb) noexcept; } // namespace VideoCommon diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp new file mode 100644 index 000000000..d10ba4ccd --- /dev/null +++ b/src/video_core/texture_cache/formatter.cpp @@ -0,0 +1,95 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <string> + +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/image_base.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/image_view_base.h" +#include "video_core/texture_cache/render_targets.h" + +namespace VideoCommon { + +std::string Name(const ImageBase& image) { + const GPUVAddr gpu_addr = image.gpu_addr; + const ImageInfo& info = image.info; + const u32 width = info.size.width; + const u32 height = info.size.height; + const u32 depth = info.size.depth; + const u32 num_layers = image.info.resources.layers; + const u32 num_levels = image.info.resources.levels; + std::string resource; + if (num_layers > 1) { + resource += fmt::format(":L{}", num_layers); + } + if (num_levels > 1) { + resource += fmt::format(":M{}", num_levels); + } + switch (image.info.type) { + case ImageType::e1D: + return fmt::format("Image 1D 0x{:x} {}{}", gpu_addr, width, resource); + case ImageType::e2D: + return fmt::format("Image 2D 0x{:x} {}x{}{}", gpu_addr, width, height, resource); + case ImageType::e3D: + return fmt::format("Image 2D 0x{:x} {}x{}x{}{}", gpu_addr, width, height, depth, resource); + case ImageType::Linear: + return fmt::format("Image Linear 0x{:x} {}x{}", gpu_addr, width, height); + case ImageType::Buffer: + return fmt::format("Buffer 0x{:x} {}", image.gpu_addr, image.info.size.width); + } + return "Invalid"; +} + +std::string Name(const ImageViewBase& image_view, std::optional<ImageViewType> type) { + const u32 width = image_view.size.width; + const u32 height = image_view.size.height; + const u32 depth = image_view.size.depth; + const u32 num_levels = image_view.range.extent.levels; + const u32 num_layers = image_view.range.extent.layers; + + const std::string level = num_levels > 1 ? fmt::format(":{}", num_levels) : ""; + switch (type.value_or(image_view.type)) { + case ImageViewType::e1D: + return fmt::format("ImageView 1D {}{}", width, level); + case ImageViewType::e2D: + return fmt::format("ImageView 2D {}x{}{}", width, height, level); + case ImageViewType::Cube: + return fmt::format("ImageView Cube {}x{}{}", width, height, level); + case ImageViewType::e3D: + return fmt::format("ImageView 3D {}x{}x{}{}", width, height, depth, level); + case ImageViewType::e1DArray: + return fmt::format("ImageView 1DArray {}{}|{}", width, level, num_layers); + case ImageViewType::e2DArray: + return fmt::format("ImageView 2DArray {}x{}{}|{}", width, height, level, num_layers); + case ImageViewType::CubeArray: + return fmt::format("ImageView CubeArray {}x{}{}|{}", width, height, level, num_layers); + case ImageViewType::Rect: + return fmt::format("ImageView Rect {}x{}{}", width, height, level); + case ImageViewType::Buffer: + return fmt::format("BufferView {}", width); + } + return "Invalid"; +} + +std::string Name(const RenderTargets& render_targets) { + std::string_view debug_prefix; + const auto num_color = std::ranges::count_if( + render_targets.color_buffer_ids, [](ImageViewId id) { return static_cast<bool>(id); }); + if (render_targets.depth_buffer_id) { + debug_prefix = num_color > 0 ? "R" : "Z"; + } else { + debug_prefix = num_color > 0 ? "C" : "X"; + } + const Extent2D size = render_targets.size; + if (num_color > 0) { + return fmt::format("Framebuffer {}{} {}x{}", debug_prefix, num_color, size.width, + size.height); + } else { + return fmt::format("Framebuffer {} {}x{}", debug_prefix, size.width, size.height); + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h new file mode 100644 index 000000000..a48413983 --- /dev/null +++ b/src/video_core/texture_cache/formatter.h @@ -0,0 +1,263 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +#include <fmt/format.h> + +#include "video_core/surface.h" +#include "video_core/texture_cache/types.h" + +template <> +struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::string_view> { + template <typename FormatContext> + auto format(VideoCore::Surface::PixelFormat format, FormatContext& ctx) { + using VideoCore::Surface::PixelFormat; + const string_view name = [format] { + switch (format) { + case PixelFormat::A8B8G8R8_UNORM: + return "A8B8G8R8_UNORM"; + case PixelFormat::A8B8G8R8_SNORM: + return "A8B8G8R8_SNORM"; + case PixelFormat::A8B8G8R8_SINT: + return "A8B8G8R8_SINT"; + case PixelFormat::A8B8G8R8_UINT: + return "A8B8G8R8_UINT"; + case PixelFormat::R5G6B5_UNORM: + return "R5G6B5_UNORM"; + case PixelFormat::B5G6R5_UNORM: + return "B5G6R5_UNORM"; + case PixelFormat::A1R5G5B5_UNORM: + return "A1R5G5B5_UNORM"; + case PixelFormat::A2B10G10R10_UNORM: + return "A2B10G10R10_UNORM"; + case PixelFormat::A2B10G10R10_UINT: + return "A2B10G10R10_UINT"; + case PixelFormat::A1B5G5R5_UNORM: + return "A1B5G5R5_UNORM"; + case PixelFormat::R8_UNORM: + return "R8_UNORM"; + case PixelFormat::R8_SNORM: + return "R8_SNORM"; + case PixelFormat::R8_SINT: + return "R8_SINT"; + case PixelFormat::R8_UINT: + return "R8_UINT"; + case PixelFormat::R16G16B16A16_FLOAT: + return "R16G16B16A16_FLOAT"; + case PixelFormat::R16G16B16A16_UNORM: + return "R16G16B16A16_UNORM"; + case PixelFormat::R16G16B16A16_SNORM: + return "R16G16B16A16_SNORM"; + case PixelFormat::R16G16B16A16_SINT: + return "R16G16B16A16_SINT"; + case PixelFormat::R16G16B16A16_UINT: + return "R16G16B16A16_UINT"; + case PixelFormat::B10G11R11_FLOAT: + return "B10G11R11_FLOAT"; + case PixelFormat::R32G32B32A32_UINT: + return "R32G32B32A32_UINT"; + case PixelFormat::BC1_RGBA_UNORM: + return "BC1_RGBA_UNORM"; + case PixelFormat::BC2_UNORM: + return "BC2_UNORM"; + case PixelFormat::BC3_UNORM: + return "BC3_UNORM"; + case PixelFormat::BC4_UNORM: + return "BC4_UNORM"; + case PixelFormat::BC4_SNORM: + return "BC4_SNORM"; + case PixelFormat::BC5_UNORM: + return "BC5_UNORM"; + case PixelFormat::BC5_SNORM: + return "BC5_SNORM"; + case PixelFormat::BC7_UNORM: + return "BC7_UNORM"; + case PixelFormat::BC6H_UFLOAT: + return "BC6H_UFLOAT"; + case PixelFormat::BC6H_SFLOAT: + return "BC6H_SFLOAT"; + case PixelFormat::ASTC_2D_4X4_UNORM: + return "ASTC_2D_4X4_UNORM"; + case PixelFormat::B8G8R8A8_UNORM: + return "B8G8R8A8_UNORM"; + case PixelFormat::R32G32B32A32_FLOAT: + return "R32G32B32A32_FLOAT"; + case PixelFormat::R32G32B32A32_SINT: + return "R32G32B32A32_SINT"; + case PixelFormat::R32G32_FLOAT: + return "R32G32_FLOAT"; + case PixelFormat::R32G32_SINT: + return "R32G32_SINT"; + case PixelFormat::R32_FLOAT: + return "R32_FLOAT"; + case PixelFormat::R16_FLOAT: + return "R16_FLOAT"; + case PixelFormat::R16_UNORM: + return "R16_UNORM"; + case PixelFormat::R16_SNORM: + return "R16_SNORM"; + case PixelFormat::R16_UINT: + return "R16_UINT"; + case PixelFormat::R16_SINT: + return "R16_SINT"; + case PixelFormat::R16G16_UNORM: + return "R16G16_UNORM"; + case PixelFormat::R16G16_FLOAT: + return "R16G16_FLOAT"; + case PixelFormat::R16G16_UINT: + return "R16G16_UINT"; + case PixelFormat::R16G16_SINT: + return "R16G16_SINT"; + case PixelFormat::R16G16_SNORM: + return "R16G16_SNORM"; + case PixelFormat::R32G32B32_FLOAT: + return "R32G32B32_FLOAT"; + case PixelFormat::A8B8G8R8_SRGB: + return "A8B8G8R8_SRGB"; + case PixelFormat::R8G8_UNORM: + return "R8G8_UNORM"; + case PixelFormat::R8G8_SNORM: + return "R8G8_SNORM"; + case PixelFormat::R8G8_SINT: + return "R8G8_SINT"; + case PixelFormat::R8G8_UINT: + return "R8G8_UINT"; + case PixelFormat::R32G32_UINT: + return "R32G32_UINT"; + case PixelFormat::R16G16B16X16_FLOAT: + return "R16G16B16X16_FLOAT"; + case PixelFormat::R32_UINT: + return "R32_UINT"; + case PixelFormat::R32_SINT: + return "R32_SINT"; + case PixelFormat::ASTC_2D_8X8_UNORM: + return "ASTC_2D_8X8_UNORM"; + case PixelFormat::ASTC_2D_8X5_UNORM: + return "ASTC_2D_8X5_UNORM"; + case PixelFormat::ASTC_2D_5X4_UNORM: + return "ASTC_2D_5X4_UNORM"; + case PixelFormat::B8G8R8A8_SRGB: + return "B8G8R8A8_SRGB"; + case PixelFormat::BC1_RGBA_SRGB: + return "BC1_RGBA_SRGB"; + case PixelFormat::BC2_SRGB: + return "BC2_SRGB"; + case PixelFormat::BC3_SRGB: + return "BC3_SRGB"; + case PixelFormat::BC7_SRGB: + return "BC7_SRGB"; + case PixelFormat::A4B4G4R4_UNORM: + return "A4B4G4R4_UNORM"; + case PixelFormat::ASTC_2D_4X4_SRGB: + return "ASTC_2D_4X4_SRGB"; + case PixelFormat::ASTC_2D_8X8_SRGB: + return "ASTC_2D_8X8_SRGB"; + case PixelFormat::ASTC_2D_8X5_SRGB: + return "ASTC_2D_8X5_SRGB"; + case PixelFormat::ASTC_2D_5X4_SRGB: + return "ASTC_2D_5X4_SRGB"; + case PixelFormat::ASTC_2D_5X5_UNORM: + return "ASTC_2D_5X5_UNORM"; + case PixelFormat::ASTC_2D_5X5_SRGB: + return "ASTC_2D_5X5_SRGB"; + case PixelFormat::ASTC_2D_10X8_UNORM: + return "ASTC_2D_10X8_UNORM"; + case PixelFormat::ASTC_2D_10X8_SRGB: + return "ASTC_2D_10X8_SRGB"; + case PixelFormat::ASTC_2D_6X6_UNORM: + return "ASTC_2D_6X6_UNORM"; + case PixelFormat::ASTC_2D_6X6_SRGB: + return "ASTC_2D_6X6_SRGB"; + case PixelFormat::ASTC_2D_10X10_UNORM: + return "ASTC_2D_10X10_UNORM"; + case PixelFormat::ASTC_2D_10X10_SRGB: + return "ASTC_2D_10X10_SRGB"; + case PixelFormat::ASTC_2D_12X12_UNORM: + return "ASTC_2D_12X12_UNORM"; + case PixelFormat::ASTC_2D_12X12_SRGB: + return "ASTC_2D_12X12_SRGB"; + case PixelFormat::ASTC_2D_8X6_UNORM: + return "ASTC_2D_8X6_UNORM"; + case PixelFormat::ASTC_2D_8X6_SRGB: + return "ASTC_2D_8X6_SRGB"; + case PixelFormat::ASTC_2D_6X5_UNORM: + return "ASTC_2D_6X5_UNORM"; + case PixelFormat::ASTC_2D_6X5_SRGB: + return "ASTC_2D_6X5_SRGB"; + case PixelFormat::E5B9G9R9_FLOAT: + return "E5B9G9R9_FLOAT"; + case PixelFormat::D32_FLOAT: + return "D32_FLOAT"; + case PixelFormat::D16_UNORM: + return "D16_UNORM"; + case PixelFormat::D24_UNORM_S8_UINT: + return "D24_UNORM_S8_UINT"; + case PixelFormat::S8_UINT_D24_UNORM: + return "S8_UINT_D24_UNORM"; + case PixelFormat::D32_FLOAT_S8_UINT: + return "D32_FLOAT_S8_UINT"; + case PixelFormat::MaxDepthStencilFormat: + case PixelFormat::Invalid: + return "Invalid"; + } + return "Invalid"; + }(); + return formatter<string_view>::format(name, ctx); + } +}; + +template <> +struct fmt::formatter<VideoCommon::ImageType> : fmt::formatter<fmt::string_view> { + template <typename FormatContext> + auto format(VideoCommon::ImageType type, FormatContext& ctx) { + const string_view name = [type] { + using VideoCommon::ImageType; + switch (type) { + case ImageType::e1D: + return "1D"; + case ImageType::e2D: + return "2D"; + case ImageType::e3D: + return "3D"; + case ImageType::Linear: + return "Linear"; + case ImageType::Buffer: + return "Buffer"; + } + return "Invalid"; + }(); + return formatter<string_view>::format(name, ctx); + } +}; + +template <> +struct fmt::formatter<VideoCommon::Extent3D> { + constexpr auto parse(fmt::format_parse_context& ctx) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const VideoCommon::Extent3D& extent, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{{{}, {}, {}}}", extent.width, extent.height, + extent.depth); + } +}; + +namespace VideoCommon { + +struct ImageBase; +struct ImageViewBase; +struct RenderTargets; + +[[nodiscard]] std::string Name(const ImageBase& image); + +[[nodiscard]] std::string Name(const ImageViewBase& image_view, + std::optional<ImageViewType> type = std::nullopt); + +[[nodiscard]] std::string Name(const RenderTargets& render_targets); + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_base.cpp b/src/video_core/texture_cache/image_base.cpp new file mode 100644 index 000000000..959b3f115 --- /dev/null +++ b/src/video_core/texture_cache/image_base.cpp @@ -0,0 +1,218 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <optional> +#include <utility> +#include <vector> + +#include "common/common_types.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/image_base.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/util.h" + +namespace VideoCommon { + +using VideoCore::Surface::DefaultBlockHeight; +using VideoCore::Surface::DefaultBlockWidth; + +namespace { +/// Returns the base layer and mip level offset +[[nodiscard]] std::pair<s32, s32> LayerMipOffset(s32 diff, u32 layer_stride) { + if (layer_stride == 0) { + return {0, diff}; + } else { + return {diff / layer_stride, diff % layer_stride}; + } +} + +[[nodiscard]] bool ValidateLayers(const SubresourceLayers& layers, const ImageInfo& info) { + return layers.base_level < info.resources.levels && + layers.base_layer + layers.num_layers <= info.resources.layers; +} + +[[nodiscard]] bool ValidateCopy(const ImageCopy& copy, const ImageInfo& dst, const ImageInfo& src) { + const Extent3D src_size = MipSize(src.size, copy.src_subresource.base_level); + const Extent3D dst_size = MipSize(dst.size, copy.dst_subresource.base_level); + if (!ValidateLayers(copy.src_subresource, src)) { + return false; + } + if (!ValidateLayers(copy.dst_subresource, dst)) { + return false; + } + if (copy.src_offset.x + copy.extent.width > src_size.width || + copy.src_offset.y + copy.extent.height > src_size.height || + copy.src_offset.z + copy.extent.depth > src_size.depth) { + return false; + } + if (copy.dst_offset.x + copy.extent.width > dst_size.width || + copy.dst_offset.y + copy.extent.height > dst_size.height || + copy.dst_offset.z + copy.extent.depth > dst_size.depth) { + return false; + } + return true; +} +} // Anonymous namespace + +ImageBase::ImageBase(const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) + : info{info_}, guest_size_bytes{CalculateGuestSizeInBytes(info)}, + unswizzled_size_bytes{CalculateUnswizzledSizeBytes(info)}, + converted_size_bytes{CalculateConvertedSizeBytes(info)}, gpu_addr{gpu_addr_}, + cpu_addr{cpu_addr_}, cpu_addr_end{cpu_addr + guest_size_bytes}, + mip_level_offsets{CalculateMipLevelOffsets(info)} { + if (info.type == ImageType::e3D) { + slice_offsets = CalculateSliceOffsets(info); + slice_subresources = CalculateSliceSubresources(info); + } +} + +std::optional<SubresourceBase> ImageBase::TryFindBase(GPUVAddr other_addr) const noexcept { + if (other_addr < gpu_addr) { + // Subresource address can't be lower than the base + return std::nullopt; + } + const u32 diff = static_cast<u32>(other_addr - gpu_addr); + if (diff > guest_size_bytes) { + // This can happen when two CPU addresses are used for different GPU addresses + return std::nullopt; + } + if (info.type != ImageType::e3D) { + const auto [layer, mip_offset] = LayerMipOffset(diff, info.layer_stride); + const auto end = mip_level_offsets.begin() + info.resources.levels; + const auto it = std::find(mip_level_offsets.begin(), end, mip_offset); + if (layer > info.resources.layers || it == end) { + return std::nullopt; + } + return SubresourceBase{ + .level = static_cast<s32>(std::distance(mip_level_offsets.begin(), it)), + .layer = layer, + }; + } else { + // TODO: Consider using binary_search after a threshold + const auto it = std::ranges::find(slice_offsets, diff); + if (it == slice_offsets.cend()) { + return std::nullopt; + } + return slice_subresources[std::distance(slice_offsets.begin(), it)]; + } +} + +ImageViewId ImageBase::FindView(const ImageViewInfo& view_info) const noexcept { + const auto it = std::ranges::find(image_view_infos, view_info); + if (it == image_view_infos.end()) { + return ImageViewId{}; + } + return image_view_ids[std::distance(image_view_infos.begin(), it)]; +} + +void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id) { + image_view_infos.push_back(view_info); + image_view_ids.push_back(image_view_id); +} + +void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) { + static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format; + ASSERT(lhs.info.type == rhs.info.type); + std::optional<SubresourceBase> base; + if (lhs.info.type == ImageType::Linear) { + base = SubresourceBase{.level = 0, .layer = 0}; + } else { + // We are passing relaxed formats as an option, having broken views or not won't matter + static constexpr bool broken_views = false; + base = FindSubresource(rhs.info, lhs, rhs.gpu_addr, OPTIONS, broken_views); + } + if (!base) { + LOG_ERROR(HW_GPU, "Image alias should have been flipped"); + return; + } + const PixelFormat lhs_format = lhs.info.format; + const PixelFormat rhs_format = rhs.info.format; + const Extent2D lhs_block{ + .width = DefaultBlockWidth(lhs_format), + .height = DefaultBlockHeight(lhs_format), + }; + const Extent2D rhs_block{ + .width = DefaultBlockWidth(rhs_format), + .height = DefaultBlockHeight(rhs_format), + }; + const bool is_lhs_compressed = lhs_block.width > 1 || lhs_block.height > 1; + const bool is_rhs_compressed = rhs_block.width > 1 || rhs_block.height > 1; + if (is_lhs_compressed && is_rhs_compressed) { + LOG_ERROR(HW_GPU, "Compressed to compressed image aliasing is not implemented"); + return; + } + const s32 lhs_mips = lhs.info.resources.levels; + const s32 rhs_mips = rhs.info.resources.levels; + const s32 num_mips = std::min(lhs_mips - base->level, rhs_mips); + AliasedImage lhs_alias; + AliasedImage rhs_alias; + lhs_alias.id = rhs_id; + rhs_alias.id = lhs_id; + lhs_alias.copies.reserve(num_mips); + rhs_alias.copies.reserve(num_mips); + for (s32 mip_level = 0; mip_level < num_mips; ++mip_level) { + Extent3D lhs_size = MipSize(lhs.info.size, base->level + mip_level); + Extent3D rhs_size = MipSize(rhs.info.size, mip_level); + if (is_lhs_compressed) { + lhs_size.width /= lhs_block.width; + lhs_size.height /= lhs_block.height; + } + if (is_rhs_compressed) { + rhs_size.width /= rhs_block.width; + rhs_size.height /= rhs_block.height; + } + const Extent3D copy_size{ + .width = std::min(lhs_size.width, rhs_size.width), + .height = std::min(lhs_size.height, rhs_size.height), + .depth = std::min(lhs_size.depth, rhs_size.depth), + }; + if (copy_size.width == 0 || copy_size.height == 0) { + LOG_WARNING(HW_GPU, "Copy size is smaller than block size. Mip cannot be aliased."); + continue; + } + const bool is_lhs_3d = lhs.info.type == ImageType::e3D; + const bool is_rhs_3d = rhs.info.type == ImageType::e3D; + const Offset3D lhs_offset{0, 0, 0}; + const Offset3D rhs_offset{0, 0, is_rhs_3d ? base->layer : 0}; + const s32 lhs_layers = is_lhs_3d ? 1 : lhs.info.resources.layers - base->layer; + const s32 rhs_layers = is_rhs_3d ? 1 : rhs.info.resources.layers; + const s32 num_layers = std::min(lhs_layers, rhs_layers); + const SubresourceLayers lhs_subresource{ + .base_level = mip_level, + .base_layer = 0, + .num_layers = num_layers, + }; + const SubresourceLayers rhs_subresource{ + .base_level = base->level + mip_level, + .base_layer = is_rhs_3d ? 0 : base->layer, + .num_layers = num_layers, + }; + [[maybe_unused]] const ImageCopy& to_lhs_copy = lhs_alias.copies.emplace_back(ImageCopy{ + .src_subresource = lhs_subresource, + .dst_subresource = rhs_subresource, + .src_offset = lhs_offset, + .dst_offset = rhs_offset, + .extent = copy_size, + }); + [[maybe_unused]] const ImageCopy& to_rhs_copy = rhs_alias.copies.emplace_back(ImageCopy{ + .src_subresource = rhs_subresource, + .dst_subresource = lhs_subresource, + .src_offset = rhs_offset, + .dst_offset = lhs_offset, + .extent = copy_size, + }); + ASSERT_MSG(ValidateCopy(to_lhs_copy, lhs.info, rhs.info), "Invalid RHS to LHS copy"); + ASSERT_MSG(ValidateCopy(to_rhs_copy, rhs.info, lhs.info), "Invalid LHS to RHS copy"); + } + ASSERT(lhs_alias.copies.empty() == rhs_alias.copies.empty()); + if (lhs_alias.copies.empty()) { + return; + } + lhs.aliased_images.push_back(std::move(lhs_alias)); + rhs.aliased_images.push_back(std::move(rhs_alias)); +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h new file mode 100644 index 000000000..b7f3b7e43 --- /dev/null +++ b/src/video_core/texture_cache/image_base.h @@ -0,0 +1,83 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <optional> +#include <vector> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +enum class ImageFlagBits : u32 { + AcceleratedUpload = 1 << 0, ///< Upload can be accelerated in the GPU + Converted = 1 << 1, ///< Guest format is not supported natively and it has to be converted + CpuModified = 1 << 2, ///< Contents have been modified from the CPU + GpuModified = 1 << 3, ///< Contents have been modified from the GPU + Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU JIT + Strong = 1 << 5, ///< Exists in the image table, the dimensions are can be trusted + Registered = 1 << 6, ///< True when the image is registered + Picked = 1 << 7, ///< Temporary flag to mark the image as picked +}; +DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) + +struct ImageViewInfo; + +struct AliasedImage { + std::vector<ImageCopy> copies; + ImageId id; +}; + +struct ImageBase { + explicit ImageBase(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); + + [[nodiscard]] std::optional<SubresourceBase> TryFindBase(GPUVAddr other_addr) const noexcept; + + [[nodiscard]] ImageViewId FindView(const ImageViewInfo& view_info) const noexcept; + + void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id); + + [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept { + const VAddr overlap_end = overlap_cpu_addr + overlap_size; + return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end; + } + + ImageInfo info; + + u32 guest_size_bytes = 0; + u32 unswizzled_size_bytes = 0; + u32 converted_size_bytes = 0; + ImageFlagBits flags = ImageFlagBits::CpuModified; + + GPUVAddr gpu_addr = 0; + VAddr cpu_addr = 0; + VAddr cpu_addr_end = 0; + + u64 modification_tick = 0; + u64 frame_tick = 0; + + std::array<u32, MAX_MIP_LEVELS> mip_level_offsets{}; + + std::vector<ImageViewInfo> image_view_infos; + std::vector<ImageViewId> image_view_ids; + + std::vector<u32> slice_offsets; + std::vector<SubresourceBase> slice_subresources; + + std::vector<AliasedImage> aliased_images; +}; + +struct ImageAllocBase { + std::vector<ImageId> images; +}; + +void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id); + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp new file mode 100644 index 000000000..64fd7010a --- /dev/null +++ b/src/video_core/texture_cache/image_info.cpp @@ -0,0 +1,189 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/format_lookup_table.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/samples_helper.h" +#include "video_core/texture_cache/types.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using Tegra::Texture::TextureType; +using Tegra::Texture::TICEntry; +using VideoCore::Surface::PixelFormat; + +ImageInfo::ImageInfo(const TICEntry& config) noexcept { + format = PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type, + config.a_type, config.srgb_conversion); + num_samples = NumSamples(config.msaa_mode); + resources.levels = config.max_mip_level + 1; + if (config.IsPitchLinear()) { + pitch = config.Pitch(); + } else if (config.IsBlockLinear()) { + block = Extent3D{ + .width = config.block_width, + .height = config.block_height, + .depth = config.block_depth, + }; + } + tile_width_spacing = config.tile_width_spacing; + if (config.texture_type != TextureType::Texture2D && + config.texture_type != TextureType::Texture2DNoMipmap) { + ASSERT(!config.IsPitchLinear()); + } + switch (config.texture_type) { + case TextureType::Texture1D: + ASSERT(config.BaseLayer() == 0); + type = ImageType::e1D; + size.width = config.Width(); + break; + case TextureType::Texture1DArray: + UNIMPLEMENTED_IF(config.BaseLayer() != 0); + type = ImageType::e1D; + size.width = config.Width(); + resources.layers = config.Depth(); + break; + case TextureType::Texture2D: + case TextureType::Texture2DNoMipmap: + ASSERT(config.Depth() == 1); + type = config.IsPitchLinear() ? ImageType::Linear : ImageType::e2D; + size.width = config.Width(); + size.height = config.Height(); + resources.layers = config.BaseLayer() + 1; + break; + case TextureType::Texture2DArray: + type = ImageType::e2D; + size.width = config.Width(); + size.height = config.Height(); + resources.layers = config.BaseLayer() + config.Depth(); + break; + case TextureType::TextureCubemap: + ASSERT(config.Depth() == 1); + type = ImageType::e2D; + size.width = config.Width(); + size.height = config.Height(); + resources.layers = config.BaseLayer() + 6; + break; + case TextureType::TextureCubeArray: + UNIMPLEMENTED_IF(config.load_store_hint != 0); + type = ImageType::e2D; + size.width = config.Width(); + size.height = config.Height(); + resources.layers = config.BaseLayer() + config.Depth() * 6; + break; + case TextureType::Texture3D: + ASSERT(config.BaseLayer() == 0); + type = ImageType::e3D; + size.width = config.Width(); + size.height = config.Height(); + size.depth = config.Depth(); + break; + case TextureType::Texture1DBuffer: + type = ImageType::Buffer; + size.width = config.Width(); + break; + default: + UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); + break; + } + if (type != ImageType::Linear) { + // FIXME: Call this without passing *this + layer_stride = CalculateLayerStride(*this); + maybe_unaligned_layer_stride = CalculateLayerSize(*this); + } +} + +ImageInfo::ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs, size_t index) noexcept { + const auto& rt = regs.rt[index]; + format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(rt.format); + if (rt.tile_mode.is_pitch_linear) { + ASSERT(rt.tile_mode.is_3d == 0); + type = ImageType::Linear; + pitch = rt.width; + size = Extent3D{ + .width = pitch / BytesPerBlock(format), + .height = rt.height, + .depth = 1, + }; + return; + } + size.width = rt.width; + size.height = rt.height; + layer_stride = rt.layer_stride * 4; + maybe_unaligned_layer_stride = layer_stride; + num_samples = NumSamples(regs.multisample_mode); + block = Extent3D{ + .width = rt.tile_mode.block_width, + .height = rt.tile_mode.block_height, + .depth = rt.tile_mode.block_depth, + }; + if (rt.tile_mode.is_3d) { + type = ImageType::e3D; + size.depth = rt.depth; + } else { + type = ImageType::e2D; + resources.layers = rt.depth; + } +} + +ImageInfo::ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs) noexcept { + format = VideoCore::Surface::PixelFormatFromDepthFormat(regs.zeta.format); + size.width = regs.zeta_width; + size.height = regs.zeta_height; + resources.levels = 1; + layer_stride = regs.zeta.layer_stride * 4; + maybe_unaligned_layer_stride = layer_stride; + num_samples = NumSamples(regs.multisample_mode); + block = Extent3D{ + .width = regs.zeta.tile_mode.block_width, + .height = regs.zeta.tile_mode.block_height, + .depth = regs.zeta.tile_mode.block_depth, + }; + if (regs.zeta.tile_mode.is_pitch_linear) { + ASSERT(regs.zeta.tile_mode.is_3d == 0); + type = ImageType::Linear; + pitch = size.width * BytesPerBlock(format); + } else if (regs.zeta.tile_mode.is_3d) { + ASSERT(regs.zeta.tile_mode.is_pitch_linear == 0); + type = ImageType::e3D; + size.depth = regs.zeta_depth; + } else { + type = ImageType::e2D; + resources.layers = regs.zeta_depth; + } +} + +ImageInfo::ImageInfo(const Tegra::Engines::Fermi2D::Surface& config) noexcept { + UNIMPLEMENTED_IF_MSG(config.layer != 0, "Surface layer is not zero"); + format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(config.format); + if (config.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch) { + type = ImageType::Linear; + size = Extent3D{ + .width = config.pitch / VideoCore::Surface::BytesPerBlock(format), + .height = config.height, + .depth = 1, + }; + pitch = config.pitch; + } else { + type = config.block_depth > 0 ? ImageType::e3D : ImageType::e2D; + block = Extent3D{ + .width = config.block_width, + .height = config.block_height, + .depth = config.block_depth, + }; + // 3D blits with more than once slice are not implemented for now + // Render to individual slices + size = Extent3D{ + .width = config.width, + .height = config.height, + .depth = 1, + }; + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h new file mode 100644 index 000000000..5049fc36e --- /dev/null +++ b/src/video_core/texture_cache/image_info.h @@ -0,0 +1,38 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +using Tegra::Texture::TICEntry; +using VideoCore::Surface::PixelFormat; + +struct ImageInfo { + explicit ImageInfo() = default; + explicit ImageInfo(const TICEntry& config) noexcept; + explicit ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs, size_t index) noexcept; + explicit ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs) noexcept; + explicit ImageInfo(const Tegra::Engines::Fermi2D::Surface& config) noexcept; + + PixelFormat format = PixelFormat::Invalid; + ImageType type = ImageType::e1D; + SubresourceExtent resources; + Extent3D size{1, 1, 1}; + union { + Extent3D block{0, 0, 0}; + u32 pitch; + }; + u32 layer_stride = 0; + u32 maybe_unaligned_layer_stride = 0; + u32 num_samples = 1; + u32 tile_width_spacing = 0; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp new file mode 100644 index 000000000..18f72e508 --- /dev/null +++ b/src/video_core/texture_cache/image_view_base.cpp @@ -0,0 +1,41 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/assert.h" +#include "core/settings.h" +#include "video_core/compatible_formats.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/image_view_base.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, + ImageId image_id_) + : image_id{image_id_}, format{info.format}, type{info.type}, range{info.range}, + size{ + .width = std::max(image_info.size.width >> range.base.level, 1u), + .height = std::max(image_info.size.height >> range.base.level, 1u), + .depth = std::max(image_info.size.depth >> range.base.level, 1u), + } { + ASSERT_MSG(VideoCore::Surface::IsViewCompatible(image_info.format, info.format, false), + "Image view format {} is incompatible with image format {}", info.format, + image_info.format); + const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); + if (image_info.type == ImageType::Linear && is_async) { + flags |= ImageViewFlagBits::PreemtiveDownload; + } + if (image_info.type == ImageType::e3D && info.type != ImageViewType::e3D) { + flags |= ImageViewFlagBits::Slice; + } +} + +ImageViewBase::ImageViewBase(const NullImageParams&) {} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h new file mode 100644 index 000000000..73954167e --- /dev/null +++ b/src/video_core/texture_cache/image_view_base.h @@ -0,0 +1,47 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +using VideoCore::Surface::PixelFormat; + +struct ImageViewInfo; +struct ImageInfo; + +struct NullImageParams {}; + +enum class ImageViewFlagBits : u16 { + PreemtiveDownload = 1 << 0, + Strong = 1 << 1, + Slice = 1 << 2, +}; +DECLARE_ENUM_FLAG_OPERATORS(ImageViewFlagBits) + +struct ImageViewBase { + explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, + ImageId image_id); + explicit ImageViewBase(const NullImageParams&); + + [[nodiscard]] bool IsBuffer() const noexcept { + return type == ImageViewType::Buffer; + } + + ImageId image_id{}; + PixelFormat format{}; + ImageViewType type{}; + SubresourceRange range; + Extent3D size{0, 0, 0}; + ImageViewFlagBits flags{}; + + u64 invalidation_tick = 0; + u64 modification_tick = 0; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_view_info.cpp b/src/video_core/texture_cache/image_view_info.cpp new file mode 100644 index 000000000..faf5b151f --- /dev/null +++ b/src/video_core/texture_cache/image_view_info.cpp @@ -0,0 +1,88 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <limits> + +#include "common/assert.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/types.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +namespace { + +constexpr u8 RENDER_TARGET_SWIZZLE = std::numeric_limits<u8>::max(); + +[[nodiscard]] u8 CastSwizzle(SwizzleSource source) { + const u8 casted = static_cast<u8>(source); + ASSERT(static_cast<SwizzleSource>(casted) == source); + return casted; +} + +} // Anonymous namespace + +ImageViewInfo::ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept + : format{PixelFormatFromTIC(config)}, x_source{CastSwizzle(config.x_source)}, + y_source{CastSwizzle(config.y_source)}, z_source{CastSwizzle(config.z_source)}, + w_source{CastSwizzle(config.w_source)} { + range.base = SubresourceBase{ + .level = static_cast<s32>(config.res_min_mip_level), + .layer = base_layer, + }; + range.extent.levels = config.res_max_mip_level - config.res_min_mip_level + 1; + + switch (config.texture_type) { + case TextureType::Texture1D: + ASSERT(config.Height() == 1); + ASSERT(config.Depth() == 1); + type = ImageViewType::e1D; + break; + case TextureType::Texture2D: + case TextureType::Texture2DNoMipmap: + ASSERT(config.Depth() == 1); + type = config.normalized_coords ? ImageViewType::e2D : ImageViewType::Rect; + break; + case TextureType::Texture3D: + type = ImageViewType::e3D; + break; + case TextureType::TextureCubemap: + ASSERT(config.Depth() == 1); + type = ImageViewType::Cube; + range.extent.layers = 6; + break; + case TextureType::Texture1DArray: + type = ImageViewType::e1DArray; + range.extent.layers = config.Depth(); + break; + case TextureType::Texture2DArray: + type = ImageViewType::e2DArray; + range.extent.layers = config.Depth(); + break; + case TextureType::Texture1DBuffer: + type = ImageViewType::Buffer; + break; + case TextureType::TextureCubeArray: + type = ImageViewType::CubeArray; + range.extent.layers = config.Depth() * 6; + break; + default: + UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); + break; + } +} + +ImageViewInfo::ImageViewInfo(ImageViewType type_, PixelFormat format_, + SubresourceRange range_) noexcept + : type{type_}, format{format_}, range{range_}, x_source{RENDER_TARGET_SWIZZLE}, + y_source{RENDER_TARGET_SWIZZLE}, z_source{RENDER_TARGET_SWIZZLE}, + w_source{RENDER_TARGET_SWIZZLE} {} + +bool ImageViewInfo::IsRenderTarget() const noexcept { + return x_source == RENDER_TARGET_SWIZZLE && y_source == RENDER_TARGET_SWIZZLE && + z_source == RENDER_TARGET_SWIZZLE && w_source == RENDER_TARGET_SWIZZLE; +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_view_info.h b/src/video_core/texture_cache/image_view_info.h new file mode 100644 index 000000000..0c1f99117 --- /dev/null +++ b/src/video_core/texture_cache/image_view_info.h @@ -0,0 +1,50 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <type_traits> + +#include "video_core/surface.h" +#include "video_core/texture_cache/types.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using Tegra::Texture::SwizzleSource; +using Tegra::Texture::TICEntry; +using VideoCore::Surface::PixelFormat; + +/// Properties used to determine a image view +struct ImageViewInfo { + explicit ImageViewInfo() noexcept = default; + explicit ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept; + explicit ImageViewInfo(ImageViewType type, PixelFormat format, + SubresourceRange range = {}) noexcept; + + auto operator<=>(const ImageViewInfo&) const noexcept = default; + + [[nodiscard]] bool IsRenderTarget() const noexcept; + + [[nodiscard]] std::array<SwizzleSource, 4> Swizzle() const noexcept { + return std::array{ + static_cast<SwizzleSource>(x_source), + static_cast<SwizzleSource>(y_source), + static_cast<SwizzleSource>(z_source), + static_cast<SwizzleSource>(w_source), + }; + } + + ImageViewType type{}; + PixelFormat format{}; + SubresourceRange range; + u8 x_source = static_cast<u8>(SwizzleSource::R); + u8 y_source = static_cast<u8>(SwizzleSource::G); + u8 z_source = static_cast<u8>(SwizzleSource::B); + u8 w_source = static_cast<u8>(SwizzleSource::A); +}; +static_assert(std::has_unique_object_representations_v<ImageViewInfo>); + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/render_targets.h b/src/video_core/texture_cache/render_targets.h new file mode 100644 index 000000000..9b9544b07 --- /dev/null +++ b/src/video_core/texture_cache/render_targets.h @@ -0,0 +1,51 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <span> +#include <utility> + +#include "common/bit_cast.h" +#include "video_core/texture_cache/types.h" + +namespace VideoCommon { + +/// Framebuffer properties used to lookup a framebuffer +struct RenderTargets { + constexpr auto operator<=>(const RenderTargets&) const noexcept = default; + + constexpr bool Contains(std::span<const ImageViewId> elements) const noexcept { + const auto contains = [elements](ImageViewId item) { + return std::ranges::find(elements, item) != elements.end(); + }; + return std::ranges::any_of(color_buffer_ids, contains) || contains(depth_buffer_id); + } + + std::array<ImageViewId, NUM_RT> color_buffer_ids; + ImageViewId depth_buffer_id; + std::array<u8, NUM_RT> draw_buffers{}; + Extent2D size; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash<VideoCommon::RenderTargets> { + size_t operator()(const VideoCommon::RenderTargets& rt) const noexcept { + using VideoCommon::ImageViewId; + size_t value = std::hash<ImageViewId>{}(rt.depth_buffer_id); + for (const ImageViewId color_buffer_id : rt.color_buffer_ids) { + value ^= std::hash<ImageViewId>{}(color_buffer_id); + } + value ^= Common::BitCast<u64>(rt.draw_buffers); + value ^= Common::BitCast<u64>(rt.size); + return value; + } +}; + +} // namespace std diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h new file mode 100644 index 000000000..04539a43c --- /dev/null +++ b/src/video_core/texture_cache/samples_helper.h @@ -0,0 +1,55 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <utility> + +#include "common/assert.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +[[nodiscard]] inline std::pair<int, int> SamplesLog2(int num_samples) { + switch (num_samples) { + case 1: + return {0, 0}; + case 2: + return {1, 0}; + case 4: + return {1, 1}; + case 8: + return {2, 1}; + case 16: + return {2, 2}; + } + UNREACHABLE_MSG("Invalid number of samples={}", num_samples); + return {1, 1}; +} + +[[nodiscard]] inline int NumSamples(Tegra::Texture::MsaaMode msaa_mode) { + using Tegra::Texture::MsaaMode; + switch (msaa_mode) { + case MsaaMode::Msaa1x1: + return 1; + case MsaaMode::Msaa2x1: + case MsaaMode::Msaa2x1_D3D: + return 2; + case MsaaMode::Msaa2x2: + case MsaaMode::Msaa2x2_VC4: + case MsaaMode::Msaa2x2_VC12: + return 4; + case MsaaMode::Msaa4x2: + case MsaaMode::Msaa4x2_D3D: + case MsaaMode::Msaa4x2_VC8: + case MsaaMode::Msaa4x2_VC24: + return 8; + case MsaaMode::Msaa4x4: + return 16; + } + UNREACHABLE_MSG("Invalid MSAA mode={}", static_cast<int>(msaa_mode)); + return 1; +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h new file mode 100644 index 000000000..eae3be6ea --- /dev/null +++ b/src/video_core/texture_cache/slot_vector.h @@ -0,0 +1,156 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <concepts> +#include <numeric> +#include <type_traits> +#include <utility> +#include <vector> + +#include "common/assert.h" +#include "common/common_types.h" + +namespace VideoCommon { + +struct SlotId { + static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::max(); + + constexpr auto operator<=>(const SlotId&) const noexcept = default; + + constexpr explicit operator bool() const noexcept { + return index != INVALID_INDEX; + } + + u32 index = INVALID_INDEX; +}; + +template <class T> +requires std::is_nothrow_move_assignable_v<T>&& + std::is_nothrow_move_constructible_v<T> class SlotVector { +public: + ~SlotVector() noexcept { + size_t index = 0; + for (u64 bits : stored_bitset) { + for (size_t bit = 0; bits; ++bit, bits >>= 1) { + if ((bits & 1) != 0) { + values[index + bit].object.~T(); + } + } + index += 64; + } + delete[] values; + } + + [[nodiscard]] T& operator[](SlotId id) noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + [[nodiscard]] const T& operator[](SlotId id) const noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + template <typename... Args> + [[nodiscard]] SlotId insert(Args&&... args) noexcept { + const u32 index = FreeValueIndex(); + new (&values[index].object) T(std::forward<Args>(args)...); + SetStorageBit(index); + + return SlotId{index}; + } + + void erase(SlotId id) noexcept { + values[id.index].object.~T(); + free_list.push_back(id.index); + ResetStorageBit(id.index); + } + +private: + struct NonTrivialDummy { + NonTrivialDummy() noexcept {} + }; + + union Entry { + Entry() noexcept : dummy{} {} + ~Entry() noexcept {} + + NonTrivialDummy dummy; + T object; + }; + + void SetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] |= u64(1) << (index % 64); + } + + void ResetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] &= ~(u64(1) << (index % 64)); + } + + bool ReadStorageBit(u32 index) noexcept { + return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0; + } + + void ValidateIndex(SlotId id) const noexcept { + DEBUG_ASSERT(id); + DEBUG_ASSERT(id.index / 64 < stored_bitset.size()); + DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0); + } + + [[nodiscard]] u32 FreeValueIndex() noexcept { + if (free_list.empty()) { + Reserve(values_capacity ? (values_capacity << 1) : 1); + } + const u32 free_index = free_list.back(); + free_list.pop_back(); + return free_index; + } + + void Reserve(size_t new_capacity) noexcept { + Entry* const new_values = new Entry[new_capacity]; + size_t index = 0; + for (u64 bits : stored_bitset) { + for (size_t bit = 0; bits; ++bit, bits >>= 1) { + const size_t i = index + bit; + if ((bits & 1) == 0) { + continue; + } + T& old_value = values[i].object; + new (&new_values[i].object) T(std::move(old_value)); + old_value.~T(); + } + index += 64; + } + + stored_bitset.resize((new_capacity + 63) / 64); + + const size_t old_free_size = free_list.size(); + free_list.resize(old_free_size + (new_capacity - values_capacity)); + std::iota(free_list.begin() + old_free_size, free_list.end(), + static_cast<u32>(values_capacity)); + + delete[] values; + values = new_values; + values_capacity = new_capacity; + } + + Entry* values = nullptr; + size_t values_capacity = 0; + size_t values_size = 0; + + std::vector<u64> stored_bitset; + std::vector<u32> free_list; +}; + +} // namespace VideoCommon + +template <> +struct std::hash<VideoCommon::SlotId> { + size_t operator()(const VideoCommon::SlotId& id) const noexcept { + return std::hash<u32>{}(id.index); + } +}; diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp deleted file mode 100644 index dfcf36e0b..000000000 --- a/src/video_core/texture_cache/surface_base.cpp +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/algorithm.h" -#include "common/assert.h" -#include "common/common_types.h" -#include "common/microprofile.h" -#include "video_core/memory_manager.h" -#include "video_core/texture_cache/surface_base.h" -#include "video_core/texture_cache/surface_params.h" -#include "video_core/textures/convert.h" - -namespace VideoCommon { - -MICROPROFILE_DEFINE(GPU_Load_Texture, "GPU", "Texture Load", MP_RGB(128, 192, 128)); -MICROPROFILE_DEFINE(GPU_Flush_Texture, "GPU", "Texture Flush", MP_RGB(128, 192, 128)); - -using Tegra::Texture::ConvertFromGuestToHost; -using VideoCore::MortonSwizzleMode; -using VideoCore::Surface::IsPixelFormatASTC; -using VideoCore::Surface::PixelFormat; - -StagingCache::StagingCache() = default; - -StagingCache::~StagingCache() = default; - -SurfaceBaseImpl::SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params, - bool is_astc_supported) - : params{params}, gpu_addr{gpu_addr}, mipmap_sizes(params.num_levels), - mipmap_offsets(params.num_levels) { - is_converted = IsPixelFormatASTC(params.pixel_format) && !is_astc_supported; - host_memory_size = params.GetHostSizeInBytes(is_converted); - - std::size_t offset = 0; - for (u32 level = 0; level < params.num_levels; ++level) { - const std::size_t mipmap_size{params.GetGuestMipmapSize(level)}; - mipmap_sizes[level] = mipmap_size; - mipmap_offsets[level] = offset; - offset += mipmap_size; - } - layer_size = offset; - if (params.is_layered) { - if (params.is_tiled) { - layer_size = - SurfaceParams::AlignLayered(layer_size, params.block_height, params.block_depth); - } - guest_memory_size = layer_size * params.depth; - } else { - guest_memory_size = layer_size; - } -} - -MatchTopologyResult SurfaceBaseImpl::MatchesTopology(const SurfaceParams& rhs) const { - const u32 src_bpp{params.GetBytesPerPixel()}; - const u32 dst_bpp{rhs.GetBytesPerPixel()}; - const bool ib1 = params.IsBuffer(); - const bool ib2 = rhs.IsBuffer(); - if (std::tie(src_bpp, params.is_tiled, ib1) == std::tie(dst_bpp, rhs.is_tiled, ib2)) { - const bool cb1 = params.IsCompressed(); - const bool cb2 = rhs.IsCompressed(); - if (cb1 == cb2) { - return MatchTopologyResult::FullMatch; - } - return MatchTopologyResult::CompressUnmatch; - } - return MatchTopologyResult::None; -} - -MatchStructureResult SurfaceBaseImpl::MatchesStructure(const SurfaceParams& rhs) const { - // Buffer surface Check - if (params.IsBuffer()) { - const std::size_t wd1 = params.width * params.GetBytesPerPixel(); - const std::size_t wd2 = rhs.width * rhs.GetBytesPerPixel(); - if (wd1 == wd2) { - return MatchStructureResult::FullMatch; - } - return MatchStructureResult::None; - } - - // Linear Surface check - if (!params.is_tiled) { - if (std::tie(params.height, params.pitch) == std::tie(rhs.height, rhs.pitch)) { - if (params.width == rhs.width) { - return MatchStructureResult::FullMatch; - } else { - return MatchStructureResult::SemiMatch; - } - } - return MatchStructureResult::None; - } - - // Tiled Surface check - if (std::tie(params.depth, params.block_width, params.block_height, params.block_depth, - params.tile_width_spacing, params.num_levels) == - std::tie(rhs.depth, rhs.block_width, rhs.block_height, rhs.block_depth, - rhs.tile_width_spacing, rhs.num_levels)) { - if (std::tie(params.width, params.height) == std::tie(rhs.width, rhs.height)) { - return MatchStructureResult::FullMatch; - } - const u32 ws = SurfaceParams::ConvertWidth(rhs.GetBlockAlignedWidth(), params.pixel_format, - rhs.pixel_format); - const u32 hs = - SurfaceParams::ConvertHeight(rhs.height, params.pixel_format, rhs.pixel_format); - const u32 w1 = params.GetBlockAlignedWidth(); - if (std::tie(w1, params.height) == std::tie(ws, hs)) { - return MatchStructureResult::SemiMatch; - } - } - return MatchStructureResult::None; -} - -std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap( - const GPUVAddr candidate_gpu_addr) const { - if (gpu_addr == candidate_gpu_addr) { - return {{0, 0}}; - } - if (candidate_gpu_addr < gpu_addr) { - return {}; - } - const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)}; - const auto layer{static_cast<u32>(relative_address / layer_size)}; - if (layer >= params.depth) { - return {}; - } - const GPUVAddr mipmap_address = relative_address - layer_size * layer; - const auto mipmap_it = - Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address); - if (mipmap_it == mipmap_offsets.end()) { - return {}; - } - const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))}; - return std::make_pair(layer, level); -} - -std::vector<CopyParams> SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& in_params) const { - const u32 layers{params.depth}; - const u32 mipmaps{params.num_levels}; - std::vector<CopyParams> result; - result.reserve(static_cast<std::size_t>(layers) * static_cast<std::size_t>(mipmaps)); - - for (u32 layer = 0; layer < layers; layer++) { - for (u32 level = 0; level < mipmaps; level++) { - const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); - const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); - result.emplace_back(0, 0, layer, 0, 0, layer, level, level, width, height, 1); - } - } - return result; -} - -std::vector<CopyParams> SurfaceBaseImpl::BreakDownNonLayered(const SurfaceParams& in_params) const { - const u32 mipmaps{params.num_levels}; - std::vector<CopyParams> result; - result.reserve(mipmaps); - - for (u32 level = 0; level < mipmaps; level++) { - const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); - const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); - const u32 depth{std::min(params.GetMipDepth(level), in_params.GetMipDepth(level))}; - result.emplace_back(width, height, depth, level); - } - return result; -} - -void SurfaceBaseImpl::SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, - u8* buffer, u32 level) { - const u32 width{params.GetMipWidth(level)}; - const u32 height{params.GetMipHeight(level)}; - const u32 block_height{params.GetMipBlockHeight(level)}; - const u32 block_depth{params.GetMipBlockDepth(level)}; - - std::size_t guest_offset{mipmap_offsets[level]}; - if (params.is_layered) { - std::size_t host_offset = 0; - const std::size_t guest_stride = layer_size; - const std::size_t host_stride = params.GetHostLayerSize(level); - for (u32 layer = 0; layer < params.depth; ++layer) { - MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, 1, - params.tile_width_spacing, buffer + host_offset, memory + guest_offset); - guest_offset += guest_stride; - host_offset += host_stride; - } - } else { - MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, - params.GetMipDepth(level), params.tile_width_spacing, buffer, - memory + guest_offset); - } -} - -void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager, - StagingCache& staging_cache) { - MICROPROFILE_SCOPE(GPU_Load_Texture); - auto& staging_buffer = staging_cache.GetBuffer(0); - u8* host_ptr; - // Use an extra temporal buffer - auto& tmp_buffer = staging_cache.GetBuffer(1); - tmp_buffer.resize(guest_memory_size); - host_ptr = tmp_buffer.data(); - memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); - - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 0, "Block width is defined as {} on texture target {}", - params.block_width, static_cast<u32>(params.target)); - for (u32 level = 0; level < params.num_levels; ++level) { - const std::size_t host_offset{params.GetHostMipmapLevelOffset(level, false)}; - SwizzleFunc(MortonSwizzleMode::MortonToLinear, host_ptr, params, - staging_buffer.data() + host_offset, level); - } - } else { - ASSERT_MSG(params.num_levels == 1, "Linear mipmap loading is not implemented"); - const u32 bpp{params.GetBytesPerPixel()}; - const u32 block_width{params.GetDefaultBlockWidth()}; - const u32 block_height{params.GetDefaultBlockHeight()}; - const u32 width{(params.width + block_width - 1) / block_width}; - const u32 height{(params.height + block_height - 1) / block_height}; - const u32 copy_size{width * bpp}; - if (params.pitch == copy_size) { - std::memcpy(staging_buffer.data(), host_ptr, params.GetHostSizeInBytes(false)); - } else { - const u8* start{host_ptr}; - u8* write_to{staging_buffer.data()}; - for (u32 h = height; h > 0; --h) { - std::memcpy(write_to, start, copy_size); - start += params.pitch; - write_to += copy_size; - } - } - } - - if (!is_converted && params.pixel_format != PixelFormat::S8_UINT_D24_UNORM) { - return; - } - - for (u32 level = params.num_levels; level--;) { - const std::size_t in_host_offset{params.GetHostMipmapLevelOffset(level, false)}; - const std::size_t out_host_offset{params.GetHostMipmapLevelOffset(level, is_converted)}; - u8* const in_buffer = staging_buffer.data() + in_host_offset; - u8* const out_buffer = staging_buffer.data() + out_host_offset; - ConvertFromGuestToHost(in_buffer, out_buffer, params.pixel_format, - params.GetMipWidth(level), params.GetMipHeight(level), - params.GetMipDepth(level), true, true); - } -} - -void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager, - StagingCache& staging_cache) { - MICROPROFILE_SCOPE(GPU_Flush_Texture); - auto& staging_buffer = staging_cache.GetBuffer(0); - u8* host_ptr; - - // Use an extra temporal buffer - auto& tmp_buffer = staging_cache.GetBuffer(1); - tmp_buffer.resize(guest_memory_size); - host_ptr = tmp_buffer.data(); - - if (params.target == SurfaceTarget::Texture3D) { - // Special case for 3D texture segments - memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); - } - - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 0, "Block width is defined as {}", params.block_width); - for (u32 level = 0; level < params.num_levels; ++level) { - const std::size_t host_offset{params.GetHostMipmapLevelOffset(level, false)}; - SwizzleFunc(MortonSwizzleMode::LinearToMorton, host_ptr, params, - staging_buffer.data() + host_offset, level); - } - } else if (params.IsBuffer()) { - // Buffers don't have pitch or any fancy layout property. We can just memcpy them to guest - // memory. - std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size); - } else { - ASSERT(params.target == SurfaceTarget::Texture2D); - ASSERT(params.num_levels == 1); - - const u32 bpp{params.GetBytesPerPixel()}; - const u32 copy_size{params.width * bpp}; - if (params.pitch == copy_size) { - std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size); - } else { - u8* start{host_ptr}; - const u8* read_to{staging_buffer.data()}; - for (u32 h = params.height; h > 0; --h) { - std::memcpy(start, read_to, copy_size); - start += params.pitch; - read_to += copy_size; - } - } - } - memory_manager.WriteBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); -} - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h deleted file mode 100644 index 173f2edba..000000000 --- a/src/video_core/texture_cache/surface_base.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <optional> -#include <tuple> -#include <unordered_map> -#include <vector> - -#include "common/common_types.h" -#include "video_core/gpu.h" -#include "video_core/morton.h" -#include "video_core/texture_cache/copy_params.h" -#include "video_core/texture_cache/surface_params.h" -#include "video_core/texture_cache/surface_view.h" - -namespace Tegra { -class MemoryManager; -} - -namespace VideoCommon { - -using VideoCore::MortonSwizzleMode; -using VideoCore::Surface::SurfaceTarget; - -enum class MatchStructureResult : u32 { - FullMatch = 0, - SemiMatch = 1, - None = 2, -}; - -enum class MatchTopologyResult : u32 { - FullMatch = 0, - CompressUnmatch = 1, - None = 2, -}; - -class StagingCache { -public: - explicit StagingCache(); - ~StagingCache(); - - std::vector<u8>& GetBuffer(std::size_t index) { - return staging_buffer[index]; - } - - const std::vector<u8>& GetBuffer(std::size_t index) const { - return staging_buffer[index]; - } - - void SetSize(std::size_t size) { - staging_buffer.resize(size); - } - -private: - std::vector<std::vector<u8>> staging_buffer; -}; - -class SurfaceBaseImpl { -public: - void LoadBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); - - void FlushBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); - - GPUVAddr GetGpuAddr() const { - return gpu_addr; - } - - bool Overlaps(const VAddr start, const VAddr end) const { - return (cpu_addr < end) && (cpu_addr_end > start); - } - - bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) const { - const GPUVAddr gpu_addr_end = gpu_addr + guest_memory_size; - return gpu_addr <= other_start && other_end <= gpu_addr_end; - } - - // Use only when recycling a surface - void SetGpuAddr(const GPUVAddr new_addr) { - gpu_addr = new_addr; - } - - VAddr GetCpuAddr() const { - return cpu_addr; - } - - VAddr GetCpuAddrEnd() const { - return cpu_addr_end; - } - - void SetCpuAddr(const VAddr new_addr) { - cpu_addr = new_addr; - cpu_addr_end = new_addr + guest_memory_size; - } - - const SurfaceParams& GetSurfaceParams() const { - return params; - } - - std::size_t GetSizeInBytes() const { - return guest_memory_size; - } - - std::size_t GetHostSizeInBytes() const { - return host_memory_size; - } - - std::size_t GetMipmapSize(const u32 level) const { - return mipmap_sizes[level]; - } - - bool IsLinear() const { - return !params.is_tiled; - } - - bool IsConverted() const { - return is_converted; - } - - bool MatchFormat(VideoCore::Surface::PixelFormat pixel_format) const { - return params.pixel_format == pixel_format; - } - - VideoCore::Surface::PixelFormat GetFormat() const { - return params.pixel_format; - } - - bool MatchTarget(VideoCore::Surface::SurfaceTarget target) const { - return params.target == target; - } - - MatchTopologyResult MatchesTopology(const SurfaceParams& rhs) const; - - MatchStructureResult MatchesStructure(const SurfaceParams& rhs) const; - - bool MatchesSubTexture(const SurfaceParams& rhs, const GPUVAddr other_gpu_addr) const { - return std::tie(gpu_addr, params.target, params.num_levels) == - std::tie(other_gpu_addr, rhs.target, rhs.num_levels) && - params.target == SurfaceTarget::Texture2D && params.num_levels == 1; - } - - std::optional<std::pair<u32, u32>> GetLayerMipmap(const GPUVAddr candidate_gpu_addr) const; - - std::vector<CopyParams> BreakDown(const SurfaceParams& in_params) const { - return params.is_layered ? BreakDownLayered(in_params) : BreakDownNonLayered(in_params); - } - -protected: - explicit SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params, - bool is_astc_supported); - ~SurfaceBaseImpl() = default; - - virtual void DecorateSurfaceName() = 0; - - const SurfaceParams params; - std::size_t layer_size; - std::size_t guest_memory_size; - std::size_t host_memory_size; - GPUVAddr gpu_addr{}; - VAddr cpu_addr{}; - VAddr cpu_addr_end{}; - bool is_converted{}; - - std::vector<std::size_t> mipmap_sizes; - std::vector<std::size_t> mipmap_offsets; - -private: - void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer, - u32 level); - - std::vector<CopyParams> BreakDownLayered(const SurfaceParams& in_params) const; - - std::vector<CopyParams> BreakDownNonLayered(const SurfaceParams& in_params) const; -}; - -template <typename TView> -class SurfaceBase : public SurfaceBaseImpl { -public: - virtual void UploadTexture(const std::vector<u8>& staging_buffer) = 0; - - virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; - - void MarkAsModified(bool is_modified_, u64 tick) { - is_modified = is_modified_ || is_target; - modification_tick = tick; - } - - void MarkAsRenderTarget(bool is_target_, u32 index_) { - is_target = is_target_; - index = index_; - } - - void SetMemoryMarked(bool is_memory_marked_) { - is_memory_marked = is_memory_marked_; - } - - bool IsMemoryMarked() const { - return is_memory_marked; - } - - void SetSyncPending(bool is_sync_pending_) { - is_sync_pending = is_sync_pending_; - } - - bool IsSyncPending() const { - return is_sync_pending; - } - - void MarkAsPicked(bool is_picked_) { - is_picked = is_picked_; - } - - bool IsModified() const { - return is_modified; - } - - bool IsProtected() const { - // Only 3D slices are to be protected - return is_target && params.target == SurfaceTarget::Texture3D; - } - - bool IsRenderTarget() const { - return is_target; - } - - u32 GetRenderTarget() const { - return index; - } - - bool IsRegistered() const { - return is_registered; - } - - bool IsPicked() const { - return is_picked; - } - - void MarkAsRegistered(bool is_reg) { - is_registered = is_reg; - } - - u64 GetModificationTick() const { - return modification_tick; - } - - TView EmplaceOverview(const SurfaceParams& overview_params) { - const u32 num_layers{(params.is_layered && !overview_params.is_layered) ? 1 : params.depth}; - return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels)); - } - - TView Emplace3DView(u32 slice, u32 depth, u32 base_level, u32 num_levels) { - return GetView(ViewParams(VideoCore::Surface::SurfaceTarget::Texture3D, slice, depth, - base_level, num_levels)); - } - - std::optional<TView> EmplaceIrregularView(const SurfaceParams& view_params, - const GPUVAddr view_addr, - const std::size_t candidate_size, const u32 mipmap, - const u32 layer) { - const auto layer_mipmap{GetLayerMipmap(view_addr + candidate_size)}; - if (!layer_mipmap) { - return {}; - } - const auto [end_layer, end_mipmap] = *layer_mipmap; - if (layer != end_layer) { - if (mipmap == 0 && end_mipmap == 0) { - return GetView(ViewParams(view_params.target, layer, end_layer - layer, 0, 1)); - } - return {}; - } else { - return GetView(ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap)); - } - } - - std::optional<TView> EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr, - const std::size_t candidate_size) { - if (params.target == SurfaceTarget::Texture3D || - view_params.target == SurfaceTarget::Texture3D || - (params.num_levels == 1 && !params.is_layered)) { - return {}; - } - const auto layer_mipmap{GetLayerMipmap(view_addr)}; - if (!layer_mipmap) { - return {}; - } - const auto [layer, mipmap] = *layer_mipmap; - if (GetMipmapSize(mipmap) != candidate_size) { - return EmplaceIrregularView(view_params, view_addr, candidate_size, mipmap, layer); - } - return GetView(ViewParams(view_params.target, layer, 1, mipmap, 1)); - } - - TView GetMainView() const { - return main_view; - } - -protected: - explicit SurfaceBase(const GPUVAddr gpu_addr, const SurfaceParams& params, - bool is_astc_supported) - : SurfaceBaseImpl(gpu_addr, params, is_astc_supported) {} - - ~SurfaceBase() = default; - - virtual TView CreateView(const ViewParams& view_key) = 0; - - TView main_view; - std::unordered_map<ViewParams, TView> views; - -private: - TView GetView(const ViewParams& key) { - const auto [entry, is_cache_miss] = views.try_emplace(key); - auto& view{entry->second}; - if (is_cache_miss) { - view = CreateView(key); - } - return view; - } - - static constexpr u32 NO_RT = 0xFFFFFFFF; - - bool is_modified{}; - bool is_target{}; - bool is_registered{}; - bool is_picked{}; - bool is_memory_marked{}; - bool is_sync_pending{}; - u32 index{NO_RT}; - u64 modification_tick{}; -}; - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp deleted file mode 100644 index e614a92df..000000000 --- a/src/video_core/texture_cache/surface_params.cpp +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <string> -#include <tuple> - -#include "common/alignment.h" -#include "common/bit_util.h" -#include "core/core.h" -#include "video_core/engines/shader_bytecode.h" -#include "video_core/surface.h" -#include "video_core/texture_cache/format_lookup_table.h" -#include "video_core/texture_cache/surface_params.h" - -namespace VideoCommon { - -using VideoCore::Surface::PixelFormat; -using VideoCore::Surface::PixelFormatFromDepthFormat; -using VideoCore::Surface::PixelFormatFromRenderTargetFormat; -using VideoCore::Surface::SurfaceTarget; -using VideoCore::Surface::SurfaceTargetFromTextureType; -using VideoCore::Surface::SurfaceType; - -namespace { - -SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { - switch (type) { - case Tegra::Shader::TextureType::Texture1D: - return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D; - case Tegra::Shader::TextureType::Texture2D: - return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; - case Tegra::Shader::TextureType::Texture3D: - ASSERT(!is_array); - return SurfaceTarget::Texture3D; - case Tegra::Shader::TextureType::TextureCube: - return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap; - default: - UNREACHABLE(); - return SurfaceTarget::Texture2D; - } -} - -SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) { - switch (type) { - case Tegra::Shader::ImageType::Texture1D: - return SurfaceTarget::Texture1D; - case Tegra::Shader::ImageType::TextureBuffer: - return SurfaceTarget::TextureBuffer; - case Tegra::Shader::ImageType::Texture1DArray: - return SurfaceTarget::Texture1DArray; - case Tegra::Shader::ImageType::Texture2D: - return SurfaceTarget::Texture2D; - case Tegra::Shader::ImageType::Texture2DArray: - return SurfaceTarget::Texture2DArray; - case Tegra::Shader::ImageType::Texture3D: - return SurfaceTarget::Texture3D; - default: - UNREACHABLE(); - return SurfaceTarget::Texture2D; - } -} - -constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { - return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); -} - -} // Anonymous namespace - -SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_table, - const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry) { - SurfaceParams params; - params.is_tiled = tic.IsTiled(); - params.srgb_conversion = tic.IsSrgbConversionEnabled(); - params.block_width = params.is_tiled ? tic.BlockWidth() : 0; - params.block_height = params.is_tiled ? tic.BlockHeight() : 0; - params.block_depth = params.is_tiled ? tic.BlockDepth() : 0; - params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; - params.pixel_format = lookup_table.GetPixelFormat( - tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type); - params.type = GetFormatType(params.pixel_format); - if (entry.is_shadow && params.type == SurfaceType::ColorTexture) { - switch (params.pixel_format) { - case PixelFormat::R16_UNORM: - case PixelFormat::R16_FLOAT: - params.pixel_format = PixelFormat::D16_UNORM; - break; - case PixelFormat::R32_FLOAT: - params.pixel_format = PixelFormat::D32_FLOAT; - break; - default: - UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}", - static_cast<u32>(params.pixel_format)); - } - params.type = GetFormatType(params.pixel_format); - } - // TODO: on 1DBuffer we should use the tic info. - if (tic.IsBuffer()) { - params.target = SurfaceTarget::TextureBuffer; - params.width = tic.Width(); - params.pitch = params.width * params.GetBytesPerPixel(); - params.height = 1; - params.depth = 1; - params.num_levels = 1; - params.emulated_levels = 1; - params.is_layered = false; - } else { - params.target = TextureTypeToSurfaceTarget(entry.type, entry.is_array); - params.width = tic.Width(); - params.height = tic.Height(); - params.depth = tic.Depth(); - params.pitch = params.is_tiled ? 0 : tic.Pitch(); - if (params.target == SurfaceTarget::TextureCubemap || - params.target == SurfaceTarget::TextureCubeArray) { - params.depth *= 6; - } - params.num_levels = tic.max_mip_level + 1; - params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); - params.is_layered = params.IsLayered(); - } - return params; -} - -SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_table, - const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Image& entry) { - SurfaceParams params; - params.is_tiled = tic.IsTiled(); - params.srgb_conversion = tic.IsSrgbConversionEnabled(); - params.block_width = params.is_tiled ? tic.BlockWidth() : 0; - params.block_height = params.is_tiled ? tic.BlockHeight() : 0; - params.block_depth = params.is_tiled ? tic.BlockDepth() : 0; - params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; - params.pixel_format = lookup_table.GetPixelFormat( - tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type); - params.type = GetFormatType(params.pixel_format); - params.target = ImageTypeToSurfaceTarget(entry.type); - // TODO: on 1DBuffer we should use the tic info. - if (tic.IsBuffer()) { - params.target = SurfaceTarget::TextureBuffer; - params.width = tic.Width(); - params.pitch = params.width * params.GetBytesPerPixel(); - params.height = 1; - params.depth = 1; - params.num_levels = 1; - params.emulated_levels = 1; - params.is_layered = false; - } else { - params.width = tic.Width(); - params.height = tic.Height(); - params.depth = tic.Depth(); - params.pitch = params.is_tiled ? 0 : tic.Pitch(); - if (params.target == SurfaceTarget::TextureCubemap || - params.target == SurfaceTarget::TextureCubeArray) { - params.depth *= 6; - } - params.num_levels = tic.max_mip_level + 1; - params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); - params.is_layered = params.IsLayered(); - } - return params; -} - -SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) { - const auto& regs = system.GPU().Maxwell3D().regs; - - const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U); - const bool is_layered = regs.zeta_layers > 1 && block_depth == 0; - const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format); - - return { - .is_tiled = regs.zeta.memory_layout.type == - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear, - .srgb_conversion = false, - .is_layered = is_layered, - .block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U), - .block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U), - .block_depth = block_depth, - .tile_width_spacing = 1, - .width = regs.zeta_width, - .height = regs.zeta_height, - .depth = is_layered ? regs.zeta_layers.Value() : 1U, - .pitch = 0, - .num_levels = 1, - .emulated_levels = 1, - .pixel_format = pixel_format, - .type = GetFormatType(pixel_format), - .target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D, - }; -} - -SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { - const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; - SurfaceParams params; - params.is_tiled = - config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB || - config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB; - params.block_width = config.memory_layout.block_width; - params.block_height = config.memory_layout.block_height; - params.block_depth = config.memory_layout.block_depth; - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.type = GetFormatType(params.pixel_format); - if (params.is_tiled) { - params.pitch = 0; - params.width = config.width; - } else { - const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; - params.pitch = config.width; - params.width = params.pitch / bpp; - } - params.height = config.height; - params.num_levels = 1; - params.emulated_levels = 1; - - if (config.memory_layout.is_3d != 0) { - params.depth = config.layers.Value(); - params.is_layered = false; - params.target = SurfaceTarget::Texture3D; - } else if (config.layers > 1) { - params.depth = config.layers.Value(); - params.is_layered = true; - params.target = SurfaceTarget::Texture2DArray; - } else { - params.depth = 1; - params.is_layered = false; - params.target = SurfaceTarget::Texture2D; - } - return params; -} - -SurfaceParams SurfaceParams::CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - const bool is_tiled = !config.linear; - const auto pixel_format = PixelFormatFromRenderTargetFormat(config.format); - - SurfaceParams params{ - .is_tiled = is_tiled, - .srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB || - config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB, - .block_width = is_tiled ? std::min(config.BlockWidth(), 5U) : 0U, - .block_height = is_tiled ? std::min(config.BlockHeight(), 5U) : 0U, - .block_depth = is_tiled ? std::min(config.BlockDepth(), 5U) : 0U, - .tile_width_spacing = 1, - .width = config.width, - .height = config.height, - .depth = 1, - .pitch = config.pitch, - .num_levels = 1, - .emulated_levels = 1, - .pixel_format = pixel_format, - .type = GetFormatType(pixel_format), - // TODO(Rodrigo): Try to guess texture arrays from parameters - .target = SurfaceTarget::Texture2D, - }; - - params.is_layered = params.IsLayered(); - return params; -} - -VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget( - const VideoCommon::Shader::Sampler& entry) { - return TextureTypeToSurfaceTarget(entry.type, entry.is_array); -} - -VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget( - const VideoCommon::Shader::Image& entry) { - return ImageTypeToSurfaceTarget(entry.type); -} - -bool SurfaceParams::IsLayered() const { - switch (target) { - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: - return true; - default: - return false; - } -} - -// Auto block resizing algorithm from: -// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c -u32 SurfaceParams::GetMipBlockHeight(u32 level) const { - if (level == 0) { - return this->block_height; - } - - const u32 height_new{GetMipHeight(level)}; - const u32 default_block_height{GetDefaultBlockHeight()}; - const u32 blocks_in_y{(height_new + default_block_height - 1) / default_block_height}; - const u32 block_height_new = Common::Log2Ceil32(blocks_in_y); - return std::clamp(block_height_new, 3U, 7U) - 3U; -} - -u32 SurfaceParams::GetMipBlockDepth(u32 level) const { - if (level == 0) { - return this->block_depth; - } - if (is_layered) { - return 0; - } - - const u32 depth_new{GetMipDepth(level)}; - const u32 block_depth_new = Common::Log2Ceil32(depth_new); - if (block_depth_new > 4) { - return 5 - (GetMipBlockHeight(level) >= 2); - } - return block_depth_new; -} - -std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { - std::size_t offset = 0; - for (u32 i = 0; i < level; i++) { - offset += GetInnerMipmapMemorySize(i, false, false); - } - return offset; -} - -std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level, bool is_converted) const { - std::size_t offset = 0; - if (is_converted) { - for (u32 i = 0; i < level; ++i) { - offset += GetConvertedMipmapSize(i) * GetNumLayers(); - } - } else { - for (u32 i = 0; i < level; ++i) { - offset += GetInnerMipmapMemorySize(i, true, false) * GetNumLayers(); - } - } - return offset; -} - -std::size_t SurfaceParams::GetConvertedMipmapSize(u32 level) const { - constexpr std::size_t rgba8_bpp = 4ULL; - const std::size_t mip_width = GetMipWidth(level); - const std::size_t mip_height = GetMipHeight(level); - const std::size_t mip_depth = is_layered ? 1 : GetMipDepth(level); - return mip_width * mip_height * mip_depth * rgba8_bpp; -} - -std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) const { - std::size_t size = 0; - for (u32 level = 0; level < num_levels; ++level) { - size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed); - } - if (is_tiled && is_layered) { - return Common::AlignBits(size, Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth); - } - return size; -} - -std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, - bool uncompressed) const { - const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())}; - const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())}; - const u32 depth{is_layered ? 1U : GetMipDepth(level)}; - if (is_tiled) { - return Tegra::Texture::CalculateSize(!as_host_size, GetBytesPerPixel(), width, height, - depth, GetMipBlockHeight(level), - GetMipBlockDepth(level)); - } else if (as_host_size || IsBuffer()) { - return GetBytesPerPixel() * width * height * depth; - } else { - // Linear Texture Case - return pitch * height * depth; - } -} - -bool SurfaceParams::operator==(const SurfaceParams& rhs) const { - return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, - height, depth, pitch, num_levels, pixel_format, type, target) == - std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, - rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, - rhs.num_levels, rhs.pixel_format, rhs.type, rhs.target); -} - -std::string SurfaceParams::TargetName() const { - switch (target) { - case SurfaceTarget::Texture1D: - return "1D"; - case SurfaceTarget::TextureBuffer: - return "TexBuffer"; - case SurfaceTarget::Texture2D: - return "2D"; - case SurfaceTarget::Texture3D: - return "3D"; - case SurfaceTarget::Texture1DArray: - return "1DArray"; - case SurfaceTarget::Texture2DArray: - return "2DArray"; - case SurfaceTarget::TextureCubemap: - return "Cube"; - case SurfaceTarget::TextureCubeArray: - return "CubeArray"; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target)); - UNREACHABLE(); - return fmt::format("TUK({})", static_cast<u32>(target)); - } -} - -u32 SurfaceParams::GetBlockSize() const { - const u32 x = 64U << block_width; - const u32 y = 8U << block_height; - const u32 z = 1U << block_depth; - return x * y * z; -} - -std::pair<u32, u32> SurfaceParams::GetBlockXY() const { - const u32 x_pixels = 64U / GetBytesPerPixel(); - const u32 x = x_pixels << block_width; - const u32 y = 8U << block_height; - return {x, y}; -} - -std::tuple<u32, u32, u32> SurfaceParams::GetBlockOffsetXYZ(u32 offset) const { - const auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); }; - const u32 block_size = GetBlockSize(); - const u32 block_index = offset / block_size; - const u32 gob_offset = offset % block_size; - const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GOB_SIZE); - const u32 x_gob_pixels = 64U / GetBytesPerPixel(); - const u32 x_block_pixels = x_gob_pixels << block_width; - const u32 y_block_pixels = 8U << block_height; - const u32 z_block_pixels = 1U << block_depth; - const u32 x_blocks = div_ceil(width, x_block_pixels); - const u32 y_blocks = div_ceil(height, y_block_pixels); - const u32 z_blocks = div_ceil(depth, z_block_pixels); - const u32 base_x = block_index % x_blocks; - const u32 base_y = (block_index / x_blocks) % y_blocks; - const u32 base_z = (block_index / (x_blocks * y_blocks)) % z_blocks; - u32 x = base_x * x_block_pixels; - u32 y = base_y * y_block_pixels; - u32 z = base_z * z_block_pixels; - z += gob_index >> block_height; - y += (gob_index * 8U) % y_block_pixels; - return {x, y, z}; -} - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h deleted file mode 100644 index 118aa689e..000000000 --- a/src/video_core/texture_cache/surface_params.h +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <utility> - -#include "common/alignment.h" -#include "common/bit_util.h" -#include "common/cityhash.h" -#include "common/common_types.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/shader/shader_ir.h" -#include "video_core/surface.h" -#include "video_core/textures/decoders.h" - -namespace VideoCommon { - -class FormatLookupTable; - -class SurfaceParams { -public: - /// Creates SurfaceCachedParams from a texture configuration. - static SurfaceParams CreateForTexture(const FormatLookupTable& lookup_table, - const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry); - - /// Creates SurfaceCachedParams from an image configuration. - static SurfaceParams CreateForImage(const FormatLookupTable& lookup_table, - const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Image& entry); - - /// Creates SurfaceCachedParams for a depth buffer configuration. - static SurfaceParams CreateForDepthBuffer(Core::System& system); - - /// Creates SurfaceCachedParams from a framebuffer configuration. - static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); - - /// Creates SurfaceCachedParams from a Fermi2D surface configuration. - static SurfaceParams CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config); - - /// Obtains the texture target from a shader's sampler entry. - static VideoCore::Surface::SurfaceTarget ExpectedTarget( - const VideoCommon::Shader::Sampler& entry); - - /// Obtains the texture target from a shader's sampler entry. - static VideoCore::Surface::SurfaceTarget ExpectedTarget( - const VideoCommon::Shader::Image& entry); - - std::size_t Hash() const { - return static_cast<std::size_t>( - Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); - } - - bool operator==(const SurfaceParams& rhs) const; - - bool operator!=(const SurfaceParams& rhs) const { - return !operator==(rhs); - } - - std::size_t GetGuestSizeInBytes() const { - return GetInnerMemorySize(false, false, false); - } - - std::size_t GetHostSizeInBytes(bool is_converted) const { - if (!is_converted) { - return GetInnerMemorySize(true, false, false); - } - // ASTC is uncompressed in software, in emulated as RGBA8 - std::size_t host_size_in_bytes = 0; - for (u32 level = 0; level < num_levels; ++level) { - host_size_in_bytes += GetConvertedMipmapSize(level) * GetNumLayers(); - } - return host_size_in_bytes; - } - - u32 GetBlockAlignedWidth() const { - return Common::AlignUp(width, 64 / GetBytesPerPixel()); - } - - /// Returns the width of a given mipmap level. - u32 GetMipWidth(u32 level) const { - return std::max(1U, width >> level); - } - - /// Returns the height of a given mipmap level. - u32 GetMipHeight(u32 level) const { - return std::max(1U, height >> level); - } - - /// Returns the depth of a given mipmap level. - u32 GetMipDepth(u32 level) const { - return is_layered ? depth : std::max(1U, depth >> level); - } - - /// Returns the block height of a given mipmap level. - u32 GetMipBlockHeight(u32 level) const; - - /// Returns the block depth of a given mipmap level. - u32 GetMipBlockDepth(u32 level) const; - - /// Returns the best possible row/pitch alignment for the surface. - u32 GetRowAlignment(u32 level, bool is_converted) const { - const u32 bpp = is_converted ? 4 : GetBytesPerPixel(); - return 1U << Common::CountTrailingZeroes32(GetMipWidth(level) * bpp); - } - - /// Returns the offset in bytes in guest memory of a given mipmap level. - std::size_t GetGuestMipmapLevelOffset(u32 level) const; - - /// Returns the offset in bytes in host memory (linear) of a given mipmap level. - std::size_t GetHostMipmapLevelOffset(u32 level, bool is_converted) const; - - /// Returns the size in bytes in guest memory of a given mipmap level. - std::size_t GetGuestMipmapSize(u32 level) const { - return GetInnerMipmapMemorySize(level, false, false); - } - - /// Returns the size in bytes in host memory (linear) of a given mipmap level. - std::size_t GetHostMipmapSize(u32 level) const { - return GetInnerMipmapMemorySize(level, true, false) * GetNumLayers(); - } - - std::size_t GetConvertedMipmapSize(u32 level) const; - - /// Get this texture Tegra Block size in guest memory layout - u32 GetBlockSize() const; - - /// Get X, Y coordinates max sizes of a single block. - std::pair<u32, u32> GetBlockXY() const; - - /// Get the offset in x, y, z coordinates from a memory offset - std::tuple<u32, u32, u32> GetBlockOffsetXYZ(u32 offset) const; - - /// Returns the size of a layer in bytes in guest memory. - std::size_t GetGuestLayerSize() const { - return GetLayerSize(false, false); - } - - /// Returns the size of a layer in bytes in host memory for a given mipmap level. - std::size_t GetHostLayerSize(u32 level) const { - ASSERT(target != VideoCore::Surface::SurfaceTarget::Texture3D); - return GetInnerMipmapMemorySize(level, true, false); - } - - /// Returns the max possible mipmap that the texture can have in host gpu - u32 MaxPossibleMipmap() const { - const u32 max_mipmap_w = Common::Log2Ceil32(width) + 1U; - const u32 max_mipmap_h = Common::Log2Ceil32(height) + 1U; - const u32 max_mipmap = std::max(max_mipmap_w, max_mipmap_h); - if (target != VideoCore::Surface::SurfaceTarget::Texture3D) - return max_mipmap; - return std::max(max_mipmap, Common::Log2Ceil32(depth) + 1U); - } - - /// Returns if the guest surface is a compressed surface. - bool IsCompressed() const { - return GetDefaultBlockHeight() > 1 || GetDefaultBlockWidth() > 1; - } - - /// Returns the default block width. - u32 GetDefaultBlockWidth() const { - return VideoCore::Surface::GetDefaultBlockWidth(pixel_format); - } - - /// Returns the default block height. - u32 GetDefaultBlockHeight() const { - return VideoCore::Surface::GetDefaultBlockHeight(pixel_format); - } - - /// Returns the bits per pixel. - u32 GetBitsPerPixel() const { - return VideoCore::Surface::GetFormatBpp(pixel_format); - } - - /// Returns the bytes per pixel. - u32 GetBytesPerPixel() const { - return VideoCore::Surface::GetBytesPerPixel(pixel_format); - } - - /// Returns true if the pixel format is a depth and/or stencil format. - bool IsPixelFormatZeta() const { - return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && - pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; - } - - /// Returns is the surface is a TextureBuffer type of surface. - bool IsBuffer() const { - return target == VideoCore::Surface::SurfaceTarget::TextureBuffer; - } - - /// Returns the number of layers in the surface. - std::size_t GetNumLayers() const { - return is_layered ? depth : 1; - } - - /// Returns the debug name of the texture for use in graphic debuggers. - std::string TargetName() const; - - // Helper used for out of class size calculations - static std::size_t AlignLayered(const std::size_t out_size, const u32 block_height, - const u32 block_depth) { - return Common::AlignBits(out_size, - Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth); - } - - /// Converts a width from a type of surface into another. This helps represent the - /// equivalent value between compressed/non-compressed textures. - static u32 ConvertWidth(u32 width, VideoCore::Surface::PixelFormat pixel_format_from, - VideoCore::Surface::PixelFormat pixel_format_to) { - const u32 bw1 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_from); - const u32 bw2 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_to); - return (width * bw2 + bw1 - 1) / bw1; - } - - /// Converts a height from a type of surface into another. This helps represent the - /// equivalent value between compressed/non-compressed textures. - static u32 ConvertHeight(u32 height, VideoCore::Surface::PixelFormat pixel_format_from, - VideoCore::Surface::PixelFormat pixel_format_to) { - const u32 bh1 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_from); - const u32 bh2 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_to); - return (height * bh2 + bh1 - 1) / bh1; - } - - // Finds the maximun possible width between 2 2D layers of different formats - static u32 IntersectWidth(const SurfaceParams& src_params, const SurfaceParams& dst_params, - const u32 src_level, const u32 dst_level) { - const u32 bw1 = src_params.GetDefaultBlockWidth(); - const u32 bw2 = dst_params.GetDefaultBlockWidth(); - const u32 t_src_width = (src_params.GetMipWidth(src_level) * bw2 + bw1 - 1) / bw1; - const u32 t_dst_width = (dst_params.GetMipWidth(dst_level) * bw1 + bw2 - 1) / bw2; - return std::min(t_src_width, t_dst_width); - } - - // Finds the maximun possible height between 2 2D layers of different formats - static u32 IntersectHeight(const SurfaceParams& src_params, const SurfaceParams& dst_params, - const u32 src_level, const u32 dst_level) { - const u32 bh1 = src_params.GetDefaultBlockHeight(); - const u32 bh2 = dst_params.GetDefaultBlockHeight(); - const u32 t_src_height = (src_params.GetMipHeight(src_level) * bh2 + bh1 - 1) / bh1; - const u32 t_dst_height = (dst_params.GetMipHeight(dst_level) * bh1 + bh2 - 1) / bh2; - return std::min(t_src_height, t_dst_height); - } - - bool is_tiled; - bool srgb_conversion; - bool is_layered; - u32 block_width; - u32 block_height; - u32 block_depth; - u32 tile_width_spacing; - u32 width; - u32 height; - u32 depth; - u32 pitch; - u32 num_levels; - u32 emulated_levels; - VideoCore::Surface::PixelFormat pixel_format; - VideoCore::Surface::SurfaceType type; - VideoCore::Surface::SurfaceTarget target; - -private: - /// Returns the size of a given mipmap level inside a layer. - std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool uncompressed) const; - - /// Returns the size of all mipmap levels and aligns as needed. - std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const { - return GetLayerSize(as_host_size, uncompressed) * - (layer_only ? 1U : (is_layered ? depth : 1U)); - } - - /// Returns the size of a layer - std::size_t GetLayerSize(bool as_host_size, bool uncompressed) const; - - /// Returns true if these parameters are from a layered surface. - bool IsLayered() const; -}; - -} // namespace VideoCommon - -namespace std { - -template <> -struct hash<VideoCommon::SurfaceParams> { - std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp deleted file mode 100644 index 6b5f5984b..000000000 --- a/src/video_core/texture_cache/surface_view.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <tuple> - -#include "common/common_types.h" -#include "video_core/texture_cache/surface_view.h" - -namespace VideoCommon { - -std::size_t ViewParams::Hash() const { - return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^ - (static_cast<std::size_t>(base_level) << 24) ^ - (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); -} - -bool ViewParams::operator==(const ViewParams& rhs) const { - return std::tie(base_layer, num_layers, base_level, num_levels, target) == - std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels, rhs.target); -} - -bool ViewParams::operator!=(const ViewParams& rhs) const { - return !operator==(rhs); -} - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h deleted file mode 100644 index 90a8bb0ae..000000000 --- a/src/video_core/texture_cache/surface_view.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <functional> - -#include "common/common_types.h" -#include "video_core/surface.h" -#include "video_core/texture_cache/surface_params.h" - -namespace VideoCommon { - -struct ViewParams { - constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, - u32 num_layers, u32 base_level, u32 num_levels) - : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, - num_levels{num_levels} {} - - std::size_t Hash() const; - - bool operator==(const ViewParams& rhs) const; - bool operator!=(const ViewParams& rhs) const; - - bool IsLayered() const { - switch (target) { - case VideoCore::Surface::SurfaceTarget::Texture1DArray: - case VideoCore::Surface::SurfaceTarget::Texture2DArray: - case VideoCore::Surface::SurfaceTarget::TextureCubemap: - case VideoCore::Surface::SurfaceTarget::TextureCubeArray: - return true; - default: - return false; - } - } - - VideoCore::Surface::SurfaceTarget target{}; - u32 base_layer{}; - u32 num_layers{}; - u32 base_level{}; - u32 num_levels{}; -}; - -class ViewBase { -public: - constexpr explicit ViewBase(const ViewParams& params) : params{params} {} - - constexpr const ViewParams& GetViewParams() const { - return params; - } - -protected: - ViewParams params; -}; - -} // namespace VideoCommon - -namespace std { - -template <> -struct hash<VideoCommon::ViewParams> { - std::size_t operator()(const VideoCommon::ViewParams& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 96c4e4cc2..b1da69971 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -6,1304 +6,1446 @@ #include <algorithm> #include <array> -#include <list> +#include <bit> #include <memory> #include <mutex> -#include <set> -#include <tuple> +#include <optional> +#include <span> +#include <type_traits> #include <unordered_map> +#include <utility> #include <vector> #include <boost/container/small_vector.hpp> -#include <boost/icl/interval_map.hpp> -#include <boost/range/iterator_range.hpp> -#include "common/assert.h" +#include "common/alignment.h" +#include "common/common_funcs.h" #include "common/common_types.h" -#include "common/math_util.h" -#include "core/core.h" -#include "core/memory.h" -#include "core/settings.h" +#include "common/logging/log.h" #include "video_core/compatible_formats.h" +#include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" #include "video_core/surface.h" -#include "video_core/texture_cache/copy_params.h" +#include "video_core/texture_cache/descriptor_table.h" #include "video_core/texture_cache/format_lookup_table.h" -#include "video_core/texture_cache/surface_base.h" -#include "video_core/texture_cache/surface_params.h" -#include "video_core/texture_cache/surface_view.h" - -namespace Tegra::Texture { -struct FullTextureInfo; -} - -namespace VideoCore { -class RasterizerInterface; -} +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/image_base.h" +#include "video_core/texture_cache/image_info.h" +#include "video_core/texture_cache/image_view_base.h" +#include "video_core/texture_cache/image_view_info.h" +#include "video_core/texture_cache/render_targets.h" +#include "video_core/texture_cache/samples_helper.h" +#include "video_core/texture_cache/slot_vector.h" +#include "video_core/texture_cache/types.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/texture.h" namespace VideoCommon { -using VideoCore::Surface::FormatCompatibility; +using Tegra::Texture::SwizzleSource; +using Tegra::Texture::TextureType; +using Tegra::Texture::TICEntry; +using Tegra::Texture::TSCEntry; +using VideoCore::Surface::GetFormatType; +using VideoCore::Surface::IsCopyCompatible; using VideoCore::Surface::PixelFormat; -using VideoCore::Surface::SurfaceTarget; -using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::SurfaceType; -template <typename TSurface, typename TView> +template <class P> class TextureCache { - using VectorSurface = boost::container::small_vector<TSurface, 1>; + /// Address shift for caching images into a hash table + static constexpr u64 PAGE_BITS = 20; + + /// Enables debugging features to the texture cache + static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; + /// Implement blits as copies between framebuffers + static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS; + /// True when some copies have to be emulated + static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; + + /// Image view ID for null descriptors + static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0}; + /// Sampler ID for bugged sampler ids + static constexpr SamplerId NULL_SAMPLER_ID{0}; + + using Runtime = typename P::Runtime; + using Image = typename P::Image; + using ImageAlloc = typename P::ImageAlloc; + using ImageView = typename P::ImageView; + using Sampler = typename P::Sampler; + using Framebuffer = typename P::Framebuffer; + + struct BlitImages { + ImageId dst_id; + ImageId src_id; + PixelFormat dst_format; + PixelFormat src_format; + }; + + template <typename T> + struct IdentityHash { + [[nodiscard]] size_t operator()(T value) const noexcept { + return static_cast<size_t>(value); + } + }; public: - void InvalidateRegion(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&, + Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&); - for (const auto& surface : GetSurfacesInRegion(addr, size)) { - Unregister(surface); - } - } + /// Notify the cache that a new frame has been queued + void TickFrame(); - void OnCPUWrite(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + /// Return a constant reference to the given image view id + [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept; - for (const auto& surface : GetSurfacesInRegion(addr, size)) { - if (surface->IsMemoryMarked()) { - UnmarkMemory(surface); - surface->SetSyncPending(true); - marked_for_unregister.emplace_back(surface); - } - } - } + /// Return a reference to the given image view id + [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept; - void SyncGuestHost() { - std::lock_guard lock{mutex}; + /// Fill image_view_ids with the graphics images in indices + void FillGraphicsImageViews(std::span<const u32> indices, + std::span<ImageViewId> image_view_ids); - for (const auto& surface : marked_for_unregister) { - if (surface->IsRegistered()) { - surface->SetSyncPending(false); - Unregister(surface); - } - } - marked_for_unregister.clear(); - } + /// Fill image_view_ids with the compute images in indices + void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids); - /** - * Guarantees that rendertargets don't unregister themselves if the - * collide. Protection is currently only done on 3D slices. - */ - void GuardRenderTargets(bool new_guard) { - guard_render_targets = new_guard; - } + /// Get the sampler from the graphics descriptor table in the specified index + Sampler* GetGraphicsSampler(u32 index); - void GuardSamplers(bool new_guard) { - guard_samplers = new_guard; - } + /// Get the sampler from the compute descriptor table in the specified index + Sampler* GetComputeSampler(u32 index); - void FlushRegion(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + /// Refresh the state for graphics image view and sampler descriptors + void SynchronizeGraphicsDescriptors(); - auto surfaces = GetSurfacesInRegion(addr, size); - if (surfaces.empty()) { - return; - } - std::sort(surfaces.begin(), surfaces.end(), [](const TSurface& a, const TSurface& b) { - return a->GetModificationTick() < b->GetModificationTick(); - }); - for (const auto& surface : surfaces) { - mutex.unlock(); - FlushSurface(surface); - mutex.lock(); - } - } + /// Refresh the state for compute image view and sampler descriptors + void SynchronizeComputeDescriptors(); - bool MustFlushRegion(VAddr addr, std::size_t size) { - std::lock_guard lock{mutex}; + /// Update bound render targets and upload memory if necessary + /// @param is_clear True when the render targets are being used for clears + void UpdateRenderTargets(bool is_clear); - const auto surfaces = GetSurfacesInRegion(addr, size); - return std::any_of(surfaces.cbegin(), surfaces.cend(), - [](const TSurface& surface) { return surface->IsModified(); }); - } + /// Find a framebuffer with the currently bound render targets + /// UpdateRenderTargets should be called before this + Framebuffer* GetFramebuffer(); - TView GetTextureSurface(const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry) { - std::lock_guard lock{mutex}; - const auto gpu_addr{tic.Address()}; - if (!gpu_addr) { - return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); - } + /// Mark images in a range as modified from the CPU + void WriteMemory(VAddr cpu_addr, size_t size); - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - if (!cpu_addr) { - return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); - } + /// Download contents of host images to guest memory in a region + void DownloadMemory(VAddr cpu_addr, size_t size); - if (!IsTypeCompatible(tic.texture_type, entry)) { - return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); - } + /// Remove images in a region + void UnmapMemory(VAddr cpu_addr, size_t size); - const auto params{SurfaceParams::CreateForTexture(format_lookup_table, tic, entry)}; - const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false); - if (guard_samplers) { - sampled_textures.push_back(surface); - } - return view; - } + /// Blit an image with the given parameters + void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Config& copy); - TView GetImageSurface(const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Image& entry) { - std::lock_guard lock{mutex}; - const auto gpu_addr{tic.Address()}; - if (!gpu_addr) { - return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); - } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - if (!cpu_addr) { - return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); - } - const auto params{SurfaceParams::CreateForImage(format_lookup_table, tic, entry)}; - const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false); - if (guard_samplers) { - sampled_textures.push_back(surface); - } - return view; - } + /// Invalidate the contents of the color buffer index + /// These contents become unspecified, the cache can assume aggressive optimizations. + void InvalidateColorBuffer(size_t index); - bool TextureBarrier() { - const bool any_rt = - std::any_of(sampled_textures.begin(), sampled_textures.end(), - [](const auto& surface) { return surface->IsRenderTarget(); }); - sampled_textures.clear(); - return any_rt; - } + /// Invalidate the contents of the depth buffer + /// These contents become unspecified, the cache can assume aggressive optimizations. + void InvalidateDepthBuffer(); - TView GetDepthBufferSurface(bool preserve_contents) { - std::lock_guard lock{mutex}; - auto& maxwell3d = system.GPU().Maxwell3D(); - if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) { - return depth_buffer.view; - } - maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false; + /// Try to find a cached image view in the given CPU address + [[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr); - const auto& regs{maxwell3d.regs}; - const auto gpu_addr{regs.zeta.Address()}; - if (!gpu_addr || !regs.zeta_enable) { - SetEmptyDepthBuffer(); - return {}; - } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - if (!cpu_addr) { - SetEmptyDepthBuffer(); - return {}; - } - const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)}; - auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true); - if (depth_buffer.target) - depth_buffer.target->MarkAsRenderTarget(false, NO_RT); - depth_buffer.target = surface_view.first; - depth_buffer.view = surface_view.second; - if (depth_buffer.target) - depth_buffer.target->MarkAsRenderTarget(true, DEPTH_RT); - return surface_view.second; - } - - TView GetColorBufferSurface(std::size_t index, bool preserve_contents) { - std::lock_guard lock{mutex}; - ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - auto& maxwell3d = system.GPU().Maxwell3D(); - if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index]) { - return render_targets[index].view; - } - maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index] = false; + /// Return true when there are uncommitted images to be downloaded + [[nodiscard]] bool HasUncommittedFlushes() const noexcept; - const auto& regs{maxwell3d.regs}; - if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || - regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - SetEmptyColorBuffer(index); - return {}; - } + /// Return true when the caller should wait for async downloads + [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept; - const auto& config{regs.rt[index]}; - const auto gpu_addr{config.Address()}; - if (!gpu_addr) { - SetEmptyColorBuffer(index); - return {}; - } + /// Commit asynchronous downloads + void CommitAsyncFlushes(); - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - if (!cpu_addr) { - SetEmptyColorBuffer(index); - return {}; - } + /// Pop asynchronous downloads + void PopAsyncFlushes(); + + /// Return true when a CPU region is modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); - auto surface_view = - GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), - preserve_contents, true); - if (render_targets[index].target) { - auto& surface = render_targets[index].target; - surface->MarkAsRenderTarget(false, NO_RT); - const auto& cr_params = surface->GetSurfaceParams(); - if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - AsyncFlushSurface(surface); + std::mutex mutex; + +private: + /// Iterate over all page indices in a range + template <typename Func> + static void ForEachPage(VAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; + const u64 page_end = (addr + size - 1) >> PAGE_BITS; + for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); } } - render_targets[index].target = surface_view.first; - render_targets[index].view = surface_view.second; - if (render_targets[index].target) - render_targets[index].target->MarkAsRenderTarget(true, static_cast<u32>(index)); - return surface_view.second; } - void MarkColorBufferInUse(std::size_t index) { - if (auto& render_target = render_targets[index].target) { - render_target->MarkAsModified(true, Tick()); - } - } + /// Fills image_view_ids in the image views in indices + void FillImageViews(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices, + std::span<ImageViewId> image_view_ids); - void MarkDepthBufferInUse() { - if (depth_buffer.target) { - depth_buffer.target->MarkAsModified(true, Tick()); - } - } + /// Find or create an image view in the guest descriptor table + ImageViewId VisitImageView(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, u32 index); - void SetEmptyDepthBuffer() { - if (depth_buffer.target == nullptr) { - return; - } - depth_buffer.target->MarkAsRenderTarget(false, NO_RT); - depth_buffer.target = nullptr; - depth_buffer.view = nullptr; - } + /// Find or create a framebuffer with the given render target parameters + FramebufferId GetFramebufferId(const RenderTargets& key); - void SetEmptyColorBuffer(std::size_t index) { - if (render_targets[index].target == nullptr) { - return; - } - render_targets[index].target->MarkAsRenderTarget(false, NO_RT); - render_targets[index].target = nullptr; - render_targets[index].view = nullptr; - } - - void DoFermiCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, - const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, - const Tegra::Engines::Fermi2D::Config& copy_config) { - std::lock_guard lock{mutex}; - SurfaceParams src_params = SurfaceParams::CreateForFermiCopySurface(src_config); - SurfaceParams dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config); - const GPUVAddr src_gpu_addr = src_config.Address(); - const GPUVAddr dst_gpu_addr = dst_config.Address(); - DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr); - - const auto& memory_manager = system.GPU().MemoryManager(); - const std::optional<VAddr> dst_cpu_addr = memory_manager.GpuToCpuAddress(dst_gpu_addr); - const std::optional<VAddr> src_cpu_addr = memory_manager.GpuToCpuAddress(src_gpu_addr); - std::pair dst_surface = GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, true, false); - TView src_surface = GetSurface(src_gpu_addr, *src_cpu_addr, src_params, true, false).second; - ImageBlit(src_surface, dst_surface.second, copy_config); - dst_surface.first->MarkAsModified(true, Tick()); - } - - TSurface TryFindFramebufferSurface(VAddr addr) const { - if (!addr) { - return nullptr; - } - const VAddr page = addr >> registry_page_bits; - const auto it = registry.find(page); - if (it == registry.end()) { - return nullptr; - } - const auto& list = it->second; - const auto found = std::find_if(list.begin(), list.end(), [addr](const auto& surface) { - return surface->GetCpuAddr() == addr; - }); - return found != list.end() ? *found : nullptr; - } + /// Refresh the contents (pixel data) of an image + void RefreshContents(Image& image); - u64 Tick() { - return ++ticks; - } + /// Upload data from guest to an image + template <typename StagingBuffer> + void UploadImageContents(Image& image, StagingBuffer& staging_buffer); - void CommitAsyncFlushes() { - committed_flushes.push_back(uncommitted_flushes); - uncommitted_flushes.reset(); - } + /// Find or create an image view from a guest descriptor + [[nodiscard]] ImageViewId FindImageView(const TICEntry& config); - bool HasUncommittedFlushes() const { - return uncommitted_flushes != nullptr; - } + /// Create a new image view from a guest descriptor + [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config); - bool ShouldWaitAsyncFlushes() const { - return !committed_flushes.empty() && committed_flushes.front() != nullptr; - } + /// Find or create an image from the given parameters + [[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options = RelaxedOptions{}); - void PopAsyncFlushes() { - if (committed_flushes.empty()) { - return; - } - auto& flush_list = committed_flushes.front(); - if (!flush_list) { - committed_flushes.pop_front(); - return; - } - for (TSurface& surface : *flush_list) { - FlushSurface(surface); - } - committed_flushes.pop_front(); - } + /// Find an image from the given parameters + [[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options); -protected: - explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - bool is_astc_supported) - : system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} { - for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { - SetEmptyColorBuffer(i); - } + /// Create an image from the given parameters + [[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options); - SetEmptyDepthBuffer(); - staging_cache.SetSize(2); + /// Create a new image and join perfectly matching existing images + /// Remove joined images from the cache + [[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); - const auto make_siblings = [this](PixelFormat a, PixelFormat b) { - siblings_table[static_cast<std::size_t>(a)] = b; - siblings_table[static_cast<std::size_t>(b)] = a; - }; - std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid); - make_siblings(PixelFormat::D16_UNORM, PixelFormat::R16_UNORM); - make_siblings(PixelFormat::D32_FLOAT, PixelFormat::R32_FLOAT); - make_siblings(PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::R32G32_FLOAT); + /// Return a blit image pair from the given guest blit parameters + [[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Surface& src); - sampled_textures.reserve(64); - } + /// Find or create a sampler from a guest descriptor sampler + [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); - ~TextureCache() = default; + /// Find or create an image view for the given color buffer index + [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear); - virtual TSurface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) = 0; + /// Find or create an image view for the depth buffer + [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear); - virtual void ImageCopy(TSurface& src_surface, TSurface& dst_surface, - const CopyParams& copy_params) = 0; + /// Find or create a view for a render target with the given image parameters + [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, + bool is_clear); - virtual void ImageBlit(TView& src_view, TView& dst_view, - const Tegra::Engines::Fermi2D::Config& copy_config) = 0; + /// Iterates over all the images in a region calling func + template <typename Func> + void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func); - // Depending on the backend, a buffer copy can be slow as it means deoptimizing the texture - // and reading it from a separate buffer. - virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0; + /// Find or create an image view in the given image with the passed parameters + [[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info); - void ManageRenderTargetUnregister(TSurface& surface) { - auto& dirty = system.GPU().Maxwell3D().dirty; - const u32 index = surface->GetRenderTarget(); - if (index == DEPTH_RT) { - dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true; - } else { - dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index] = true; - } - dirty.flags[VideoCommon::Dirty::RenderTargets] = true; + /// Register image in the page table + void RegisterImage(ImageId image); + + /// Unregister image from the page table + void UnregisterImage(ImageId image); + + /// Track CPU reads and writes for image + void TrackImage(ImageBase& image); + + /// Stop tracking CPU reads and writes for image + void UntrackImage(ImageBase& image); + + /// Delete image from the cache + void DeleteImage(ImageId image); + + /// Remove image views references from the cache + void RemoveImageViewReferences(std::span<const ImageViewId> removed_views); + + /// Remove framebuffers using the given image views from the cache + void RemoveFramebuffers(std::span<const ImageViewId> removed_views); + + /// Mark an image as modified from the GPU + void MarkModification(ImageBase& image) noexcept; + + /// Synchronize image aliases, copying data if needed + void SynchronizeAliases(ImageId image_id); + + /// Prepare an image to be used + void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); + + /// Prepare an image view to be used + void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); + + /// Execute copies from one image to the other, even if they are incompatible + void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies); + + /// Bind an image view as render target, downloading resources preemtively if needed + void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id); + + /// Create a render target from a given image and image view parameters + [[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage( + ImageId, const ImageViewInfo& view_info); + + /// Returns true if the current clear parameters clear the whole image of a given image view + [[nodiscard]] bool IsFullClear(ImageViewId id); + + Runtime& runtime; + VideoCore::RasterizerInterface& rasterizer; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; + + DescriptorTable<TICEntry> graphics_image_table{gpu_memory}; + DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory}; + std::vector<SamplerId> graphics_sampler_ids; + std::vector<ImageViewId> graphics_image_view_ids; + + DescriptorTable<TICEntry> compute_image_table{gpu_memory}; + DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory}; + std::vector<SamplerId> compute_sampler_ids; + std::vector<ImageViewId> compute_image_view_ids; + + RenderTargets render_targets; + + std::unordered_map<TICEntry, ImageViewId> image_views; + std::unordered_map<TSCEntry, SamplerId> samplers; + std::unordered_map<RenderTargets, FramebufferId> framebuffers; + + std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table; + + bool has_deleted_images = false; + + SlotVector<Image> slot_images; + SlotVector<ImageView> slot_image_views; + SlotVector<ImageAlloc> slot_image_allocs; + SlotVector<Sampler> slot_samplers; + SlotVector<Framebuffer> slot_framebuffers; + + // TODO: This data structure is not optimal and it should be reworked + std::vector<ImageId> uncommitted_downloads; + std::queue<std::vector<ImageId>> committed_downloads; + + static constexpr size_t TICKS_TO_DESTROY = 6; + DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images; + DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view; + DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers; + + std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table; + + u64 modification_tick = 0; + u64 frame_tick = 0; +}; + +template <class P> +TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, + Tegra::Engines::KeplerCompute& kepler_compute_, + Tegra::MemoryManager& gpu_memory_) + : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, + kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} { + // Configure null sampler + TSCEntry sampler_descriptor{}; + sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear); + sampler_descriptor.mag_filter.Assign(Tegra::Texture::TextureFilter::Linear); + sampler_descriptor.mipmap_filter.Assign(Tegra::Texture::TextureMipmapFilter::Linear); + sampler_descriptor.cubemap_anisotropy.Assign(1); + + // Make sure the first index is reserved for the null resources + // This way the null resource becomes a compile time constant + void(slot_image_views.insert(runtime, NullImageParams{})); + void(slot_samplers.insert(runtime, sampler_descriptor)); +} + +template <class P> +void TextureCache<P>::TickFrame() { + // Tick sentenced resources in this order to ensure they are destroyed in the right order + sentenced_images.Tick(); + sentenced_framebuffers.Tick(); + sentenced_image_view.Tick(); + ++frame_tick; +} + +template <class P> +const typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) const noexcept { + return slot_image_views[id]; +} + +template <class P> +typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) noexcept { + return slot_image_views[id]; +} + +template <class P> +void TextureCache<P>::FillGraphicsImageViews(std::span<const u32> indices, + std::span<ImageViewId> image_view_ids) { + FillImageViews(graphics_image_table, graphics_image_view_ids, indices, image_view_ids); +} + +template <class P> +void TextureCache<P>::FillComputeImageViews(std::span<const u32> indices, + std::span<ImageViewId> image_view_ids) { + FillImageViews(compute_image_table, compute_image_view_ids, indices, image_view_ids); +} + +template <class P> +typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { + [[unlikely]] if (index > graphics_sampler_table.Limit()) { + LOG_ERROR(HW_GPU, "Invalid sampler index={}", index); + return &slot_samplers[NULL_SAMPLER_ID]; + } + const auto [descriptor, is_new] = graphics_sampler_table.Read(index); + SamplerId& id = graphics_sampler_ids[index]; + [[unlikely]] if (is_new) { + id = FindSampler(descriptor); } + return &slot_samplers[id]; +} - void Register(TSurface surface) { - const GPUVAddr gpu_addr = surface->GetGpuAddr(); - const std::size_t size = surface->GetSizeInBytes(); - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - if (!cpu_addr) { - LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}", - gpu_addr); - return; - } - surface->SetCpuAddr(*cpu_addr); - RegisterInnerCache(surface); - surface->MarkAsRegistered(true); - surface->SetMemoryMarked(true); - rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1); +template <class P> +typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { + [[unlikely]] if (index > compute_sampler_table.Limit()) { + LOG_ERROR(HW_GPU, "Invalid sampler index={}", index); + return &slot_samplers[NULL_SAMPLER_ID]; } + const auto [descriptor, is_new] = compute_sampler_table.Read(index); + SamplerId& id = compute_sampler_ids[index]; + [[unlikely]] if (is_new) { + id = FindSampler(descriptor); + } + return &slot_samplers[id]; +} - void UnmarkMemory(TSurface surface) { - if (!surface->IsMemoryMarked()) { - return; - } - const std::size_t size = surface->GetSizeInBytes(); - const VAddr cpu_addr = surface->GetCpuAddr(); - rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1); - surface->SetMemoryMarked(false); +template <class P> +void TextureCache<P>::SynchronizeGraphicsDescriptors() { + using SamplerIndex = Tegra::Engines::Maxwell3D::Regs::SamplerIndex; + const bool linked_tsc = maxwell3d.regs.sampler_index == SamplerIndex::ViaHeaderIndex; + const u32 tic_limit = maxwell3d.regs.tic.limit; + const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d.regs.tsc.limit; + if (graphics_sampler_table.Synchornize(maxwell3d.regs.tsc.Address(), tsc_limit)) { + graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); + } + if (graphics_image_table.Synchornize(maxwell3d.regs.tic.Address(), tic_limit)) { + graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); } +} - void Unregister(TSurface surface) { - if (guard_render_targets && surface->IsProtected()) { - return; - } - if (!guard_render_targets && surface->IsRenderTarget()) { - ManageRenderTargetUnregister(surface); - } - UnmarkMemory(surface); - if (surface->IsSyncPending()) { - marked_for_unregister.remove(surface); - surface->SetSyncPending(false); - } - UnregisterInnerCache(surface); - surface->MarkAsRegistered(false); - ReserveSurface(surface->GetSurfaceParams(), surface); +template <class P> +void TextureCache<P>::SynchronizeComputeDescriptors() { + const bool linked_tsc = kepler_compute.launch_description.linked_tsc; + const u32 tic_limit = kepler_compute.regs.tic.limit; + const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute.regs.tsc.limit; + const GPUVAddr tsc_gpu_addr = kepler_compute.regs.tsc.Address(); + if (compute_sampler_table.Synchornize(tsc_gpu_addr, tsc_limit)) { + compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); + } + if (compute_image_table.Synchornize(kepler_compute.regs.tic.Address(), tic_limit)) { + compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); } +} - TSurface GetUncachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) { - if (const auto surface = TryGetReservedSurface(params); surface) { - surface->SetGpuAddr(gpu_addr); - return surface; - } - // No reserved surface available, create a new one and reserve it - auto new_surface{CreateSurface(gpu_addr, params)}; - return new_surface; +template <class P> +void TextureCache<P>::UpdateRenderTargets(bool is_clear) { + using namespace VideoCommon::Dirty; + auto& flags = maxwell3d.dirty.flags; + if (!flags[Dirty::RenderTargets]) { + return; } + flags[Dirty::RenderTargets] = false; - Core::System& system; - const bool is_astc_supported; + // Render target control is used on all render targets, so force look ups when this one is up + const bool force = flags[Dirty::RenderTargetControl]; + flags[Dirty::RenderTargetControl] = false; -private: - enum class RecycleStrategy : u32 { - Ignore = 0, - Flush = 1, - BufferCopy = 3, - }; + for (size_t index = 0; index < NUM_RT; ++index) { + ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; + if (flags[Dirty::ColorBuffer0 + index] || force) { + flags[Dirty::ColorBuffer0 + index] = false; + BindRenderTarget(&color_buffer_id, FindColorBuffer(index, is_clear)); + } + PrepareImageView(color_buffer_id, true, is_clear && IsFullClear(color_buffer_id)); + } + if (flags[Dirty::ZetaBuffer] || force) { + flags[Dirty::ZetaBuffer] = false; + BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer(is_clear)); + } + const ImageViewId depth_buffer_id = render_targets.depth_buffer_id; + PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id)); - enum class DeductionType : u32 { - DeductionComplete, - DeductionIncomplete, - DeductionFailed, + for (size_t index = 0; index < NUM_RT; ++index) { + render_targets.draw_buffers[index] = static_cast<u8>(maxwell3d.regs.rt_control.Map(index)); + } + render_targets.size = Extent2D{ + maxwell3d.regs.render_area.width, + maxwell3d.regs.render_area.height, }; +} - struct Deduction { - DeductionType type{DeductionType::DeductionFailed}; - TSurface surface{}; +template <class P> +typename P::Framebuffer* TextureCache<P>::GetFramebuffer() { + return &slot_framebuffers[GetFramebufferId(render_targets)]; +} - bool Failed() const { - return type == DeductionType::DeductionFailed; - } +template <class P> +void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, + std::span<const u32> indices, + std::span<ImageViewId> image_view_ids) { + ASSERT(indices.size() <= image_view_ids.size()); + do { + has_deleted_images = false; + std::ranges::transform(indices, image_view_ids.begin(), [&](u32 index) { + return VisitImageView(table, cached_image_view_ids, index); + }); + } while (has_deleted_images); +} - bool Incomplete() const { - return type == DeductionType::DeductionIncomplete; - } +template <class P> +ImageViewId TextureCache<P>::VisitImageView(DescriptorTable<TICEntry>& table, + std::span<ImageViewId> cached_image_view_ids, + u32 index) { + if (index > table.Limit()) { + LOG_ERROR(HW_GPU, "Invalid image view index={}", index); + return NULL_IMAGE_VIEW_ID; + } + const auto [descriptor, is_new] = table.Read(index); + ImageViewId& image_view_id = cached_image_view_ids[index]; + if (is_new) { + image_view_id = FindImageView(descriptor); + } + if (image_view_id != NULL_IMAGE_VIEW_ID) { + PrepareImageView(image_view_id, false, false); + } + return image_view_id; +} - bool IsDepth() const { - return surface->GetSurfaceParams().IsPixelFormatZeta(); - } - }; +template <class P> +FramebufferId TextureCache<P>::GetFramebufferId(const RenderTargets& key) { + const auto [pair, is_new] = framebuffers.try_emplace(key); + FramebufferId& framebuffer_id = pair->second; + if (!is_new) { + return framebuffer_id; + } + std::array<ImageView*, NUM_RT> color_buffers; + std::ranges::transform(key.color_buffer_ids, color_buffers.begin(), + [this](ImageViewId id) { return id ? &slot_image_views[id] : nullptr; }); + ImageView* const depth_buffer = + key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr; + framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key); + return framebuffer_id; +} - /** - * Takes care of selecting a proper strategy to deal with a texture recycle. - * - * @param overlaps The overlapping surfaces registered in the cache. - * @param params The parameters on the new surface. - * @param gpu_addr The starting address of the new surface. - * @param untopological Indicates to the recycler that the texture has no way - * to match the overlaps due to topological reasons. - **/ - RecycleStrategy PickStrategy(VectorSurface& overlaps, const SurfaceParams& params, - const GPUVAddr gpu_addr, const MatchTopologyResult untopological) { - if (Settings::IsGPULevelExtreme()) { - return RecycleStrategy::Flush; - } - // 3D Textures decision - if (params.target == SurfaceTarget::Texture3D) { - return RecycleStrategy::Flush; - } - for (const auto& s : overlaps) { - const auto& s_params = s->GetSurfaceParams(); - if (s_params.target == SurfaceTarget::Texture3D) { - return RecycleStrategy::Flush; - } - } - // Untopological decision - if (untopological == MatchTopologyResult::CompressUnmatch) { - return RecycleStrategy::Flush; - } - if (untopological == MatchTopologyResult::FullMatch && !params.is_tiled) { - return RecycleStrategy::Flush; - } - return RecycleStrategy::Ignore; - } - - /** - * Used to decide what to do with textures we can't resolve in the cache It has 2 implemented - * strategies: Ignore and Flush. - * - * - Ignore: Just unregisters all the overlaps and loads the new texture. - * - Flush: Flushes all the overlaps into memory and loads the new surface from that data. - * - * @param overlaps The overlapping surfaces registered in the cache. - * @param params The parameters for the new surface. - * @param gpu_addr The starting address of the new surface. - * @param preserve_contents Indicates that the new surface should be loaded from memory or left - * blank. - * @param untopological Indicates to the recycler that the texture has no way to match the - * overlaps due to topological reasons. - **/ - std::pair<TSurface, TView> RecycleSurface(VectorSurface& overlaps, const SurfaceParams& params, - const GPUVAddr gpu_addr, const bool preserve_contents, - const MatchTopologyResult untopological) { - const bool do_load = preserve_contents && Settings::IsGPULevelExtreme(); - for (auto& surface : overlaps) { - Unregister(surface); - } - switch (PickStrategy(overlaps, params, gpu_addr, untopological)) { - case RecycleStrategy::Ignore: { - return InitializeSurface(gpu_addr, params, do_load); - } - case RecycleStrategy::Flush: { - std::sort(overlaps.begin(), overlaps.end(), - [](const TSurface& a, const TSurface& b) -> bool { - return a->GetModificationTick() < b->GetModificationTick(); - }); - for (auto& surface : overlaps) { - FlushSurface(surface); - } - return InitializeSurface(gpu_addr, params, preserve_contents); +template <class P> +void TextureCache<P>::WriteMemory(VAddr cpu_addr, size_t size) { + ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) { + if (True(image.flags & ImageFlagBits::CpuModified)) { + return; } - case RecycleStrategy::BufferCopy: { - auto new_surface = GetUncachedSurface(gpu_addr, params); - BufferCopy(overlaps[0], new_surface); - return {new_surface, new_surface->GetMainView()}; + image.flags |= ImageFlagBits::CpuModified; + UntrackImage(image); + }); +} + +template <class P> +void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) { + std::vector<ImageId> images; + ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { + // Skip images that were not modified from the GPU + if (False(image.flags & ImageFlagBits::GpuModified)) { + return; } - default: { - UNIMPLEMENTED_MSG("Unimplemented Texture Cache Recycling Strategy!"); - return InitializeSurface(gpu_addr, params, do_load); + // Skip images that .are. modified from the CPU + // We don't want to write sensitive data from the guest + if (True(image.flags & ImageFlagBits::CpuModified)) { + return; } + if (image.info.num_samples > 1) { + LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); + return; } + image.flags &= ~ImageFlagBits::GpuModified; + images.push_back(image_id); + }); + if (images.empty()) { + return; } + std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) { + return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick; + }); + for (const ImageId image_id : images) { + Image& image = slot_images[image_id]; + auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); + const auto copies = FullDownloadCopies(image.info); + image.DownloadMemory(map, copies); + runtime.Finish(); + SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span); + } +} - /** - * Takes a single surface and recreates into another that may differ in - * format, target or width alignment. - * - * @param current_surface The registered surface in the cache which we want to convert. - * @param params The new surface params which we'll use to recreate the surface. - * @param is_render Whether or not the surface is a render target. - **/ - std::pair<TSurface, TView> RebuildSurface(TSurface current_surface, const SurfaceParams& params, - bool is_render) { - const auto gpu_addr = current_surface->GetGpuAddr(); - const auto& cr_params = current_surface->GetSurfaceParams(); - TSurface new_surface; - if (cr_params.pixel_format != params.pixel_format && !is_render && - GetSiblingFormat(cr_params.pixel_format) == params.pixel_format) { - SurfaceParams new_params = params; - new_params.pixel_format = cr_params.pixel_format; - new_params.type = cr_params.type; - new_surface = GetUncachedSurface(gpu_addr, new_params); - } else { - new_surface = GetUncachedSurface(gpu_addr, params); - } - const SurfaceParams& final_params = new_surface->GetSurfaceParams(); - if (cr_params.type != final_params.type) { - if (Settings::IsGPULevelExtreme()) { - BufferCopy(current_surface, new_surface); - } - } else { - std::vector<CopyParams> bricks = current_surface->BreakDown(final_params); - for (auto& brick : bricks) { - TryCopyImage(current_surface, new_surface, brick); - } - } - Unregister(current_surface); - Register(new_surface); - new_surface->MarkAsModified(current_surface->IsModified(), Tick()); - return {new_surface, new_surface->GetMainView()}; - } - - /** - * Takes a single surface and checks with the new surface's params if it's an exact - * match, we return the main view of the registered surface. If its formats don't - * match, we rebuild the surface. We call this last method a `Mirage`. If formats - * match but the targets don't, we create an overview View of the registered surface. - * - * @param current_surface The registered surface in the cache which we want to convert. - * @param params The new surface params which we want to check. - * @param is_render Whether or not the surface is a render target. - **/ - std::pair<TSurface, TView> ManageStructuralMatch(TSurface current_surface, - const SurfaceParams& params, bool is_render) { - const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); - const bool matches_target = current_surface->MatchTarget(params.target); - const auto match_check = [&]() -> std::pair<TSurface, TView> { - if (matches_target) { - return {current_surface, current_surface->GetMainView()}; - } - return {current_surface, current_surface->EmplaceOverview(params)}; - }; - if (!is_mirage) { - return match_check(); - } - if (!is_render && GetSiblingFormat(current_surface->GetFormat()) == params.pixel_format) { - return match_check(); - } - return RebuildSurface(current_surface, params, is_render); - } - - /** - * Unlike RebuildSurface where we know whether or not registered surfaces match the candidate - * in some way, we have no guarantees here. We try to see if the overlaps are sublayers/mipmaps - * of the new surface, if they all match we end up recreating a surface for them, - * else we return nothing. - * - * @param overlaps The overlapping surfaces registered in the cache. - * @param params The parameters on the new surface. - * @param gpu_addr The starting address of the new surface. - **/ - std::optional<std::pair<TSurface, TView>> TryReconstructSurface(VectorSurface& overlaps, - const SurfaceParams& params, - GPUVAddr gpu_addr) { - if (params.target == SurfaceTarget::Texture3D) { - return std::nullopt; - } - const auto test_modified = [](TSurface& surface) { return surface->IsModified(); }; - TSurface new_surface = GetUncachedSurface(gpu_addr, params); +template <class P> +void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) { + std::vector<ImageId> deleted_images; + ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); + for (const ImageId id : deleted_images) { + Image& image = slot_images[id]; + if (True(image.flags & ImageFlagBits::Tracked)) { + UntrackImage(image); + } + UnregisterImage(id); + DeleteImage(id); + } +} - if (std::none_of(overlaps.begin(), overlaps.end(), test_modified)) { - LoadSurface(new_surface); - for (const auto& surface : overlaps) { - Unregister(surface); - } - Register(new_surface); - return {{new_surface, new_surface->GetMainView()}}; - } +template <class P> +void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Config& copy) { + const BlitImages images = GetBlitImages(dst, src); + const ImageId dst_id = images.dst_id; + const ImageId src_id = images.src_id; + PrepareImage(src_id, false, false); + PrepareImage(dst_id, true, false); + + ImageBase& dst_image = slot_images[dst_id]; + const ImageBase& src_image = slot_images[src_id]; + + // TODO: Deduplicate + const std::optional dst_base = dst_image.TryFindBase(dst.Address()); + const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}}; + const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range); + const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); + const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples); + const std::array src_region{ + Offset2D{.x = copy.src_x0 >> src_samples_x, .y = copy.src_y0 >> src_samples_y}, + Offset2D{.x = copy.src_x1 >> src_samples_x, .y = copy.src_y1 >> src_samples_y}, + }; - std::size_t passed_tests = 0; - for (auto& surface : overlaps) { - const SurfaceParams& src_params = surface->GetSurfaceParams(); - const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())}; - if (!mipmap_layer) { - continue; - } - const auto [base_layer, base_mipmap] = *mipmap_layer; - if (new_surface->GetMipmapSize(base_mipmap) != surface->GetMipmapSize(0)) { - continue; - } - ++passed_tests; - - // Copy all mipmaps and layers - const u32 block_width = params.GetDefaultBlockWidth(); - const u32 block_height = params.GetDefaultBlockHeight(); - for (u32 mipmap = base_mipmap; mipmap < base_mipmap + src_params.num_levels; ++mipmap) { - const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap); - const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap); - if (width < block_width || height < block_height) { - // Current APIs forbid copying small compressed textures, avoid errors - break; - } - const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height, - src_params.depth); - TryCopyImage(surface, new_surface, copy_params); - } - } - if (passed_tests == 0) { - return std::nullopt; - } - if (Settings::IsGPULevelExtreme() && passed_tests != overlaps.size()) { - // In Accurate GPU all tests should pass, else we recycle - return std::nullopt; - } + const std::optional src_base = src_image.TryFindBase(src.Address()); + const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}}; + const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range); + const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info); + const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples); + const std::array dst_region{ + Offset2D{.x = copy.dst_x0 >> dst_samples_x, .y = copy.dst_y0 >> dst_samples_y}, + Offset2D{.x = copy.dst_x1 >> dst_samples_x, .y = copy.dst_y1 >> dst_samples_y}, + }; - const bool modified = std::any_of(overlaps.begin(), overlaps.end(), test_modified); - for (const auto& surface : overlaps) { - Unregister(surface); - } + // Always call this after src_framebuffer_id was queried, as the address might be invalidated. + Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id]; + if constexpr (FRAMEBUFFER_BLITS) { + // OpenGL blits from framebuffers, not images + Framebuffer* const src_framebuffer = &slot_framebuffers[src_framebuffer_id]; + runtime.BlitFramebuffer(dst_framebuffer, src_framebuffer, dst_region, src_region, + copy.filter, copy.operation); + } else { + // Vulkan can blit images, but it lacks format reinterpretations + // Provide a framebuffer in case it's necessary + ImageView& dst_view = slot_image_views[dst_view_id]; + ImageView& src_view = slot_image_views[src_view_id]; + runtime.BlitImage(dst_framebuffer, dst_view, src_view, dst_region, src_region, copy.filter, + copy.operation); + } +} - new_surface->MarkAsModified(modified, Tick()); - Register(new_surface); - return {{new_surface, new_surface->GetMainView()}}; - } - - /** - * Takes care of managing 3D textures and its slices. Does HLE methods for reconstructing the 3D - * textures within the GPU if possible. Falls back to LLE when it isn't possible to use any of - * the HLE methods. - * - * @param overlaps The overlapping surfaces registered in the cache. - * @param params The parameters on the new surface. - * @param gpu_addr The starting address of the new surface. - * @param cpu_addr The starting address of the new surface on physical memory. - * @param preserve_contents Indicates that the new surface should be loaded from memory or - * left blank. - */ - std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(VectorSurface& overlaps, - const SurfaceParams& params, - GPUVAddr gpu_addr, VAddr cpu_addr, - bool preserve_contents) { - if (params.target != SurfaceTarget::Texture3D) { - for (const auto& surface : overlaps) { - if (!surface->MatchTarget(params.target)) { - if (overlaps.size() == 1 && surface->GetCpuAddr() == cpu_addr) { - if (Settings::IsGPULevelExtreme()) { - return std::nullopt; - } - Unregister(surface); - return InitializeSurface(gpu_addr, params, preserve_contents); - } - return std::nullopt; - } - if (surface->GetCpuAddr() != cpu_addr) { - continue; - } - if (surface->MatchesStructure(params) == MatchStructureResult::FullMatch) { - return std::make_pair(surface, surface->GetMainView()); - } - } - return InitializeSurface(gpu_addr, params, preserve_contents); - } +template <class P> +void TextureCache<P>::InvalidateColorBuffer(size_t index) { + ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index]; + color_buffer_id = FindColorBuffer(index, false); + if (!color_buffer_id) { + LOG_ERROR(HW_GPU, "Invalidating invalid color buffer in index={}", index); + return; + } + // When invalidating a color buffer, the old contents are no longer relevant + ImageView& color_buffer = slot_image_views[color_buffer_id]; + Image& image = slot_images[color_buffer.image_id]; + image.flags &= ~ImageFlagBits::CpuModified; + image.flags &= ~ImageFlagBits::GpuModified; - if (params.num_levels > 1) { - // We can't handle mipmaps in 3D textures yet, better fallback to LLE approach - return std::nullopt; - } + runtime.InvalidateColorBuffer(color_buffer, index); +} - if (overlaps.size() == 1) { - const auto& surface = overlaps[0]; - const SurfaceParams& overlap_params = surface->GetSurfaceParams(); - // Don't attempt to render to textures with more than one level for now - // The texture has to be to the right or the sample address if we want to render to it - if (overlap_params.num_levels == 1 && cpu_addr >= surface->GetCpuAddr()) { - const u32 offset = static_cast<u32>(cpu_addr - surface->GetCpuAddr()); - const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset)); - if (slice < overlap_params.depth) { - auto view = surface->Emplace3DView(slice, params.depth, 0, 1); - return std::make_pair(std::move(surface), std::move(view)); - } - } - } +template <class P> +void TextureCache<P>::InvalidateDepthBuffer() { + ImageViewId& depth_buffer_id = render_targets.depth_buffer_id; + depth_buffer_id = FindDepthBuffer(false); + if (!depth_buffer_id) { + LOG_ERROR(HW_GPU, "Invalidating invalid depth buffer"); + return; + } + // When invalidating the depth buffer, the old contents are no longer relevant + ImageBase& image = slot_images[slot_image_views[depth_buffer_id].image_id]; + image.flags &= ~ImageFlagBits::CpuModified; + image.flags &= ~ImageFlagBits::GpuModified; - TSurface new_surface = GetUncachedSurface(gpu_addr, params); - bool modified = false; + ImageView& depth_buffer = slot_image_views[depth_buffer_id]; + runtime.InvalidateDepthBuffer(depth_buffer); +} - for (auto& surface : overlaps) { - const SurfaceParams& src_params = surface->GetSurfaceParams(); - if (src_params.target != SurfaceTarget::Texture2D || - src_params.height != params.height || - src_params.block_depth != params.block_depth || - src_params.block_height != params.block_height) { - return std::nullopt; - } - modified |= surface->IsModified(); - - const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr); - const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset)); - const u32 width = params.width; - const u32 height = params.height; - const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1); - TryCopyImage(surface, new_surface, copy_params); +template <class P> +typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) { + // TODO: Properly implement this + const auto it = page_table.find(cpu_addr >> PAGE_BITS); + if (it == page_table.end()) { + return nullptr; + } + const auto& image_ids = it->second; + for (const ImageId image_id : image_ids) { + const ImageBase& image = slot_images[image_id]; + if (image.cpu_addr != cpu_addr) { + continue; } - for (const auto& surface : overlaps) { - Unregister(surface); + if (image.image_view_ids.empty()) { + continue; } - new_surface->MarkAsModified(modified, Tick()); - Register(new_surface); - - TView view = new_surface->GetMainView(); - return std::make_pair(std::move(new_surface), std::move(view)); - } - - /** - * Gets the starting address and parameters of a candidate surface and tries - * to find a matching surface within the cache. This is done in 3 big steps: - * - * 1. Check the 1st Level Cache in order to find an exact match, if we fail, we move to step 2. - * - * 2. Check if there are any overlaps at all, if there are none, we just load the texture from - * memory else we move to step 3. - * - * 3. Consists of figuring out the relationship between the candidate texture and the - * overlaps. We divide the scenarios depending if there's 1 or many overlaps. If - * there's many, we just try to reconstruct a new surface out of them based on the - * candidate's parameters, if we fail, we recycle. When there's only 1 overlap then we - * have to check if the candidate is a view (layer/mipmap) of the overlap or if the - * registered surface is a mipmap/layer of the candidate. In this last case we reconstruct - * a new surface. - * - * @param gpu_addr The starting address of the candidate surface. - * @param params The parameters on the candidate surface. - * @param preserve_contents Indicates that the new surface should be loaded from memory or - * left blank. - * @param is_render Whether or not the surface is a render target. - **/ - std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const VAddr cpu_addr, - const SurfaceParams& params, bool preserve_contents, - bool is_render) { - // Step 1 - // Check Level 1 Cache for a fast structural match. If candidate surface - // matches at certain level we are pretty much done. - if (const auto iter = l1_cache.find(cpu_addr); iter != l1_cache.end()) { - TSurface& current_surface = iter->second; - const auto topological_result = current_surface->MatchesTopology(params); - if (topological_result != MatchTopologyResult::FullMatch) { - VectorSurface overlaps{current_surface}; - return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, - topological_result); - } + return &slot_image_views[image.image_view_ids.at(0)]; + } + return nullptr; +} - const auto struct_result = current_surface->MatchesStructure(params); - if (struct_result != MatchStructureResult::None) { - const auto& old_params = current_surface->GetSurfaceParams(); - const bool not_3d = params.target != SurfaceTarget::Texture3D && - old_params.target != SurfaceTarget::Texture3D; - if (not_3d || current_surface->MatchTarget(params.target)) { - if (struct_result == MatchStructureResult::FullMatch) { - return ManageStructuralMatch(current_surface, params, is_render); - } else { - return RebuildSurface(current_surface, params, is_render); - } - } - } - } +template <class P> +bool TextureCache<P>::HasUncommittedFlushes() const noexcept { + return !uncommitted_downloads.empty(); +} - // Step 2 - // Obtain all possible overlaps in the memory region - const std::size_t candidate_size = params.GetGuestSizeInBytes(); - auto overlaps{GetSurfacesInRegion(cpu_addr, candidate_size)}; +template <class P> +bool TextureCache<P>::ShouldWaitAsyncFlushes() const noexcept { + return !committed_downloads.empty() && !committed_downloads.front().empty(); +} - // If none are found, we are done. we just load the surface and create it. - if (overlaps.empty()) { - return InitializeSurface(gpu_addr, params, preserve_contents); - } +template <class P> +void TextureCache<P>::CommitAsyncFlushes() { + // This is intentionally passing the value by copy + committed_downloads.push(uncommitted_downloads); + uncommitted_downloads.clear(); +} - // Step 3 - // Now we need to figure the relationship between the texture and its overlaps - // we do a topological test to ensure we can find some relationship. If it fails - // immediately recycle the texture - for (const auto& surface : overlaps) { - const auto topological_result = surface->MatchesTopology(params); - if (topological_result != MatchTopologyResult::FullMatch) { - return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, - topological_result); - } - } +template <class P> +void TextureCache<P>::PopAsyncFlushes() { + if (committed_downloads.empty()) { + return; + } + const std::span<const ImageId> download_ids = committed_downloads.front(); + if (download_ids.empty()) { + committed_downloads.pop(); + return; + } + size_t total_size_bytes = 0; + for (const ImageId image_id : download_ids) { + total_size_bytes += slot_images[image_id].unswizzled_size_bytes; + } + auto download_map = runtime.DownloadStagingBuffer(total_size_bytes); + const size_t original_offset = download_map.offset; + for (const ImageId image_id : download_ids) { + Image& image = slot_images[image_id]; + const auto copies = FullDownloadCopies(image.info); + image.DownloadMemory(download_map, copies); + download_map.offset += image.unswizzled_size_bytes; + } + // Wait for downloads to finish + runtime.Finish(); + + download_map.offset = original_offset; + std::span<u8> download_span = download_map.mapped_span; + for (const ImageId image_id : download_ids) { + const ImageBase& image = slot_images[image_id]; + const auto copies = FullDownloadCopies(image.info); + SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, download_span); + download_map.offset += image.unswizzled_size_bytes; + download_span = download_span.subspan(image.unswizzled_size_bytes); + } + committed_downloads.pop(); +} - // Manage 3D textures - if (params.block_depth > 0) { - auto surface = - Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr, preserve_contents); - if (surface) { - return *surface; - } +template <class P> +bool TextureCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { + bool is_modified = false; + ForEachImageInRegion(addr, size, [&is_modified](ImageId, ImageBase& image) { + if (False(image.flags & ImageFlagBits::GpuModified)) { + return false; } + is_modified = true; + return true; + }); + return is_modified; +} - // Split cases between 1 overlap or many. - if (overlaps.size() == 1) { - TSurface current_surface = overlaps[0]; - // First check if the surface is within the overlap. If not, it means - // two things either the candidate surface is a supertexture of the overlap - // or they don't match in any known way. - if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) { - const std::optional view = TryReconstructSurface(overlaps, params, gpu_addr); - if (view) { - return *view; - } - return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, - MatchTopologyResult::FullMatch); - } - // Now we check if the candidate is a mipmap/layer of the overlap - std::optional<TView> view = - current_surface->EmplaceView(params, gpu_addr, candidate_size); - if (view) { - const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); - if (is_mirage) { - // On a mirage view, we need to recreate the surface under this new view - // and then obtain a view again. - SurfaceParams new_params = current_surface->GetSurfaceParams(); - const u32 wh = SurfaceParams::ConvertWidth( - new_params.width, new_params.pixel_format, params.pixel_format); - const u32 hh = SurfaceParams::ConvertHeight( - new_params.height, new_params.pixel_format, params.pixel_format); - new_params.width = wh; - new_params.height = hh; - new_params.pixel_format = params.pixel_format; - std::pair<TSurface, TView> pair = - RebuildSurface(current_surface, new_params, is_render); - std::optional<TView> mirage_view = - pair.first->EmplaceView(params, gpu_addr, candidate_size); - if (mirage_view) - return {pair.first, *mirage_view}; - return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, - MatchTopologyResult::FullMatch); - } - return {current_surface, *view}; - } - } else { - // If there are many overlaps, odds are they are subtextures of the candidate - // surface. We try to construct a new surface based on the candidate parameters, - // using the overlaps. If a single overlap fails, this will fail. - std::optional<std::pair<TSurface, TView>> view = - TryReconstructSurface(overlaps, params, gpu_addr); - if (view) { - return *view; - } - } - // We failed all the tests, recycle the overlaps into a new texture. - return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, - MatchTopologyResult::FullMatch); - } - - /** - * Gets the starting address and parameters of a candidate surface and tries to find a - * matching surface within the cache that's similar to it. If there are many textures - * or the texture found if entirely incompatible, it will fail. If no texture is found, the - * blit will be unsuccessful. - * - * @param gpu_addr The starting address of the candidate surface. - * @param params The parameters on the candidate surface. - **/ - Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) { - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); - - if (!cpu_addr) { - Deduction result{}; - result.type = DeductionType::DeductionFailed; - return result; - } +template <class P> +void TextureCache<P>::RefreshContents(Image& image) { + if (False(image.flags & ImageFlagBits::CpuModified)) { + // Only upload modified images + return; + } + image.flags &= ~ImageFlagBits::CpuModified; + TrackImage(image); - if (const auto iter = l1_cache.find(*cpu_addr); iter != l1_cache.end()) { - TSurface& current_surface = iter->second; - const auto topological_result = current_surface->MatchesTopology(params); - if (topological_result != MatchTopologyResult::FullMatch) { - Deduction result{}; - result.type = DeductionType::DeductionFailed; - return result; - } - const auto struct_result = current_surface->MatchesStructure(params); - if (struct_result != MatchStructureResult::None && - current_surface->MatchTarget(params.target)) { - Deduction result{}; - result.type = DeductionType::DeductionComplete; - result.surface = current_surface; - return result; - } - } + if (image.info.num_samples > 1) { + LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); + return; + } + auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image)); + UploadImageContents(image, staging); + runtime.InsertUploadMemoryBarrier(); +} - const std::size_t candidate_size = params.GetGuestSizeInBytes(); - auto overlaps{GetSurfacesInRegion(*cpu_addr, candidate_size)}; +template <class P> +template <typename StagingBuffer> +void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging) { + const std::span<u8> mapped_span = staging.mapped_span; + const GPUVAddr gpu_addr = image.gpu_addr; + + if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { + gpu_memory.ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes()); + const auto uploads = FullUploadSwizzles(image.info); + runtime.AccelerateImageUpload(image, staging, uploads); + } else if (True(image.flags & ImageFlagBits::Converted)) { + std::vector<u8> unswizzled_data(image.unswizzled_size_bytes); + auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, unswizzled_data); + ConvertImage(unswizzled_data, image.info, mapped_span, copies); + image.UploadMemory(staging, copies); + } else if (image.info.type == ImageType::Buffer) { + const std::array copies{UploadBufferCopy(gpu_memory, gpu_addr, image, mapped_span)}; + image.UploadMemory(staging, copies); + } else { + const auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, mapped_span); + image.UploadMemory(staging, copies); + } +} - if (overlaps.empty()) { - Deduction result{}; - result.type = DeductionType::DeductionIncomplete; - return result; - } +template <class P> +ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) { + if (!IsValidAddress(gpu_memory, config)) { + return NULL_IMAGE_VIEW_ID; + } + const auto [pair, is_new] = image_views.try_emplace(config); + ImageViewId& image_view_id = pair->second; + if (is_new) { + image_view_id = CreateImageView(config); + } + return image_view_id; +} - if (overlaps.size() > 1) { - Deduction result{}; - result.type = DeductionType::DeductionFailed; - return result; - } else { - Deduction result{}; - result.type = DeductionType::DeductionComplete; - result.surface = overlaps[0]; - return result; - } +template <class P> +ImageViewId TextureCache<P>::CreateImageView(const TICEntry& config) { + const ImageInfo info(config); + const GPUVAddr image_gpu_addr = config.Address() - config.BaseLayer() * info.layer_stride; + const ImageId image_id = FindOrInsertImage(info, image_gpu_addr); + if (!image_id) { + return NULL_IMAGE_VIEW_ID; } + ImageBase& image = slot_images[image_id]; + const SubresourceBase base = image.TryFindBase(config.Address()).value(); + ASSERT(base.level == 0); + const ImageViewInfo view_info(config, base.layer); + const ImageViewId image_view_id = FindOrEmplaceImageView(image_id, view_info); + ImageViewBase& image_view = slot_image_views[image_view_id]; + image_view.flags |= ImageViewFlagBits::Strong; + image.flags |= ImageFlagBits::Strong; + return image_view_id; +} - /** - * Gets a null surface based on a target texture. - * @param target The target of the null surface. - */ - TView GetNullSurface(SurfaceTarget target) { - const u32 i_target = static_cast<u32>(target); - if (const auto it = invalid_cache.find(i_target); it != invalid_cache.end()) { - return it->second->GetMainView(); - } - SurfaceParams params{}; - params.target = target; - params.is_tiled = false; - params.srgb_conversion = false; - params.is_layered = - target == SurfaceTarget::Texture1DArray || target == SurfaceTarget::Texture2DArray || - target == SurfaceTarget::TextureCubemap || target == SurfaceTarget::TextureCubeArray; - params.block_width = 0; - params.block_height = 0; - params.block_depth = 0; - params.tile_width_spacing = 1; - params.width = 1; - params.height = 1; - params.depth = 1; - if (target == SurfaceTarget::TextureCubemap || target == SurfaceTarget::TextureCubeArray) { - params.depth = 6; - } - params.pitch = 4; - params.num_levels = 1; - params.emulated_levels = 1; - params.pixel_format = VideoCore::Surface::PixelFormat::R8_UNORM; - params.type = VideoCore::Surface::SurfaceType::ColorTexture; - auto surface = CreateSurface(0ULL, params); - invalid_memory.resize(surface->GetHostSizeInBytes(), 0U); - surface->UploadTexture(invalid_memory); - surface->MarkAsModified(false, Tick()); - invalid_cache.emplace(i_target, surface); - return surface->GetMainView(); - } - - /** - * Gets the a source and destination starting address and parameters, - * and tries to deduce if they are supposed to be depth textures. If so, their - * parameters are modified and fixed into so. - * - * @param src_params The parameters of the candidate surface. - * @param dst_params The parameters of the destination surface. - * @param src_gpu_addr The starting address of the candidate surface. - * @param dst_gpu_addr The starting address of the destination surface. - **/ - void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params, - const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) { - auto deduced_src = DeduceSurface(src_gpu_addr, src_params); - auto deduced_dst = DeduceSurface(dst_gpu_addr, dst_params); - if (deduced_src.Failed() || deduced_dst.Failed()) { - return; +template <class P> +ImageId TextureCache<P>::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options) { + if (const ImageId image_id = FindImage(info, gpu_addr, options); image_id) { + return image_id; + } + return InsertImage(info, gpu_addr, options); +} + +template <class P> +ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options) { + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + if (!cpu_addr) { + return ImageId{}; + } + const bool broken_views = runtime.HasBrokenTextureViewFormats(); + ImageId image_id; + const auto lambda = [&](ImageId existing_image_id, ImageBase& existing_image) { + if (info.type == ImageType::Linear || existing_image.info.type == ImageType::Linear) { + const bool strict_size = False(options & RelaxedOptions::Size) && + True(existing_image.flags & ImageFlagBits::Strong); + const ImageInfo& existing = existing_image.info; + if (existing_image.gpu_addr == gpu_addr && existing.type == info.type && + existing.pitch == info.pitch && + IsPitchLinearSameSize(existing, info, strict_size) && + IsViewCompatible(existing.format, info.format, broken_views)) { + image_id = existing_image_id; + return true; + } + } else if (IsSubresource(info, existing_image, gpu_addr, options, broken_views)) { + image_id = existing_image_id; + return true; } + return false; + }; + ForEachImageInRegion(*cpu_addr, CalculateGuestSizeInBytes(info), lambda); + return image_id; +} - const bool incomplete_src = deduced_src.Incomplete(); - const bool incomplete_dst = deduced_dst.Incomplete(); +template <class P> +ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, + RelaxedOptions options) { + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); + const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); + const Image& image = slot_images[image_id]; + // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different + const auto [it, is_new] = image_allocs_table.try_emplace(image.gpu_addr); + if (is_new) { + it->second = slot_image_allocs.insert(); + } + slot_image_allocs[it->second].images.push_back(image_id); + return image_id; +} - if (incomplete_src && incomplete_dst) { +template <class P> +ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr) { + ImageInfo new_info = info; + const size_t size_bytes = CalculateGuestSizeInBytes(new_info); + const bool broken_views = runtime.HasBrokenTextureViewFormats(); + std::vector<ImageId> overlap_ids; + std::vector<ImageId> left_aliased_ids; + std::vector<ImageId> right_aliased_ids; + ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { + if (info.type != overlap.info.type) { return; } - - const bool any_incomplete = incomplete_src || incomplete_dst; - - if (!any_incomplete) { - if (!(deduced_src.IsDepth() && deduced_dst.IsDepth())) { - return; - } - } else { - if (incomplete_src && !(deduced_dst.IsDepth())) { - return; - } - - if (incomplete_dst && !(deduced_src.IsDepth())) { - return; + if (info.type == ImageType::Linear) { + if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) { + // Alias linear images with the same pitch + left_aliased_ids.push_back(overlap_id); } + return; } - - const auto inherit_format = [](SurfaceParams& to, TSurface from) { - const SurfaceParams& params = from->GetSurfaceParams(); - to.pixel_format = params.pixel_format; - to.type = params.type; - }; - // Now we got the cases where one or both is Depth and the other is not known - if (!incomplete_src) { - inherit_format(src_params, deduced_src.surface); - } else { - inherit_format(src_params, deduced_dst.surface); + static constexpr bool strict_size = true; + const std::optional<OverlapResult> solution = + ResolveOverlap(new_info, gpu_addr, cpu_addr, overlap, strict_size, broken_views); + if (solution) { + gpu_addr = solution->gpu_addr; + cpu_addr = solution->cpu_addr; + new_info.resources = solution->resources; + overlap_ids.push_back(overlap_id); + return; } - if (!incomplete_dst) { - inherit_format(dst_params, deduced_dst.surface); + static constexpr auto options = RelaxedOptions::Size | RelaxedOptions::Format; + const ImageBase new_image_base(new_info, gpu_addr, cpu_addr); + if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views)) { + left_aliased_ids.push_back(overlap_id); + } else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options, + broken_views)) { + right_aliased_ids.push_back(overlap_id); + } + }); + const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr); + Image& new_image = slot_images[new_image_id]; + + // TODO: Only upload what we need + RefreshContents(new_image); + + for (const ImageId overlap_id : overlap_ids) { + Image& overlap = slot_images[overlap_id]; + if (overlap.info.num_samples != new_image.info.num_samples) { + LOG_WARNING(HW_GPU, "Copying between images with different samples is not implemented"); } else { - inherit_format(dst_params, deduced_src.surface); + const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value(); + const auto copies = MakeShrinkImageCopies(new_info, overlap.info, base); + runtime.CopyImage(new_image, overlap, copies); + } + if (True(overlap.flags & ImageFlagBits::Tracked)) { + UntrackImage(overlap); } + UnregisterImage(overlap_id); + DeleteImage(overlap_id); + } + ImageBase& new_image_base = new_image; + for (const ImageId aliased_id : right_aliased_ids) { + ImageBase& aliased = slot_images[aliased_id]; + AddImageAlias(new_image_base, aliased, new_image_id, aliased_id); } + for (const ImageId aliased_id : left_aliased_ids) { + ImageBase& aliased = slot_images[aliased_id]; + AddImageAlias(aliased, new_image_base, aliased_id, new_image_id); + } + RegisterImage(new_image_id); + return new_image_id; +} - std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params, - bool preserve_contents) { - auto new_surface{GetUncachedSurface(gpu_addr, params)}; - Register(new_surface); - if (preserve_contents) { - LoadSurface(new_surface); - } - return {new_surface, new_surface->GetMainView()}; +template <class P> +typename TextureCache<P>::BlitImages TextureCache<P>::GetBlitImages( + const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src) { + static constexpr auto FIND_OPTIONS = RelaxedOptions::Format | RelaxedOptions::Samples; + const GPUVAddr dst_addr = dst.Address(); + const GPUVAddr src_addr = src.Address(); + ImageInfo dst_info(dst); + ImageInfo src_info(src); + ImageId dst_id; + ImageId src_id; + do { + has_deleted_images = false; + dst_id = FindImage(dst_info, dst_addr, FIND_OPTIONS); + src_id = FindImage(src_info, src_addr, FIND_OPTIONS); + const ImageBase* const dst_image = dst_id ? &slot_images[dst_id] : nullptr; + const ImageBase* const src_image = src_id ? &slot_images[src_id] : nullptr; + DeduceBlitImages(dst_info, src_info, dst_image, src_image); + if (GetFormatType(dst_info.format) != GetFormatType(src_info.format)) { + continue; + } + if (!dst_id) { + dst_id = InsertImage(dst_info, dst_addr, RelaxedOptions{}); + } + if (!src_id) { + src_id = InsertImage(src_info, src_addr, RelaxedOptions{}); + } + } while (has_deleted_images); + return BlitImages{ + .dst_id = dst_id, + .src_id = src_id, + .dst_format = dst_info.format, + .src_format = src_info.format, + }; +} + +template <class P> +SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) { + if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) { + return NULL_SAMPLER_ID; } + const auto [pair, is_new] = samplers.try_emplace(config); + if (is_new) { + pair->second = slot_samplers.insert(runtime, config); + } + return pair->second; +} - void LoadSurface(const TSurface& surface) { - staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); - surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache); - surface->UploadTexture(staging_cache.GetBuffer(0)); - surface->MarkAsModified(false, Tick()); +template <class P> +ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) { + const auto& regs = maxwell3d.regs; + if (index >= regs.rt_control.count) { + return ImageViewId{}; + } + const auto& rt = regs.rt[index]; + const GPUVAddr gpu_addr = rt.Address(); + if (gpu_addr == 0) { + return ImageViewId{}; } + if (rt.format == Tegra::RenderTargetFormat::NONE) { + return ImageViewId{}; + } + const ImageInfo info(regs, index); + return FindRenderTargetView(info, gpu_addr, is_clear); +} - void FlushSurface(const TSurface& surface) { - if (!surface->IsModified()) { - return; - } - staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); - surface->DownloadTexture(staging_cache.GetBuffer(0)); - surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache); - surface->MarkAsModified(false, Tick()); - } - - void RegisterInnerCache(TSurface& surface) { - const VAddr cpu_addr = surface->GetCpuAddr(); - VAddr start = cpu_addr >> registry_page_bits; - const VAddr end = (surface->GetCpuAddrEnd() - 1) >> registry_page_bits; - l1_cache[cpu_addr] = surface; - while (start <= end) { - registry[start].push_back(surface); - start++; - } +template <class P> +ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) { + const auto& regs = maxwell3d.regs; + if (!regs.zeta_enable) { + return ImageViewId{}; + } + const GPUVAddr gpu_addr = regs.zeta.Address(); + if (gpu_addr == 0) { + return ImageViewId{}; } + const ImageInfo info(regs); + return FindRenderTargetView(info, gpu_addr, is_clear); +} - void UnregisterInnerCache(TSurface& surface) { - const VAddr cpu_addr = surface->GetCpuAddr(); - VAddr start = cpu_addr >> registry_page_bits; - const VAddr end = (surface->GetCpuAddrEnd() - 1) >> registry_page_bits; - l1_cache.erase(cpu_addr); - while (start <= end) { - auto& reg{registry[start]}; - reg.erase(std::find(reg.begin(), reg.end(), surface)); - start++; - } +template <class P> +ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr, + bool is_clear) { + const auto options = is_clear ? RelaxedOptions::Samples : RelaxedOptions{}; + const ImageId image_id = FindOrInsertImage(info, gpu_addr, options); + if (!image_id) { + return NULL_IMAGE_VIEW_ID; + } + Image& image = slot_images[image_id]; + const ImageViewType view_type = RenderTargetImageViewType(info); + SubresourceBase base; + if (image.info.type == ImageType::Linear) { + base = SubresourceBase{.level = 0, .layer = 0}; + } else { + base = image.TryFindBase(gpu_addr).value(); } + const s32 layers = image.info.type == ImageType::e3D ? info.size.depth : info.resources.layers; + const SubresourceRange range{ + .base = base, + .extent = {.levels = 1, .layers = layers}, + }; + return FindOrEmplaceImageView(image_id, ImageViewInfo(view_type, info.format, range)); +} - VectorSurface GetSurfacesInRegion(const VAddr cpu_addr, const std::size_t size) { - if (size == 0) { - return {}; +template <class P> +template <typename Func> +void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) { + using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type; + static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>; + boost::container::small_vector<ImageId, 32> images; + ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { + const auto it = page_table.find(page); + if (it == page_table.end()) { + if constexpr (BOOL_BREAK) { + return false; + } else { + return; + } } - const VAddr cpu_addr_end = cpu_addr + size; - const VAddr end = (cpu_addr_end - 1) >> registry_page_bits; - VectorSurface surfaces; - for (VAddr start = cpu_addr >> registry_page_bits; start <= end; ++start) { - const auto it = registry.find(start); - if (it == registry.end()) { + for (const ImageId image_id : it->second) { + Image& image = slot_images[image_id]; + if (True(image.flags & ImageFlagBits::Picked)) { + continue; + } + if (!image.Overlaps(cpu_addr, size)) { continue; } - for (auto& surface : it->second) { - if (surface->IsPicked() || !surface->Overlaps(cpu_addr, cpu_addr_end)) { - continue; + image.flags |= ImageFlagBits::Picked; + images.push_back(image_id); + if constexpr (BOOL_BREAK) { + if (func(image_id, image)) { + return true; } - surface->MarkAsPicked(true); - surfaces.push_back(surface); + } else { + func(image_id, image); } } - for (auto& surface : surfaces) { - surface->MarkAsPicked(false); + if constexpr (BOOL_BREAK) { + return false; } - return surfaces; + }); + for (const ImageId image_id : images) { + slot_images[image_id].flags &= ~ImageFlagBits::Picked; } +} - void ReserveSurface(const SurfaceParams& params, TSurface surface) { - surface_reserve[params].push_back(std::move(surface)); +template <class P> +ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info) { + Image& image = slot_images[image_id]; + if (const ImageViewId image_view_id = image.FindView(info); image_view_id) { + return image_view_id; } + const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image); + image.InsertView(info, image_view_id); + return image_view_id; +} + +template <class P> +void TextureCache<P>::RegisterImage(ImageId image_id) { + ImageBase& image = slot_images[image_id]; + ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), + "Trying to register an already registered image"); + image.flags |= ImageFlagBits::Registered; + ForEachPage(image.cpu_addr, image.guest_size_bytes, + [this, image_id](u64 page) { page_table[page].push_back(image_id); }); +} - TSurface TryGetReservedSurface(const SurfaceParams& params) { - auto search{surface_reserve.find(params)}; - if (search == surface_reserve.end()) { - return {}; +template <class P> +void TextureCache<P>::UnregisterImage(ImageId image_id) { + Image& image = slot_images[image_id]; + ASSERT_MSG(True(image.flags & ImageFlagBits::Registered), + "Trying to unregister an already registered image"); + image.flags &= ~ImageFlagBits::Registered; + ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { + const auto page_it = page_table.find(page); + if (page_it == page_table.end()) { + UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + return; } - for (auto& surface : search->second) { - if (!surface->IsRegistered()) { - return surface; - } + std::vector<ImageId>& image_ids = page_it->second; + const auto vector_it = std::ranges::find(image_ids, image_id); + if (vector_it == image_ids.end()) { + UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", page << PAGE_BITS); + return; } - return {}; - } + image_ids.erase(vector_it); + }); +} - /// Try to do an image copy logging when formats are incompatible. - void TryCopyImage(TSurface& src, TSurface& dst, const CopyParams& copy) { - const SurfaceParams& src_params = src->GetSurfaceParams(); - const SurfaceParams& dst_params = dst->GetSurfaceParams(); - if (!format_compatibility.TestCopy(src_params.pixel_format, dst_params.pixel_format)) { - LOG_ERROR(HW_GPU, "Illegal copy between formats={{{}, {}}}", - static_cast<int>(dst_params.pixel_format), - static_cast<int>(src_params.pixel_format)); - return; +template <class P> +void TextureCache<P>::TrackImage(ImageBase& image) { + ASSERT(False(image.flags & ImageFlagBits::Tracked)); + image.flags |= ImageFlagBits::Tracked; + rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1); +} + +template <class P> +void TextureCache<P>::UntrackImage(ImageBase& image) { + ASSERT(True(image.flags & ImageFlagBits::Tracked)); + image.flags &= ~ImageFlagBits::Tracked; + rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1); +} + +template <class P> +void TextureCache<P>::DeleteImage(ImageId image_id) { + ImageBase& image = slot_images[image_id]; + const GPUVAddr gpu_addr = image.gpu_addr; + const auto alloc_it = image_allocs_table.find(gpu_addr); + if (alloc_it == image_allocs_table.end()) { + UNREACHABLE_MSG("Trying to delete an image alloc that does not exist in address 0x{:x}", + gpu_addr); + return; + } + const ImageAllocId alloc_id = alloc_it->second; + std::vector<ImageId>& alloc_images = slot_image_allocs[alloc_id].images; + const auto alloc_image_it = std::ranges::find(alloc_images, image_id); + if (alloc_image_it == alloc_images.end()) { + UNREACHABLE_MSG("Trying to delete an image that does not exist"); + return; + } + ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); + ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered"); + + // Mark render targets as dirty + auto& dirty = maxwell3d.dirty.flags; + dirty[Dirty::RenderTargets] = true; + dirty[Dirty::ZetaBuffer] = true; + for (size_t rt = 0; rt < NUM_RT; ++rt) { + dirty[Dirty::ColorBuffer0 + rt] = true; + } + const std::span<const ImageViewId> image_view_ids = image.image_view_ids; + for (const ImageViewId image_view_id : image_view_ids) { + std::ranges::replace(render_targets.color_buffer_ids, image_view_id, ImageViewId{}); + if (render_targets.depth_buffer_id == image_view_id) { + render_targets.depth_buffer_id = ImageViewId{}; } - ImageCopy(src, dst, copy); } + RemoveImageViewReferences(image_view_ids); + RemoveFramebuffers(image_view_ids); + + for (const AliasedImage& alias : image.aliased_images) { + ImageBase& other_image = slot_images[alias.id]; + [[maybe_unused]] const size_t num_removed_aliases = + std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) { + return other_alias.id == image_id; + }); + ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}", + num_removed_aliases); + } + for (const ImageViewId image_view_id : image_view_ids) { + sentenced_image_view.Push(std::move(slot_image_views[image_view_id])); + slot_image_views.erase(image_view_id); + } + sentenced_images.Push(std::move(slot_images[image_id])); + slot_images.erase(image_id); - constexpr PixelFormat GetSiblingFormat(PixelFormat format) const { - return siblings_table[static_cast<std::size_t>(format)]; + alloc_images.erase(alloc_image_it); + if (alloc_images.empty()) { + image_allocs_table.erase(alloc_it); + } + if constexpr (ENABLE_VALIDATION) { + std::ranges::fill(graphics_image_view_ids, CORRUPT_ID); + std::ranges::fill(compute_image_view_ids, CORRUPT_ID); } + graphics_image_table.Invalidate(); + compute_image_table.Invalidate(); + has_deleted_images = true; +} - /// Returns true the shader sampler entry is compatible with the TIC texture type. - static bool IsTypeCompatible(Tegra::Texture::TextureType tic_type, - const VideoCommon::Shader::Sampler& entry) { - const auto shader_type = entry.type; - switch (tic_type) { - case Tegra::Texture::TextureType::Texture1D: - case Tegra::Texture::TextureType::Texture1DArray: - return shader_type == Tegra::Shader::TextureType::Texture1D; - case Tegra::Texture::TextureType::Texture1DBuffer: - // TODO(Rodrigo): Assume as valid for now - return true; - case Tegra::Texture::TextureType::Texture2D: - case Tegra::Texture::TextureType::Texture2DNoMipmap: - return shader_type == Tegra::Shader::TextureType::Texture2D; - case Tegra::Texture::TextureType::Texture2DArray: - return shader_type == Tegra::Shader::TextureType::Texture2D || - shader_type == Tegra::Shader::TextureType::TextureCube; - case Tegra::Texture::TextureType::Texture3D: - return shader_type == Tegra::Shader::TextureType::Texture3D; - case Tegra::Texture::TextureType::TextureCubeArray: - case Tegra::Texture::TextureType::TextureCubemap: - if (shader_type == Tegra::Shader::TextureType::TextureCube) { - return true; - } - return shader_type == Tegra::Shader::TextureType::Texture2D && entry.is_array; +template <class P> +void TextureCache<P>::RemoveImageViewReferences(std::span<const ImageViewId> removed_views) { + auto it = image_views.begin(); + while (it != image_views.end()) { + const auto found = std::ranges::find(removed_views, it->second); + if (found != removed_views.end()) { + it = image_views.erase(it); + } else { + ++it; } - UNREACHABLE(); - return true; } +} - struct FramebufferTargetInfo { - TSurface target; - TView view; - }; - - void AsyncFlushSurface(TSurface& surface) { - if (!uncommitted_flushes) { - uncommitted_flushes = std::make_shared<std::list<TSurface>>(); +template <class P> +void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_views) { + auto it = framebuffers.begin(); + while (it != framebuffers.end()) { + if (it->first.Contains(removed_views)) { + it = framebuffers.erase(it); + } else { + ++it; } - uncommitted_flushes->push_back(surface); } +} - VideoCore::RasterizerInterface& rasterizer; - - FormatLookupTable format_lookup_table; - FormatCompatibility format_compatibility; - - u64 ticks{}; - - // Guards the cache for protection conflicts. - bool guard_render_targets{}; - bool guard_samplers{}; - - // The siblings table is for formats that can inter exchange with one another - // without causing issues. This is only valid when a conflict occurs on a non - // rendering use. - std::array<PixelFormat, static_cast<std::size_t>(PixelFormat::Max)> siblings_table; - - // The internal Cache is different for the Texture Cache. It's based on buckets - // of 1MB. This fits better for the purpose of this cache as textures are normaly - // large in size. - static constexpr u64 registry_page_bits{20}; - static constexpr u64 registry_page_size{1 << registry_page_bits}; - std::unordered_map<VAddr, std::vector<TSurface>> registry; +template <class P> +void TextureCache<P>::MarkModification(ImageBase& image) noexcept { + image.flags |= ImageFlagBits::GpuModified; + image.modification_tick = ++modification_tick; +} - static constexpr u32 DEPTH_RT = 8; - static constexpr u32 NO_RT = 0xFFFFFFFF; +template <class P> +void TextureCache<P>::SynchronizeAliases(ImageId image_id) { + boost::container::small_vector<const AliasedImage*, 1> aliased_images; + ImageBase& image = slot_images[image_id]; + u64 most_recent_tick = image.modification_tick; + for (const AliasedImage& aliased : image.aliased_images) { + ImageBase& aliased_image = slot_images[aliased.id]; + if (image.modification_tick < aliased_image.modification_tick) { + most_recent_tick = std::max(most_recent_tick, aliased_image.modification_tick); + aliased_images.push_back(&aliased); + } + } + if (aliased_images.empty()) { + return; + } + image.modification_tick = most_recent_tick; + std::ranges::sort(aliased_images, [this](const AliasedImage* lhs, const AliasedImage* rhs) { + const ImageBase& lhs_image = slot_images[lhs->id]; + const ImageBase& rhs_image = slot_images[rhs->id]; + return lhs_image.modification_tick < rhs_image.modification_tick; + }); + for (const AliasedImage* const aliased : aliased_images) { + CopyImage(image_id, aliased->id, aliased->copies); + } +} - // The L1 Cache is used for fast texture lookup before checking the overlaps - // This avoids calculating size and other stuffs. - std::unordered_map<VAddr, TSurface> l1_cache; +template <class P> +void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool invalidate) { + Image& image = slot_images[image_id]; + if (invalidate) { + image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified); + if (False(image.flags & ImageFlagBits::Tracked)) { + TrackImage(image); + } + } else { + RefreshContents(image); + SynchronizeAliases(image_id); + } + if (is_modification) { + MarkModification(image); + } + image.frame_tick = frame_tick; +} - /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have - /// previously been used. This is to prevent surfaces from being constantly created and - /// destroyed when used with different surface parameters. - std::unordered_map<SurfaceParams, std::vector<TSurface>> surface_reserve; - std::array<FramebufferTargetInfo, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> - render_targets; - FramebufferTargetInfo depth_buffer; +template <class P> +void TextureCache<P>::PrepareImageView(ImageViewId image_view_id, bool is_modification, + bool invalidate) { + if (!image_view_id) { + return; + } + const ImageViewBase& image_view = slot_image_views[image_view_id]; + PrepareImage(image_view.image_id, is_modification, invalidate); +} - std::vector<TSurface> sampled_textures; +template <class P> +void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies) { + Image& dst = slot_images[dst_id]; + Image& src = slot_images[src_id]; + const auto dst_format_type = GetFormatType(dst.info.format); + const auto src_format_type = GetFormatType(src.info.format); + if (src_format_type == dst_format_type) { + if constexpr (HAS_EMULATED_COPIES) { + if (!runtime.CanImageBeCopied(dst, src)) { + return runtime.EmulateCopyImage(dst, src, copies); + } + } + return runtime.CopyImage(dst, src, copies); + } + UNIMPLEMENTED_IF(dst.info.type != ImageType::e2D); + UNIMPLEMENTED_IF(src.info.type != ImageType::e2D); + for (const ImageCopy& copy : copies) { + UNIMPLEMENTED_IF(copy.dst_subresource.num_layers != 1); + UNIMPLEMENTED_IF(copy.src_subresource.num_layers != 1); + UNIMPLEMENTED_IF(copy.src_offset != Offset3D{}); + UNIMPLEMENTED_IF(copy.dst_offset != Offset3D{}); + + const SubresourceBase dst_base{ + .level = copy.dst_subresource.base_level, + .layer = copy.dst_subresource.base_layer, + }; + const SubresourceBase src_base{ + .level = copy.src_subresource.base_level, + .layer = copy.src_subresource.base_layer, + }; + const SubresourceExtent dst_extent{.levels = 1, .layers = 1}; + const SubresourceExtent src_extent{.levels = 1, .layers = 1}; + const SubresourceRange dst_range{.base = dst_base, .extent = dst_extent}; + const SubresourceRange src_range{.base = src_base, .extent = src_extent}; + const ImageViewInfo dst_view_info(ImageViewType::e2D, dst.info.format, dst_range); + const ImageViewInfo src_view_info(ImageViewType::e2D, src.info.format, src_range); + const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); + Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id]; + const ImageViewId src_view_id = FindOrEmplaceImageView(src_id, src_view_info); + ImageView& dst_view = slot_image_views[dst_view_id]; + ImageView& src_view = slot_image_views[src_view_id]; + [[maybe_unused]] const Extent3D expected_size{ + .width = std::min(dst_view.size.width, src_view.size.width), + .height = std::min(dst_view.size.height, src_view.size.height), + .depth = std::min(dst_view.size.depth, src_view.size.depth), + }; + UNIMPLEMENTED_IF(copy.extent != expected_size); - /// This cache stores null surfaces in order to be used as a placeholder - /// for invalid texture calls. - std::unordered_map<u32, TSurface> invalid_cache; - std::vector<u8> invalid_memory; + runtime.ConvertImage(dst_framebuffer, dst_view, src_view); + } +} - std::list<TSurface> marked_for_unregister; +template <class P> +void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id) { + if (*old_id == new_id) { + return; + } + if (*old_id) { + const ImageViewBase& old_view = slot_image_views[*old_id]; + if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) { + uncommitted_downloads.push_back(old_view.image_id); + } + } + *old_id = new_id; +} - std::shared_ptr<std::list<TSurface>> uncommitted_flushes{}; - std::list<std::shared_ptr<std::list<TSurface>>> committed_flushes; +template <class P> +std::pair<FramebufferId, ImageViewId> TextureCache<P>::RenderTargetFromImage( + ImageId image_id, const ImageViewInfo& view_info) { + const ImageViewId view_id = FindOrEmplaceImageView(image_id, view_info); + const ImageBase& image = slot_images[image_id]; + const bool is_color = GetFormatType(image.info.format) == SurfaceType::ColorTexture; + const ImageViewId color_view_id = is_color ? view_id : ImageViewId{}; + const ImageViewId depth_view_id = is_color ? ImageViewId{} : view_id; + const Extent3D extent = MipSize(image.info.size, view_info.range.base.level); + const u32 num_samples = image.info.num_samples; + const auto [samples_x, samples_y] = SamplesLog2(num_samples); + const FramebufferId framebuffer_id = GetFramebufferId(RenderTargets{ + .color_buffer_ids = {color_view_id}, + .depth_buffer_id = depth_view_id, + .size = {extent.width >> samples_x, extent.height >> samples_y}, + }); + return {framebuffer_id, view_id}; +} - StagingCache staging_cache; - std::recursive_mutex mutex; -}; +template <class P> +bool TextureCache<P>::IsFullClear(ImageViewId id) { + if (!id) { + return true; + } + const ImageViewBase& image_view = slot_image_views[id]; + const ImageBase& image = slot_images[image_view.image_id]; + const Extent3D size = image_view.size; + const auto& regs = maxwell3d.regs; + const auto& scissor = regs.scissor_test[0]; + if (image.info.resources.levels > 1 || image.info.resources.layers > 1) { + // Images with multiple resources can't be cleared in a single call + return false; + } + if (regs.clear_flags.scissor == 0) { + // If scissor testing is disabled, the clear is always full + return true; + } + // Make sure the clear covers all texels in the subresource + return scissor.min_x == 0 && scissor.min_y == 0 && scissor.max_x >= size.width && + scissor.max_y >= size.height; +} } // namespace VideoCommon diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h new file mode 100644 index 000000000..2ad2d72a6 --- /dev/null +++ b/src/video_core/texture_cache/types.h @@ -0,0 +1,140 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/texture_cache/slot_vector.h" + +namespace VideoCommon { + +constexpr size_t NUM_RT = 8; +constexpr size_t MAX_MIP_LEVELS = 14; + +constexpr SlotId CORRUPT_ID{0xfffffffe}; + +using ImageId = SlotId; +using ImageViewId = SlotId; +using ImageAllocId = SlotId; +using SamplerId = SlotId; +using FramebufferId = SlotId; + +enum class ImageType : u32 { + e1D, + e2D, + e3D, + Linear, + Buffer, +}; + +enum class ImageViewType : u32 { + e1D, + e2D, + Cube, + e3D, + e1DArray, + e2DArray, + CubeArray, + Rect, + Buffer, +}; +constexpr size_t NUM_IMAGE_VIEW_TYPES = 9; + +enum class RelaxedOptions : u32 { + Size = 1 << 0, + Format = 1 << 1, + Samples = 1 << 2, +}; +DECLARE_ENUM_FLAG_OPERATORS(RelaxedOptions) + +struct Offset2D { + constexpr auto operator<=>(const Offset2D&) const noexcept = default; + + s32 x; + s32 y; +}; + +struct Offset3D { + constexpr auto operator<=>(const Offset3D&) const noexcept = default; + + s32 x; + s32 y; + s32 z; +}; + +struct Extent2D { + constexpr auto operator<=>(const Extent2D&) const noexcept = default; + + u32 width; + u32 height; +}; + +struct Extent3D { + constexpr auto operator<=>(const Extent3D&) const noexcept = default; + + u32 width; + u32 height; + u32 depth; +}; + +struct SubresourceLayers { + s32 base_level = 0; + s32 base_layer = 0; + s32 num_layers = 1; +}; + +struct SubresourceBase { + constexpr auto operator<=>(const SubresourceBase&) const noexcept = default; + + s32 level = 0; + s32 layer = 0; +}; + +struct SubresourceExtent { + constexpr auto operator<=>(const SubresourceExtent&) const noexcept = default; + + s32 levels = 1; + s32 layers = 1; +}; + +struct SubresourceRange { + constexpr auto operator<=>(const SubresourceRange&) const noexcept = default; + + SubresourceBase base; + SubresourceExtent extent; +}; + +struct ImageCopy { + SubresourceLayers src_subresource; + SubresourceLayers dst_subresource; + Offset3D src_offset; + Offset3D dst_offset; + Extent3D extent; +}; + +struct BufferImageCopy { + size_t buffer_offset; + size_t buffer_size; + u32 buffer_row_length; + u32 buffer_image_height; + SubresourceLayers image_subresource; + Offset3D image_offset; + Extent3D image_extent; +}; + +struct BufferCopy { + size_t src_offset; + size_t dst_offset; + size_t size; +}; + +struct SwizzleParameters { + Extent3D num_tiles; + Extent3D block; + size_t buffer_offset; + s32 level; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp new file mode 100644 index 000000000..a0bc1f7b6 --- /dev/null +++ b/src/video_core/texture_cache/util.cpp @@ -0,0 +1,1210 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This files contains code from Ryujinx +// A copy of the code can be obtained from https://github.com/Ryujinx/Ryujinx +// The sections using code from Ryujinx are marked with a link to the original version + +// MIT License +// +// Copyright (c) Ryujinx Team and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#include <algorithm> +#include <array> +#include <numeric> +#include <optional> +#include <span> +#include <vector> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "video_core/compatible_formats.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/decode_bc4.h" +#include "video_core/texture_cache/format_lookup_table.h" +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/samples_helper.h" +#include "video_core/texture_cache/util.h" +#include "video_core/textures/astc.h" +#include "video_core/textures/decoders.h" + +namespace VideoCommon { + +namespace { + +using Tegra::Texture::GOB_SIZE; +using Tegra::Texture::GOB_SIZE_SHIFT; +using Tegra::Texture::GOB_SIZE_X; +using Tegra::Texture::GOB_SIZE_X_SHIFT; +using Tegra::Texture::GOB_SIZE_Y; +using Tegra::Texture::GOB_SIZE_Y_SHIFT; +using Tegra::Texture::GOB_SIZE_Z; +using Tegra::Texture::GOB_SIZE_Z_SHIFT; +using Tegra::Texture::MsaaMode; +using Tegra::Texture::SwizzleTexture; +using Tegra::Texture::TextureFormat; +using Tegra::Texture::TextureType; +using Tegra::Texture::TICEntry; +using Tegra::Texture::UnswizzleTexture; +using VideoCore::Surface::BytesPerBlock; +using VideoCore::Surface::DefaultBlockHeight; +using VideoCore::Surface::DefaultBlockWidth; +using VideoCore::Surface::IsCopyCompatible; +using VideoCore::Surface::IsPixelFormatASTC; +using VideoCore::Surface::IsViewCompatible; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::SurfaceType; + +constexpr u32 CONVERTED_BYTES_PER_BLOCK = BytesPerBlock(PixelFormat::A8B8G8R8_UNORM); + +struct LevelInfo { + Extent3D size; + Extent3D block; + Extent2D tile_size; + u32 bpp_log2; + u32 tile_width_spacing; +}; + +[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) { + if (shift == 0) { + return 0; + } + u32 x = unit_factor << (shift - 1); + if (x >= dimension) { + while (--shift) { + x >>= 1; + if (x < dimension) { + break; + } + } + } + return shift; +} + +[[nodiscard]] constexpr u32 AdjustMipSize(u32 size, u32 level) { + return std::max<u32>(size >> level, 1); +} + +[[nodiscard]] constexpr Extent3D AdjustMipSize(Extent3D size, s32 level) { + return Extent3D{ + .width = AdjustMipSize(size.width, level), + .height = AdjustMipSize(size.height, level), + .depth = AdjustMipSize(size.depth, level), + }; +} + +[[nodiscard]] Extent3D AdjustSamplesSize(Extent3D size, s32 num_samples) { + const auto [samples_x, samples_y] = SamplesLog2(num_samples); + return Extent3D{ + .width = size.width >> samples_x, + .height = size.height >> samples_y, + .depth = size.depth, + }; +} + +template <u32 GOB_EXTENT> +[[nodiscard]] constexpr u32 AdjustMipBlockSize(u32 num_tiles, u32 block_size, u32 level) { + do { + while (block_size > 0 && num_tiles <= (1U << (block_size - 1)) * GOB_EXTENT) { + --block_size; + } + } while (level--); + return block_size; +} + +[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size, + u32 level) { + return { + .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), + .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), + .depth = AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), + }; +} + +[[nodiscard]] constexpr Extent3D AdjustTileSize(Extent3D size, Extent2D tile_size) { + return { + .width = Common::DivCeil(size.width, tile_size.width), + .height = Common::DivCeil(size.height, tile_size.height), + .depth = size.depth, + }; +} + +[[nodiscard]] constexpr u32 BytesPerBlockLog2(u32 bytes_per_block) { + return std::countl_zero(bytes_per_block) ^ 0x1F; +} + +[[nodiscard]] constexpr u32 BytesPerBlockLog2(PixelFormat format) { + return BytesPerBlockLog2(BytesPerBlock(format)); +} + +[[nodiscard]] constexpr u32 NumBlocks(Extent3D size, Extent2D tile_size) { + const Extent3D num_blocks = AdjustTileSize(size, tile_size); + return num_blocks.width * num_blocks.height * num_blocks.depth; +} + +[[nodiscard]] constexpr u32 AdjustSize(u32 size, u32 level, u32 block_size) { + return Common::DivCeil(AdjustMipSize(size, level), block_size); +} + +[[nodiscard]] constexpr std::pair<int, int> Samples(int num_samples) { + switch (num_samples) { + case 1: + return {1, 1}; + case 2: + return {2, 1}; + case 4: + return {2, 2}; + case 8: + return {4, 2}; + case 16: + return {4, 4}; + } + UNREACHABLE_MSG("Invalid number of samples={}", num_samples); + return {1, 1}; +} + +[[nodiscard]] constexpr Extent2D DefaultBlockSize(PixelFormat format) { + return {DefaultBlockWidth(format), DefaultBlockHeight(format)}; +} + +[[nodiscard]] constexpr Extent3D NumLevelBlocks(const LevelInfo& info, u32 level) { + return Extent3D{ + .width = AdjustSize(info.size.width, level, info.tile_size.width) << info.bpp_log2, + .height = AdjustSize(info.size.height, level, info.tile_size.height), + .depth = AdjustMipSize(info.size.depth, level), + }; +} + +[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { + const Extent3D blocks = NumLevelBlocks(info, level); + return Extent3D{ + .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), + .height = AdjustTileSize(info.block.height, GOB_SIZE_Y, blocks.height), + .depth = AdjustTileSize(info.block.depth, GOB_SIZE_Z, blocks.depth), + }; +} + +[[nodiscard]] constexpr Extent2D GobSize(u32 bpp_log2, u32 block_height, u32 tile_width_spacing) { + return Extent2D{ + .width = GOB_SIZE_X_SHIFT - bpp_log2 + tile_width_spacing, + .height = GOB_SIZE_Y_SHIFT + block_height, + }; +} + +[[nodiscard]] constexpr bool IsSmallerThanGobSize(Extent3D num_tiles, Extent2D gob, + u32 block_depth) { + return num_tiles.width <= (1U << gob.width) || num_tiles.height <= (1U << gob.height) || + num_tiles.depth < (1U << block_depth); +} + +[[nodiscard]] constexpr u32 StrideAlignment(Extent3D num_tiles, Extent3D block, Extent2D gob, + u32 bpp_log2) { + if (IsSmallerThanGobSize(num_tiles, gob, block.depth)) { + return GOB_SIZE_X_SHIFT - bpp_log2; + } else { + return gob.width; + } +} + +[[nodiscard]] constexpr u32 StrideAlignment(Extent3D num_tiles, Extent3D block, u32 bpp_log2, + u32 tile_width_spacing) { + const Extent2D gob = GobSize(bpp_log2, block.height, tile_width_spacing); + return StrideAlignment(num_tiles, block, gob, bpp_log2); +} + +[[nodiscard]] constexpr Extent2D NumGobs(const LevelInfo& info, u32 level) { + const Extent3D blocks = NumLevelBlocks(info, level); + const Extent2D gobs{ + .width = Common::DivCeilLog2(blocks.width, GOB_SIZE_X_SHIFT), + .height = Common::DivCeilLog2(blocks.height, GOB_SIZE_Y_SHIFT), + }; + const Extent2D gob = GobSize(info.bpp_log2, info.block.height, info.tile_width_spacing); + const bool is_small = IsSmallerThanGobSize(blocks, gob, info.block.depth); + const u32 alignment = is_small ? 0 : info.tile_width_spacing; + return Extent2D{ + .width = Common::AlignUpLog2(gobs.width, alignment), + .height = gobs.height, + }; +} + +[[nodiscard]] constexpr Extent3D LevelTiles(const LevelInfo& info, u32 level) { + const Extent3D blocks = NumLevelBlocks(info, level); + const Extent3D tile_shift = TileShift(info, level); + const Extent2D gobs = NumGobs(info, level); + return Extent3D{ + .width = Common::DivCeilLog2(gobs.width, tile_shift.width), + .height = Common::DivCeilLog2(gobs.height, tile_shift.height), + .depth = Common::DivCeilLog2(blocks.depth, tile_shift.depth), + }; +} + +[[nodiscard]] constexpr u32 CalculateLevelSize(const LevelInfo& info, u32 level) { + const Extent3D tile_shift = TileShift(info, level); + const Extent3D tiles = LevelTiles(info, level); + const u32 num_tiles = tiles.width * tiles.height * tiles.depth; + const u32 shift = GOB_SIZE_SHIFT + tile_shift.width + tile_shift.height + tile_shift.depth; + return num_tiles << shift; +} + +[[nodiscard]] constexpr std::array<u32, MAX_MIP_LEVELS> CalculateLevelSizes(const LevelInfo& info, + u32 num_levels) { + ASSERT(num_levels <= MAX_MIP_LEVELS); + std::array<u32, MAX_MIP_LEVELS> sizes{}; + for (u32 level = 0; level < num_levels; ++level) { + sizes[level] = CalculateLevelSize(info, level); + } + return sizes; +} + +[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block, + u32 num_samples, u32 tile_width_spacing) { + const auto [samples_x, samples_y] = Samples(num_samples); + const u32 bytes_per_block = BytesPerBlock(format); + return { + .size = + { + .width = size.width * samples_x, + .height = size.height * samples_y, + .depth = size.depth, + }, + .block = block, + .tile_size = DefaultBlockSize(format), + .bpp_log2 = BytesPerBlockLog2(bytes_per_block), + .tile_width_spacing = tile_width_spacing, + }; +} + +[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) { + return MakeLevelInfo(info.format, info.size, info.block, info.num_samples, + info.tile_width_spacing); +} + +[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block, + u32 num_samples, u32 tile_width_spacing, + u32 level) { + const LevelInfo info = MakeLevelInfo(format, size, block, num_samples, tile_width_spacing); + u32 offset = 0; + for (u32 current_level = 0; current_level < level; ++current_level) { + offset += CalculateLevelSize(info, current_level); + } + return offset; +} + +[[nodiscard]] constexpr u32 AlignLayerSize(u32 size_bytes, Extent3D size, Extent3D block, + u32 tile_size_y, u32 tile_width_spacing) { + // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L134 + if (tile_width_spacing > 0) { + const u32 alignment_log2 = GOB_SIZE_SHIFT + tile_width_spacing + block.height + block.depth; + return Common::AlignUpLog2(size_bytes, alignment_log2); + } + const u32 aligned_height = Common::AlignUp(size.height, tile_size_y); + while (block.height != 0 && aligned_height <= (1U << (block.height - 1)) * GOB_SIZE_Y) { + --block.height; + } + while (block.depth != 0 && size.depth <= (1U << (block.depth - 1))) { + --block.depth; + } + const u32 block_shift = GOB_SIZE_SHIFT + block.height + block.depth; + const u32 num_blocks = size_bytes >> block_shift; + if (size_bytes != num_blocks << block_shift) { + return (num_blocks + 1) << block_shift; + } + return size_bytes; +} + +[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapEqualAddress(const ImageInfo& new_info, + const ImageBase& overlap, + bool strict_size) { + const ImageInfo& info = overlap.info; + if (!IsBlockLinearSizeCompatible(new_info, info, 0, 0, strict_size)) { + return std::nullopt; + } + if (new_info.block != info.block) { + return std::nullopt; + } + const SubresourceExtent resources = new_info.resources; + return SubresourceExtent{ + .levels = std::max(resources.levels, info.resources.levels), + .layers = std::max(resources.layers, info.resources.layers), + }; +} + +[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapRightAddress3D( + const ImageInfo& new_info, GPUVAddr gpu_addr, const ImageBase& overlap, bool strict_size) { + const std::vector<u32> slice_offsets = CalculateSliceOffsets(new_info); + const u32 diff = static_cast<u32>(overlap.gpu_addr - gpu_addr); + const auto it = std::ranges::find(slice_offsets, diff); + if (it == slice_offsets.end()) { + return std::nullopt; + } + const std::vector subresources = CalculateSliceSubresources(new_info); + const SubresourceBase base = subresources[std::distance(slice_offsets.begin(), it)]; + const ImageInfo& info = overlap.info; + if (!IsBlockLinearSizeCompatible(new_info, info, base.level, 0, strict_size)) { + return std::nullopt; + } + const u32 mip_depth = std::max(1U, new_info.size.depth << base.level); + if (mip_depth < info.size.depth + base.layer) { + return std::nullopt; + } + if (MipBlockSize(new_info, base.level) != info.block) { + return std::nullopt; + } + return SubresourceExtent{ + .levels = std::max(new_info.resources.levels, info.resources.levels + base.level), + .layers = 1, + }; +} + +[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapRightAddress2D( + const ImageInfo& new_info, GPUVAddr gpu_addr, const ImageBase& overlap, bool strict_size) { + const u32 layer_stride = new_info.layer_stride; + const s32 new_size = layer_stride * new_info.resources.layers; + const s32 diff = static_cast<s32>(overlap.gpu_addr - gpu_addr); + if (diff > new_size) { + return std::nullopt; + } + const s32 base_layer = diff / layer_stride; + const s32 mip_offset = diff % layer_stride; + const std::array offsets = CalculateMipLevelOffsets(new_info); + const auto end = offsets.begin() + new_info.resources.levels; + const auto it = std::find(offsets.begin(), end, mip_offset); + if (it == end) { + // Mipmap is not aligned to any valid size + return std::nullopt; + } + const SubresourceBase base{ + .level = static_cast<s32>(std::distance(offsets.begin(), it)), + .layer = base_layer, + }; + const ImageInfo& info = overlap.info; + if (!IsBlockLinearSizeCompatible(new_info, info, base.level, 0, strict_size)) { + return std::nullopt; + } + if (MipBlockSize(new_info, base.level) != info.block) { + return std::nullopt; + } + return SubresourceExtent{ + .levels = std::max(new_info.resources.levels, info.resources.levels + base.level), + .layers = std::max(new_info.resources.layers, info.resources.layers + base.layer), + }; +} + +[[nodiscard]] std::optional<OverlapResult> ResolveOverlapRightAddress(const ImageInfo& new_info, + GPUVAddr gpu_addr, + VAddr cpu_addr, + const ImageBase& overlap, + bool strict_size) { + std::optional<SubresourceExtent> resources; + if (new_info.type != ImageType::e3D) { + resources = ResolveOverlapRightAddress2D(new_info, gpu_addr, overlap, strict_size); + } else { + resources = ResolveOverlapRightAddress3D(new_info, gpu_addr, overlap, strict_size); + } + if (!resources) { + return std::nullopt; + } + return OverlapResult{ + .gpu_addr = gpu_addr, + .cpu_addr = cpu_addr, + .resources = *resources, + }; +} + +[[nodiscard]] std::optional<OverlapResult> ResolveOverlapLeftAddress(const ImageInfo& new_info, + GPUVAddr gpu_addr, + VAddr cpu_addr, + const ImageBase& overlap, + bool strict_size) { + const std::optional<SubresourceBase> base = overlap.TryFindBase(gpu_addr); + if (!base) { + return std::nullopt; + } + const ImageInfo& info = overlap.info; + if (!IsBlockLinearSizeCompatible(new_info, info, base->level, 0, strict_size)) { + return std::nullopt; + } + if (new_info.block != MipBlockSize(info, base->level)) { + return std::nullopt; + } + const SubresourceExtent resources = new_info.resources; + s32 layers = 1; + if (info.type != ImageType::e3D) { + layers = std::max(resources.layers, info.resources.layers + base->layer); + } + return OverlapResult{ + .gpu_addr = overlap.gpu_addr, + .cpu_addr = overlap.cpu_addr, + .resources = + { + .levels = std::max(resources.levels + base->level, info.resources.levels), + .layers = layers, + }, + }; +} + +[[nodiscard]] Extent2D PitchLinearAlignedSize(const ImageInfo& info) { + // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L212 + static constexpr u32 STRIDE_ALIGNMENT = 32; + ASSERT(info.type == ImageType::Linear); + const Extent2D num_tiles{ + .width = Common::DivCeil(info.size.width, DefaultBlockWidth(info.format)), + .height = Common::DivCeil(info.size.height, DefaultBlockHeight(info.format)), + }; + const u32 width_alignment = STRIDE_ALIGNMENT / BytesPerBlock(info.format); + return Extent2D{ + .width = Common::AlignUp(num_tiles.width, width_alignment), + .height = num_tiles.height, + }; +} + +[[nodiscard]] Extent3D BlockLinearAlignedSize(const ImageInfo& info, u32 level) { + // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L176 + ASSERT(info.type != ImageType::Linear); + const Extent3D size = AdjustMipSize(info.size, level); + const Extent3D num_tiles{ + .width = Common::DivCeil(size.width, DefaultBlockWidth(info.format)), + .height = Common::DivCeil(size.height, DefaultBlockHeight(info.format)), + .depth = size.depth, + }; + const u32 bpp_log2 = BytesPerBlockLog2(info.format); + const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing); + const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0); + return Extent3D{ + .width = Common::AlignUpLog2(num_tiles.width, alignment), + .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height), + .depth = Common::AlignUpLog2(num_tiles.depth, GOB_SIZE_Z_SHIFT + mip_block.depth), + }; +} + +[[nodiscard]] constexpr u32 NumBlocksPerLayer(const ImageInfo& info, Extent2D tile_size) noexcept { + u32 num_blocks = 0; + for (s32 level = 0; level < info.resources.levels; ++level) { + const Extent3D mip_size = AdjustMipSize(info.size, level); + num_blocks += NumBlocks(mip_size, tile_size); + } + return num_blocks; +} + +[[nodiscard]] u32 NumSlices(const ImageInfo& info) noexcept { + ASSERT(info.type == ImageType::e3D); + u32 num_slices = 0; + for (s32 level = 0; level < info.resources.levels; ++level) { + num_slices += AdjustMipSize(info.size.depth, level); + } + return num_slices; +} + +void SwizzlePitchLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, + const ImageInfo& info, const BufferImageCopy& copy, + std::span<const u8> memory) { + ASSERT(copy.image_offset.z == 0); + ASSERT(copy.image_extent.depth == 1); + ASSERT(copy.image_subresource.base_level == 0); + ASSERT(copy.image_subresource.base_layer == 0); + ASSERT(copy.image_subresource.num_layers == 1); + + const u32 bytes_per_block = BytesPerBlock(info.format); + const u32 row_length = copy.image_extent.width * bytes_per_block; + const u32 guest_offset_x = copy.image_offset.x * bytes_per_block; + + for (u32 line = 0; line < copy.image_extent.height; ++line) { + const u32 host_offset_y = line * info.pitch; + const u32 guest_offset_y = (copy.image_offset.y + line) * info.pitch; + const u32 guest_offset = guest_offset_x + guest_offset_y; + gpu_memory.WriteBlockUnsafe(gpu_addr + guest_offset, memory.data() + host_offset_y, + row_length); + } +} + +void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, + const ImageInfo& info, const BufferImageCopy& copy, + std::span<const u8> input) { + const Extent3D size = info.size; + const LevelInfo level_info = MakeLevelInfo(info); + const Extent2D tile_size = DefaultBlockSize(info.format); + const u32 bytes_per_block = BytesPerBlock(info.format); + + const s32 level = copy.image_subresource.base_level; + const Extent3D level_size = AdjustMipSize(size, level); + const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size); + const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block; + + UNIMPLEMENTED_IF(info.tile_width_spacing > 0); + + UNIMPLEMENTED_IF(copy.image_offset.x != 0); + UNIMPLEMENTED_IF(copy.image_offset.y != 0); + UNIMPLEMENTED_IF(copy.image_offset.z != 0); + UNIMPLEMENTED_IF(copy.image_extent != level_size); + + const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); + const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + + size_t host_offset = copy.buffer_offset; + + const u32 num_levels = info.resources.levels; + const std::array sizes = CalculateLevelSizes(level_info, num_levels); + size_t guest_offset = std::reduce(sizes.begin(), sizes.begin() + level, 0); + const size_t layer_stride = + AlignLayerSize(std::reduce(sizes.begin(), sizes.begin() + num_levels, 0), size, + level_info.block, tile_size.height, info.tile_width_spacing); + const size_t subresource_size = sizes[level]; + + const auto dst_data = std::make_unique<u8[]>(subresource_size); + const std::span<u8> dst(dst_data.get(), subresource_size); + + for (s32 layer = 0; layer < info.resources.layers; ++layer) { + const std::span<const u8> src = input.subspan(host_offset); + SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height, + num_tiles.depth, block.height, block.depth); + + gpu_memory.WriteBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes()); + + host_offset += host_bytes_per_layer; + guest_offset += layer_stride; + } + ASSERT(host_offset - copy.buffer_offset == copy.buffer_size); +} + +} // Anonymous namespace + +u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept { + if (info.type == ImageType::Buffer) { + return info.size.width * BytesPerBlock(info.format); + } + if (info.type == ImageType::Linear) { + return info.pitch * Common::DivCeil(info.size.height, DefaultBlockHeight(info.format)); + } + if (info.resources.layers > 1) { + ASSERT(info.layer_stride != 0); + return info.layer_stride * info.resources.layers; + } else { + return CalculateLayerSize(info); + } +} + +u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept { + if (info.type == ImageType::Buffer) { + return info.size.width * BytesPerBlock(info.format); + } + if (info.num_samples > 1) { + // Multisample images can't be uploaded or downloaded to the host + return 0; + } + if (info.type == ImageType::Linear) { + return info.pitch * Common::DivCeil(info.size.height, DefaultBlockHeight(info.format)); + } + const Extent2D tile_size = DefaultBlockSize(info.format); + return NumBlocksPerLayer(info, tile_size) * info.resources.layers * BytesPerBlock(info.format); +} + +u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept { + if (info.type == ImageType::Buffer) { + return info.size.width * BytesPerBlock(info.format); + } + static constexpr Extent2D TILE_SIZE{1, 1}; + return NumBlocksPerLayer(info, TILE_SIZE) * info.resources.layers * CONVERTED_BYTES_PER_BLOCK; +} + +u32 CalculateLayerStride(const ImageInfo& info) noexcept { + ASSERT(info.type != ImageType::Linear); + const u32 layer_size = CalculateLayerSize(info); + const Extent3D size = info.size; + const Extent3D block = info.block; + const u32 tile_size_y = DefaultBlockHeight(info.format); + return AlignLayerSize(layer_size, size, block, tile_size_y, info.tile_width_spacing); +} + +u32 CalculateLayerSize(const ImageInfo& info) noexcept { + ASSERT(info.type != ImageType::Linear); + return CalculateLevelOffset(info.format, info.size, info.block, info.num_samples, + info.tile_width_spacing, info.resources.levels); +} + +std::array<u32, MAX_MIP_LEVELS> CalculateMipLevelOffsets(const ImageInfo& info) noexcept { + ASSERT(info.resources.levels <= static_cast<s32>(MAX_MIP_LEVELS)); + const LevelInfo level_info = MakeLevelInfo(info); + std::array<u32, MAX_MIP_LEVELS> offsets{}; + u32 offset = 0; + for (s32 level = 0; level < info.resources.levels; ++level) { + offsets[level] = offset; + offset += CalculateLevelSize(level_info, level); + } + return offsets; +} + +std::vector<u32> CalculateSliceOffsets(const ImageInfo& info) { + ASSERT(info.type == ImageType::e3D); + std::vector<u32> offsets; + offsets.reserve(NumSlices(info)); + + const LevelInfo level_info = MakeLevelInfo(info); + u32 mip_offset = 0; + for (s32 level = 0; level < info.resources.levels; ++level) { + const Extent3D tile_shift = TileShift(level_info, level); + const Extent3D tiles = LevelTiles(level_info, level); + const u32 gob_size_shift = tile_shift.height + GOB_SIZE_SHIFT; + const u32 slice_size = (tiles.width * tiles.height) << gob_size_shift; + const u32 z_mask = (1U << tile_shift.depth) - 1; + const u32 depth = AdjustMipSize(info.size.depth, level); + for (u32 slice = 0; slice < depth; ++slice) { + const u32 z_low = slice & z_mask; + const u32 z_high = slice & ~z_mask; + offsets.push_back(mip_offset + (z_low << gob_size_shift) + (z_high * slice_size)); + } + mip_offset += CalculateLevelSize(level_info, level); + } + return offsets; +} + +std::vector<SubresourceBase> CalculateSliceSubresources(const ImageInfo& info) { + ASSERT(info.type == ImageType::e3D); + std::vector<SubresourceBase> subresources; + subresources.reserve(NumSlices(info)); + for (s32 level = 0; level < info.resources.levels; ++level) { + const s32 depth = AdjustMipSize(info.size.depth, level); + for (s32 slice = 0; slice < depth; ++slice) { + subresources.emplace_back(SubresourceBase{ + .level = level, + .layer = slice, + }); + } + } + return subresources; +} + +u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) { + const Extent2D tile_size = DefaultBlockSize(info.format); + const Extent3D level_size = AdjustMipSize(info.size, level); + const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); + const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level); + const u32 bpp_log2 = BytesPerBlockLog2(info.format); + return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing); +} + +PixelFormat PixelFormatFromTIC(const TICEntry& config) noexcept { + return PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type, + config.a_type, config.srgb_conversion); +} + +ImageViewType RenderTargetImageViewType(const ImageInfo& info) noexcept { + switch (info.type) { + case ImageType::e2D: + return info.resources.layers > 1 ? ImageViewType::e2DArray : ImageViewType::e2D; + case ImageType::e3D: + return ImageViewType::e2DArray; + case ImageType::Linear: + return ImageViewType::e2D; + default: + UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<int>(info.type)); + return ImageViewType{}; + } +} + +std::vector<ImageCopy> MakeShrinkImageCopies(const ImageInfo& dst, const ImageInfo& src, + SubresourceBase base) { + ASSERT(dst.resources.levels >= src.resources.levels); + ASSERT(dst.num_samples == src.num_samples); + + const bool is_dst_3d = dst.type == ImageType::e3D; + if (is_dst_3d) { + ASSERT(src.type == ImageType::e3D); + ASSERT(src.resources.levels == 1); + } + + std::vector<ImageCopy> copies; + copies.reserve(src.resources.levels); + for (s32 level = 0; level < src.resources.levels; ++level) { + ImageCopy& copy = copies.emplace_back(); + copy.src_subresource = SubresourceLayers{ + .base_level = level, + .base_layer = 0, + .num_layers = src.resources.layers, + }; + copy.dst_subresource = SubresourceLayers{ + .base_level = base.level + level, + .base_layer = is_dst_3d ? 0 : base.layer, + .num_layers = is_dst_3d ? 1 : src.resources.layers, + }; + copy.src_offset = Offset3D{ + .x = 0, + .y = 0, + .z = 0, + }; + copy.dst_offset = Offset3D{ + .x = 0, + .y = 0, + .z = is_dst_3d ? base.layer : 0, + }; + const Extent3D mip_size = AdjustMipSize(dst.size, base.level + level); + copy.extent = AdjustSamplesSize(mip_size, dst.num_samples); + if (is_dst_3d) { + copy.extent.depth = src.size.depth; + } + } + return copies; +} + +bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config) { + if (config.Address() == 0) { + return false; + } + if (config.Address() > (u64(1) << 48)) { + return false; + } + return gpu_memory.GpuToCpuAddress(config.Address()).has_value(); +} + +std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, + const ImageInfo& info, std::span<u8> output) { + const size_t guest_size_bytes = CalculateGuestSizeInBytes(info); + const u32 bpp_log2 = BytesPerBlockLog2(info.format); + const Extent3D size = info.size; + + if (info.type == ImageType::Linear) { + gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes); + + ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch); + return {{ + .buffer_offset = 0, + .buffer_size = guest_size_bytes, + .buffer_row_length = info.pitch >> bpp_log2, + .buffer_image_height = size.height, + .image_subresource = + { + .base_level = 0, + .base_layer = 0, + .num_layers = 1, + }, + .image_offset = {0, 0, 0}, + .image_extent = size, + }}; + } + const auto input_data = std::make_unique<u8[]>(guest_size_bytes); + gpu_memory.ReadBlockUnsafe(gpu_addr, input_data.get(), guest_size_bytes); + const std::span<const u8> input(input_data.get(), guest_size_bytes); + + const LevelInfo level_info = MakeLevelInfo(info); + const s32 num_layers = info.resources.layers; + const s32 num_levels = info.resources.levels; + const Extent2D tile_size = DefaultBlockSize(info.format); + const std::array level_sizes = CalculateLevelSizes(level_info, num_levels); + const Extent2D gob = GobSize(bpp_log2, info.block.height, info.tile_width_spacing); + const u32 layer_size = std::reduce(level_sizes.begin(), level_sizes.begin() + num_levels, 0); + const u32 layer_stride = AlignLayerSize(layer_size, size, level_info.block, tile_size.height, + info.tile_width_spacing); + size_t guest_offset = 0; + u32 host_offset = 0; + std::vector<BufferImageCopy> copies(num_levels); + + for (s32 level = 0; level < num_levels; ++level) { + const Extent3D level_size = AdjustMipSize(size, level); + const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size); + const u32 host_bytes_per_layer = num_blocks_per_layer << bpp_log2; + copies[level] = BufferImageCopy{ + .buffer_offset = host_offset, + .buffer_size = static_cast<size_t>(host_bytes_per_layer) * num_layers, + .buffer_row_length = Common::AlignUp(level_size.width, tile_size.width), + .buffer_image_height = Common::AlignUp(level_size.height, tile_size.height), + .image_subresource = + { + .base_level = level, + .base_layer = 0, + .num_layers = info.resources.layers, + }, + .image_offset = {0, 0, 0}, + .image_extent = level_size, + }; + const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); + const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); + size_t guest_layer_offset = 0; + + for (s32 layer = 0; layer < info.resources.layers; ++layer) { + const std::span<u8> dst = output.subspan(host_offset); + const std::span<const u8> src = input.subspan(guest_offset + guest_layer_offset); + UnswizzleTexture(dst, src, 1U << bpp_log2, num_tiles.width, num_tiles.height, + num_tiles.depth, block.height, block.depth, stride_alignment); + guest_layer_offset += layer_stride; + host_offset += host_bytes_per_layer; + } + guest_offset += level_sizes[level]; + } + return copies; +} + +BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, + const ImageBase& image, std::span<u8> output) { + gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), image.guest_size_bytes); + return BufferCopy{ + .src_offset = 0, + .dst_offset = 0, + .size = image.guest_size_bytes, + }; +} + +void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output, + std::span<BufferImageCopy> copies) { + u32 output_offset = 0; + + const Extent2D tile_size = DefaultBlockSize(info.format); + for (BufferImageCopy& copy : copies) { + const u32 level = copy.image_subresource.base_level; + const Extent3D mip_size = AdjustMipSize(info.size, level); + ASSERT(copy.image_offset == Offset3D{}); + ASSERT(copy.image_subresource.base_layer == 0); + ASSERT(copy.image_extent == mip_size); + ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); + ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); + + if (IsPixelFormatASTC(info.format)) { + ASSERT(copy.image_extent.depth == 1); + Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset), + copy.image_extent.width, copy.image_extent.height, + copy.image_subresource.num_layers, tile_size.width, + tile_size.height, output.subspan(output_offset)); + } else { + DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, + output.subspan(output_offset)); + } + copy.buffer_offset = output_offset; + copy.buffer_row_length = mip_size.width; + copy.buffer_image_height = mip_size.height; + + output_offset += copy.image_extent.width * copy.image_extent.height * + copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK; + } +} + +std::vector<BufferImageCopy> FullDownloadCopies(const ImageInfo& info) { + const Extent3D size = info.size; + const u32 bytes_per_block = BytesPerBlock(info.format); + if (info.type == ImageType::Linear) { + ASSERT(info.pitch % bytes_per_block == 0); + return {{ + .buffer_offset = 0, + .buffer_size = static_cast<size_t>(info.pitch) * size.height, + .buffer_row_length = info.pitch / bytes_per_block, + .buffer_image_height = size.height, + .image_subresource = + { + .base_level = 0, + .base_layer = 0, + .num_layers = 1, + }, + .image_offset = {0, 0, 0}, + .image_extent = size, + }}; + } + UNIMPLEMENTED_IF(info.tile_width_spacing > 0); + + const s32 num_layers = info.resources.layers; + const s32 num_levels = info.resources.levels; + const Extent2D tile_size = DefaultBlockSize(info.format); + + u32 host_offset = 0; + + std::vector<BufferImageCopy> copies(num_levels); + for (s32 level = 0; level < num_levels; ++level) { + const Extent3D level_size = AdjustMipSize(size, level); + const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size); + const u32 host_bytes_per_level = num_blocks_per_layer * bytes_per_block * num_layers; + copies[level] = BufferImageCopy{ + .buffer_offset = host_offset, + .buffer_size = host_bytes_per_level, + .buffer_row_length = level_size.width, + .buffer_image_height = level_size.height, + .image_subresource = + { + .base_level = level, + .base_layer = 0, + .num_layers = info.resources.layers, + }, + .image_offset = {0, 0, 0}, + .image_extent = level_size, + }; + host_offset += host_bytes_per_level; + } + return copies; +} + +Extent3D MipSize(Extent3D size, u32 level) { + return AdjustMipSize(size, level); +} + +Extent3D MipBlockSize(const ImageInfo& info, u32 level) { + const LevelInfo level_info = MakeLevelInfo(info); + const Extent2D tile_size = DefaultBlockSize(info.format); + const Extent3D level_size = AdjustMipSize(info.size, level); + const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); + return AdjustMipBlockSize(num_tiles, level_info.block, level); +} + +std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info) { + const Extent2D tile_size = DefaultBlockSize(info.format); + if (info.type == ImageType::Linear) { + return std::vector{SwizzleParameters{ + .num_tiles = AdjustTileSize(info.size, tile_size), + .block = {}, + .buffer_offset = 0, + .level = 0, + }}; + } + const LevelInfo level_info = MakeLevelInfo(info); + const Extent3D size = info.size; + const s32 num_levels = info.resources.levels; + + u32 guest_offset = 0; + std::vector<SwizzleParameters> params(num_levels); + for (s32 level = 0; level < num_levels; ++level) { + const Extent3D level_size = AdjustMipSize(size, level); + const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); + const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + params[level] = SwizzleParameters{ + .num_tiles = num_tiles, + .block = block, + .buffer_offset = guest_offset, + .level = level, + }; + guest_offset += CalculateLevelSize(level_info, level); + } + return params; +} + +void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info, + std::span<const BufferImageCopy> copies, std::span<const u8> memory) { + const bool is_pitch_linear = info.type == ImageType::Linear; + for (const BufferImageCopy& copy : copies) { + if (is_pitch_linear) { + SwizzlePitchLinearImage(gpu_memory, gpu_addr, info, copy, memory); + } else { + SwizzleBlockLinearImage(gpu_memory, gpu_addr, info, copy, memory); + } + } +} + +bool IsBlockLinearSizeCompatible(const ImageInfo& lhs, const ImageInfo& rhs, u32 lhs_level, + u32 rhs_level, bool strict_size) noexcept { + ASSERT(lhs.type != ImageType::Linear); + ASSERT(rhs.type != ImageType::Linear); + if (strict_size) { + const Extent3D lhs_size = AdjustMipSize(lhs.size, lhs_level); + const Extent3D rhs_size = AdjustMipSize(rhs.size, rhs_level); + return lhs_size.width == rhs_size.width && lhs_size.height == rhs_size.height; + } else { + const Extent3D lhs_size = BlockLinearAlignedSize(lhs, lhs_level); + const Extent3D rhs_size = BlockLinearAlignedSize(rhs, rhs_level); + return lhs_size.width == rhs_size.width && lhs_size.height == rhs_size.height; + } +} + +bool IsPitchLinearSameSize(const ImageInfo& lhs, const ImageInfo& rhs, bool strict_size) noexcept { + ASSERT(lhs.type == ImageType::Linear); + ASSERT(rhs.type == ImageType::Linear); + if (strict_size) { + return lhs.size.width == rhs.size.width && lhs.size.height == rhs.size.height; + } else { + const Extent2D lhs_size = PitchLinearAlignedSize(lhs); + const Extent2D rhs_size = PitchLinearAlignedSize(rhs); + return lhs_size == rhs_size; + } +} + +std::optional<OverlapResult> ResolveOverlap(const ImageInfo& new_info, GPUVAddr gpu_addr, + VAddr cpu_addr, const ImageBase& overlap, + bool strict_size, bool broken_views) { + ASSERT(new_info.type != ImageType::Linear); + ASSERT(overlap.info.type != ImageType::Linear); + if (!IsLayerStrideCompatible(new_info, overlap.info)) { + return std::nullopt; + } + if (!IsViewCompatible(overlap.info.format, new_info.format, broken_views)) { + return std::nullopt; + } + if (gpu_addr == overlap.gpu_addr) { + const std::optional solution = ResolveOverlapEqualAddress(new_info, overlap, strict_size); + if (!solution) { + return std::nullopt; + } + return OverlapResult{ + .gpu_addr = gpu_addr, + .cpu_addr = cpu_addr, + .resources = *solution, + }; + } + if (overlap.gpu_addr > gpu_addr) { + return ResolveOverlapRightAddress(new_info, gpu_addr, cpu_addr, overlap, strict_size); + } + // if overlap.gpu_addr < gpu_addr + return ResolveOverlapLeftAddress(new_info, gpu_addr, cpu_addr, overlap, strict_size); +} + +bool IsLayerStrideCompatible(const ImageInfo& lhs, const ImageInfo& rhs) { + // If either of the layer strides is zero, we can assume they are compatible + // These images generally come from rendertargets + if (lhs.layer_stride == 0) { + return true; + } + if (rhs.layer_stride == 0) { + return true; + } + // It's definitely compatible if the layer stride matches + if (lhs.layer_stride == rhs.layer_stride) { + return true; + } + // Although we also have to compare for cases where it can be unaligned + // This can happen if the image doesn't have layers, so the stride is not aligned + if (lhs.maybe_unaligned_layer_stride == rhs.maybe_unaligned_layer_stride) { + return true; + } + return false; +} + +std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const ImageBase& image, + GPUVAddr candidate_addr, RelaxedOptions options, + bool broken_views) { + const std::optional<SubresourceBase> base = image.TryFindBase(candidate_addr); + if (!base) { + return std::nullopt; + } + const ImageInfo& existing = image.info; + if (False(options & RelaxedOptions::Format)) { + if (!IsViewCompatible(existing.format, candidate.format, broken_views)) { + return std::nullopt; + } + } + if (!IsLayerStrideCompatible(existing, candidate)) { + return std::nullopt; + } + if (existing.type != candidate.type) { + return std::nullopt; + } + if (False(options & RelaxedOptions::Samples)) { + if (existing.num_samples != candidate.num_samples) { + return std::nullopt; + } + } + if (existing.resources.levels < candidate.resources.levels + base->level) { + return std::nullopt; + } + if (existing.type == ImageType::e3D) { + const u32 mip_depth = std::max(1U, existing.size.depth << base->level); + if (mip_depth < candidate.size.depth + base->layer) { + return std::nullopt; + } + } else { + if (existing.resources.layers < candidate.resources.layers + base->layer) { + return std::nullopt; + } + } + const bool strict_size = False(options & RelaxedOptions::Size); + if (!IsBlockLinearSizeCompatible(existing, candidate, base->level, 0, strict_size)) { + return std::nullopt; + } + // TODO: compare block sizes + return base; +} + +bool IsSubresource(const ImageInfo& candidate, const ImageBase& image, GPUVAddr candidate_addr, + RelaxedOptions options, bool broken_views) { + return FindSubresource(candidate, image, candidate_addr, options, broken_views).has_value(); +} + +void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase* dst, + const ImageBase* src) { + if (src && GetFormatType(src->info.format) != SurfaceType::ColorTexture) { + src_info.format = src->info.format; + } + if (dst && GetFormatType(dst->info.format) != SurfaceType::ColorTexture) { + dst_info.format = dst->info.format; + } + if (!dst && src && GetFormatType(src->info.format) != SurfaceType::ColorTexture) { + dst_info.format = src->info.format; + } + if (!src && dst && GetFormatType(dst->info.format) != SurfaceType::ColorTexture) { + src_info.format = src->info.format; + } +} + +u32 MapSizeBytes(const ImageBase& image) { + if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { + return image.guest_size_bytes; + } else if (True(image.flags & ImageFlagBits::Converted)) { + return image.converted_size_bytes; + } else { + return image.unswizzled_size_bytes; + } +} + +static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == + 0x7f8000); +static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x4000); + +static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 1, 0, 7) == + 0x2afc00); +static_assert(CalculateLevelOffset(PixelFormat::ASTC_2D_12X12_UNORM, {8192, 4096, 1}, {0, 2, 0}, 1, + 0, 12) == 0x50d200); + +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 0) == 0); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 1) == 0x400000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 2) == 0x500000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 3) == 0x540000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 4) == 0x550000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 5) == 0x554000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 6) == 0x555000); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 7) == 0x555400); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 8) == 0x555600); +static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0, + 9) == 0x555800); + +constexpr u32 ValidateLayerSize(PixelFormat format, u32 width, u32 height, u32 block_height, + u32 tile_width_spacing, u32 level) { + const Extent3D size{width, height, 1}; + const Extent3D block{0, block_height, 0}; + const u32 offset = CalculateLevelOffset(format, size, block, 1, tile_width_spacing, level); + return AlignLayerSize(offset, size, block, DefaultBlockHeight(format), tile_width_spacing); +} + +static_assert(ValidateLayerSize(PixelFormat::ASTC_2D_12X12_UNORM, 8192, 4096, 2, 0, 12) == + 0x50d800); +static_assert(ValidateLayerSize(PixelFormat::A8B8G8R8_UNORM, 1024, 1024, 2, 0, 10) == 0x556000); +static_assert(ValidateLayerSize(PixelFormat::BC3_UNORM, 128, 128, 2, 0, 8) == 0x6000); + +static_assert(ValidateLayerSize(PixelFormat::A8B8G8R8_UNORM, 518, 572, 4, 3, 1) == 0x190000, + "Tile width spacing is not working"); +static_assert(ValidateLayerSize(PixelFormat::BC5_UNORM, 1024, 1024, 3, 4, 11) == 0x160000, + "Compressed tile width spacing is not working"); + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h new file mode 100644 index 000000000..52a9207d6 --- /dev/null +++ b/src/video_core/texture_cache/util.h @@ -0,0 +1,109 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <span> + +#include "common/common_types.h" + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/image_base.h" +#include "video_core/texture_cache/image_view_base.h" +#include "video_core/texture_cache/types.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using Tegra::Texture::TICEntry; + +struct OverlapResult { + GPUVAddr gpu_addr; + VAddr cpu_addr; + SubresourceExtent resources; +}; + +[[nodiscard]] u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept; + +[[nodiscard]] u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept; + +[[nodiscard]] u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept; + +[[nodiscard]] u32 CalculateLayerStride(const ImageInfo& info) noexcept; + +[[nodiscard]] u32 CalculateLayerSize(const ImageInfo& info) noexcept; + +[[nodiscard]] std::array<u32, MAX_MIP_LEVELS> CalculateMipLevelOffsets( + const ImageInfo& info) noexcept; + +[[nodiscard]] std::vector<u32> CalculateSliceOffsets(const ImageInfo& info); + +[[nodiscard]] std::vector<SubresourceBase> CalculateSliceSubresources(const ImageInfo& info); + +[[nodiscard]] u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level); + +[[nodiscard]] VideoCore::Surface::PixelFormat PixelFormatFromTIC( + const Tegra::Texture::TICEntry& config) noexcept; + +[[nodiscard]] ImageViewType RenderTargetImageViewType(const ImageInfo& info) noexcept; + +[[nodiscard]] std::vector<ImageCopy> MakeShrinkImageCopies(const ImageInfo& dst, + const ImageInfo& src, + SubresourceBase base); + +[[nodiscard]] bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config); + +[[nodiscard]] std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, + GPUVAddr gpu_addr, const ImageInfo& info, + std::span<u8> output); + +[[nodiscard]] BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, + const ImageBase& image, std::span<u8> output); + +void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output, + std::span<BufferImageCopy> copies); + +[[nodiscard]] std::vector<BufferImageCopy> FullDownloadCopies(const ImageInfo& info); + +[[nodiscard]] Extent3D MipSize(Extent3D size, u32 level); + +[[nodiscard]] Extent3D MipBlockSize(const ImageInfo& info, u32 level); + +[[nodiscard]] std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info); + +void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info, + std::span<const BufferImageCopy> copies, std::span<const u8> memory); + +[[nodiscard]] bool IsBlockLinearSizeCompatible(const ImageInfo& new_info, + const ImageInfo& overlap_info, u32 new_level, + u32 overlap_level, bool strict_size) noexcept; + +[[nodiscard]] bool IsPitchLinearSameSize(const ImageInfo& lhs, const ImageInfo& rhs, + bool strict_size) noexcept; + +[[nodiscard]] std::optional<OverlapResult> ResolveOverlap(const ImageInfo& new_info, + GPUVAddr gpu_addr, VAddr cpu_addr, + const ImageBase& overlap, + bool strict_size, bool broken_views); + +[[nodiscard]] bool IsLayerStrideCompatible(const ImageInfo& lhs, const ImageInfo& rhs); + +[[nodiscard]] std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, + const ImageBase& image, + GPUVAddr candidate_addr, + RelaxedOptions options, + bool broken_views); + +[[nodiscard]] bool IsSubresource(const ImageInfo& candidate, const ImageBase& image, + GPUVAddr candidate_addr, RelaxedOptions options, + bool broken_views); + +void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase* dst, + const ImageBase* src); + +[[nodiscard]] u32 MapSizeBytes(const ImageBase& image); + +} // namespace VideoCommon diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index 365bde2f1..3625b666c 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -18,6 +18,7 @@ #include <algorithm> #include <cassert> #include <cstring> +#include <span> #include <vector> #include <boost/container/static_vector.hpp> @@ -41,21 +42,24 @@ constexpr u32 Popcnt(u32 n) { class InputBitStream { public: - constexpr explicit InputBitStream(const u8* ptr, std::size_t start_offset = 0) - : cur_byte{ptr}, next_bit{start_offset % 8} {} + constexpr explicit InputBitStream(std::span<const u8> data, size_t start_offset = 0) + : cur_byte{data.data()}, total_bits{data.size()}, next_bit{start_offset % 8} {} - constexpr std::size_t GetBitsRead() const { + constexpr size_t GetBitsRead() const { return bits_read; } constexpr bool ReadBit() { - const bool bit = (*cur_byte >> next_bit++) & 1; + if (bits_read >= total_bits * 8) { + return 0; + } + const bool bit = ((*cur_byte >> next_bit) & 1) != 0; + ++next_bit; while (next_bit >= 8) { next_bit -= 8; - cur_byte++; + ++cur_byte; } - - bits_read++; + ++bits_read; return bit; } @@ -78,8 +82,9 @@ public: private: const u8* cur_byte; - std::size_t next_bit = 0; - std::size_t bits_read = 0; + size_t total_bits = 0; + size_t next_bit = 0; + size_t bits_read = 0; }; class OutputBitStream { @@ -192,15 +197,15 @@ struct IntegerEncodedValue { }; }; using IntegerEncodedVector = boost::container::static_vector< - IntegerEncodedValue, 64, + IntegerEncodedValue, 256, boost::container::static_vector_options< boost::container::inplace_alignment<alignof(IntegerEncodedValue)>, boost::container::throw_on_overflow<false>>::type>; static void DecodeTritBlock(InputBitStream& bits, IntegerEncodedVector& result, u32 nBitsPerValue) { // Implement the algorithm in section C.2.12 - u32 m[5]; - u32 t[5]; + std::array<u32, 5> m; + std::array<u32, 5> t; u32 T; // Read the trit encoded block according to @@ -600,7 +605,7 @@ static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) { return params; } -static void FillVoidExtentLDR(InputBitStream& strm, u32* const outBuf, u32 blockWidth, +static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { // Don't actually care about the void extent, just read the bits... for (s32 i = 0; i < 4; ++i) { @@ -623,7 +628,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, u32* const outBuf, u32 block } } -static void FillError(u32* outBuf, u32 blockWidth, u32 blockHeight) { +static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { for (u32 j = 0; j < blockHeight; j++) { for (u32 i = 0; i < blockWidth; i++) { outBuf[j * blockWidth + i] = 0xFFFF00FF; @@ -865,7 +870,7 @@ public: } }; -static void DecodeColorValues(u32* out, u8* data, const u32* modes, const u32 nPartitions, +static void DecodeColorValues(u32* out, std::span<u8> data, const u32* modes, const u32 nPartitions, const u32 nBitsForColorData) { // First figure out how many color values we have u32 nValues = 0; @@ -897,7 +902,7 @@ static void DecodeColorValues(u32* out, u8* data, const u32* modes, const u32 nP // We now have enough to decode our integer sequence. IntegerEncodedVector decodedColorValues; - InputBitStream colorStream(data); + InputBitStream colorStream(data, 0); DecodeIntegerSequence(decodedColorValues, colorStream, range, nValues); // Once we have the decoded values, we need to dequantize them to the 0-255 range @@ -1438,8 +1443,8 @@ static void ComputeEndpos32s(Pixel& ep1, Pixel& ep2, const u32*& colorValues, #undef READ_INT_VALUES } -static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32 blockHeight, - u32* outBuf) { +static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth, + const u32 blockHeight, std::span<u32, 12 * 12> outBuf) { InputBitStream strm(inBuf); TexelWeightParams weightParams = DecodeBlockInfo(strm); @@ -1601,8 +1606,8 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32 } // Read the texel weight data.. - u8 texelWeightData[16]; - memcpy(texelWeightData, inBuf, sizeof(texelWeightData)); + std::array<u8, 16> texelWeightData; + std::ranges::copy(inBuf, texelWeightData.begin()); // Reverse everything for (u32 i = 0; i < 8; i++) { @@ -1618,10 +1623,12 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32 // Make sure that higher non-texel bits are set to zero const u32 clearByteStart = (weightParams.GetPackedBitSize() >> 3) + 1; - texelWeightData[clearByteStart - 1] = - texelWeightData[clearByteStart - 1] & - static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1); - memset(texelWeightData + clearByteStart, 0, 16 - clearByteStart); + if (clearByteStart > 0 && clearByteStart <= texelWeightData.size()) { + texelWeightData[clearByteStart - 1] &= + static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1); + std::memset(texelWeightData.data() + clearByteStart, 0, + std::min(16U - clearByteStart, 16U)); + } IntegerEncodedVector texelWeightValues; @@ -1672,36 +1679,32 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32 namespace Tegra::Texture::ASTC { -std::vector<u8> Decompress(const u8* data, u32 width, u32 height, u32 depth, u32 block_width, - u32 block_height) { - u32 blockIdx = 0; +void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, + uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) { + u32 block_index = 0; std::size_t depth_offset = 0; - std::vector<u8> outData(height * width * depth * 4); - for (u32 k = 0; k < depth; k++) { - for (u32 j = 0; j < height; j += block_height) { - for (u32 i = 0; i < width; i += block_width) { - - const u8* blockPtr = data + blockIdx * 16; + for (u32 z = 0; z < depth; z++) { + for (u32 y = 0; y < height; y += block_height) { + for (u32 x = 0; x < width; x += block_width) { + const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)}; // Blocks can be at most 12x12 - u32 uncompData[144]; + std::array<u32, 12 * 12> uncompData; ASTCC::DecompressBlock(blockPtr, block_width, block_height, uncompData); - u32 decompWidth = std::min(block_width, width - i); - u32 decompHeight = std::min(block_height, height - j); + u32 decompWidth = std::min(block_width, width - x); + u32 decompHeight = std::min(block_height, height - y); - u8* outRow = depth_offset + outData.data() + (j * width + i) * 4; + const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4); for (u32 jj = 0; jj < decompHeight; jj++) { - memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4); + std::memcpy(outRow.data() + jj * width * 4, + uncompData.data() + jj * block_width, decompWidth * 4); } - - blockIdx++; + ++block_index; } } depth_offset += height * width * 4; } - - return outData; } } // namespace Tegra::Texture::ASTC diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h index 991cdba72..9105119bc 100644 --- a/src/video_core/textures/astc.h +++ b/src/video_core/textures/astc.h @@ -5,11 +5,10 @@ #pragma once #include <cstdint> -#include <vector> namespace Tegra::Texture::ASTC { -std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t height, - uint32_t depth, uint32_t block_width, uint32_t block_height); +void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, + uint32_t block_width, uint32_t block_height, std::span<uint8_t> output); } // namespace Tegra::Texture::ASTC diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp deleted file mode 100644 index 962921483..000000000 --- a/src/video_core/textures/convert.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cstring> -#include <tuple> -#include <vector> - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "video_core/surface.h" -#include "video_core/textures/astc.h" -#include "video_core/textures/convert.h" - -namespace Tegra::Texture { - -using VideoCore::Surface::PixelFormat; - -template <bool reverse> -void SwapS8Z24ToZ24S8(u8* data, u32 width, u32 height) { - union S8Z24 { - BitField<0, 24, u32> z24; - BitField<24, 8, u32> s8; - }; - static_assert(sizeof(S8Z24) == 4, "S8Z24 is incorrect size"); - - union Z24S8 { - BitField<0, 8, u32> s8; - BitField<8, 24, u32> z24; - }; - static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size"); - - S8Z24 s8z24_pixel{}; - Z24S8 z24s8_pixel{}; - constexpr auto bpp{ - VideoCore::Surface::GetBytesPerPixel(VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM)}; - for (std::size_t y = 0; y < height; ++y) { - for (std::size_t x = 0; x < width; ++x) { - const std::size_t offset{bpp * (y * width + x)}; - if constexpr (reverse) { - std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8)); - s8z24_pixel.s8.Assign(z24s8_pixel.s8); - s8z24_pixel.z24.Assign(z24s8_pixel.z24); - std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24)); - } else { - std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24)); - z24s8_pixel.s8.Assign(s8z24_pixel.s8); - z24s8_pixel.z24.Assign(s8z24_pixel.z24); - std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8)); - } - } - } -} - -static void ConvertS8Z24ToZ24S8(u8* data, u32 width, u32 height) { - SwapS8Z24ToZ24S8<false>(data, width, height); -} - -static void ConvertZ24S8ToS8Z24(u8* data, u32 width, u32 height) { - SwapS8Z24ToZ24S8<true>(data, width, height); -} - -void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format, u32 width, - u32 height, u32 depth, bool convert_astc, bool convert_s8z24) { - if (convert_astc && IsPixelFormatASTC(pixel_format)) { - // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC. - u32 block_width{}; - u32 block_height{}; - std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format); - const std::vector<u8> rgba8_data = Tegra::Texture::ASTC::Decompress( - in_data, width, height, depth, block_width, block_height); - std::copy(rgba8_data.begin(), rgba8_data.end(), out_data); - - } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) { - Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height); - } -} - -void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 height, u32 depth, - bool convert_astc, bool convert_s8z24) { - if (convert_astc && IsPixelFormatASTC(pixel_format)) { - LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented", - static_cast<u32>(pixel_format)); - UNREACHABLE(); - - } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) { - Tegra::Texture::ConvertZ24S8ToS8Z24(data, width, height); - } -} - -} // namespace Tegra::Texture diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h deleted file mode 100644 index d5d6c77bb..000000000 --- a/src/video_core/textures/convert.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace VideoCore::Surface { -enum class PixelFormat; -} - -namespace Tegra::Texture { - -void ConvertFromGuestToHost(u8* in_data, u8* out_data, VideoCore::Surface::PixelFormat pixel_format, - u32 width, u32 height, u32 depth, bool convert_astc, - bool convert_s8z24); - -void ConvertFromHostToGuest(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width, - u32 height, u32 depth, bool convert_astc, bool convert_s8z24); - -} // namespace Tegra::Texture diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 16d46a018..62685a183 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -2,204 +2,111 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <cmath> #include <cstring> +#include <span> +#include <utility> + #include "common/alignment.h" #include "common/assert.h" #include "common/bit_util.h" +#include "common/div_ceil.h" #include "video_core/gpu.h" #include "video_core/textures/decoders.h" #include "video_core/textures/texture.h" namespace Tegra::Texture { -namespace { +namespace { /** - * This table represents the internal swizzle of a gob, - * in format 16 bytes x 2 sector packing. + * This table represents the internal swizzle of a gob, in format 16 bytes x 2 sector packing. * Calculates the offset of an (x, y) position within a swizzled texture. * Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188 */ -template <std::size_t N, std::size_t M, u32 Align> -struct alignas(64) SwizzleTable { - static_assert(M * Align == 64, "Swizzle Table does not align to GOB"); - constexpr SwizzleTable() { - for (u32 y = 0; y < N; ++y) { - for (u32 x = 0; x < M; ++x) { - const u32 x2 = x * Align; - values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 + - ((x2 % 32) / 16) * 32 + (y % 2) * 16 + (x2 % 16)); - } +constexpr SwizzleTable MakeSwizzleTableConst() { + SwizzleTable table{}; + for (u32 y = 0; y < table.size(); ++y) { + for (u32 x = 0; x < table[0].size(); ++x) { + table[y][x] = ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); } } - const std::array<u16, M>& operator[](std::size_t index) const { - return values[index]; - } - std::array<std::array<u16, M>, N> values{}; -}; + return table; +} -constexpr u32 FAST_SWIZZLE_ALIGN = 16; +constexpr SwizzleTable SWIZZLE_TABLE = MakeSwizzleTableConst(); -constexpr auto LEGACY_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_X, GOB_SIZE_X, GOB_SIZE_Z>(); -constexpr auto FAST_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_Y, 4, FAST_SWIZZLE_ALIGN>(); +template <bool TO_LINEAR> +void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { + // The origin of the transformation can be configured here, leave it as zero as the current API + // doesn't expose it. + static constexpr u32 origin_x = 0; + static constexpr u32 origin_y = 0; + static constexpr u32 origin_z = 0; -/** - * This function manages ALL the GOBs(Group of Bytes) Inside a single block. - * Instead of going gob by gob, we map the coordinates inside a block and manage from - * those. Block_Width is assumed to be 1. - */ -void PreciseProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle, - const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end, - const u32 y_end, const u32 z_end, const u32 tile_offset, - const u32 xy_block_size, const u32 layer_z, const u32 stride_x, - const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) { - std::array<u8*, 2> data_ptrs; - u32 z_address = tile_offset; - - for (u32 z = z_start; z < z_end; z++) { - u32 y_address = z_address; - u32 pixel_base = layer_z * z + y_start * stride_x; - for (u32 y = y_start; y < y_end; y++) { - const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y]; - for (u32 x = x_start; x < x_end; x++) { - const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % GOB_SIZE_X]}; - const u32 pixel_index{x * out_bytes_per_pixel + pixel_base}; - data_ptrs[unswizzle] = swizzled_data + swizzle_offset; - data_ptrs[!unswizzle] = unswizzled_data + pixel_index; - std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - pixel_base += stride_x; - if ((y + 1) % GOB_SIZE_Y == 0) - y_address += GOB_SIZE; - } - z_address += xy_block_size; - } -} + // We can configure here a custom pitch + // As it's not exposed 'width * bpp' will be the expected pitch. + const u32 pitch = width * bytes_per_pixel; + const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; -/** - * This function manages ALL the GOBs(Group of Bytes) Inside a single block. - * Instead of going gob by gob, we map the coordinates inside a block and manage from - * those. Block_Width is assumed to be 1. - */ -void FastProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle, - const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end, - const u32 y_end, const u32 z_end, const u32 tile_offset, - const u32 xy_block_size, const u32 layer_z, const u32 stride_x, - const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) { - std::array<u8*, 2> data_ptrs; - u32 z_address = tile_offset; - const u32 x_startb = x_start * bytes_per_pixel; - const u32 x_endb = x_end * bytes_per_pixel; - - for (u32 z = z_start; z < z_end; z++) { - u32 y_address = z_address; - u32 pixel_base = layer_z * z + y_start * stride_x; - for (u32 y = y_start; y < y_end; y++) { - const auto& table = FAST_SWIZZLE_TABLE[y % GOB_SIZE_Y]; - for (u32 xb = x_startb; xb < x_endb; xb += FAST_SWIZZLE_ALIGN) { - const u32 swizzle_offset{y_address + table[(xb / FAST_SWIZZLE_ALIGN) % 4]}; - const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel; - const u32 pixel_index{out_x + pixel_base}; - data_ptrs[unswizzle ? 1 : 0] = swizzled_data + swizzle_offset; - data_ptrs[unswizzle ? 0 : 1] = unswizzled_data + pixel_index; - std::memcpy(data_ptrs[0], data_ptrs[1], FAST_SWIZZLE_ALIGN); - } - pixel_base += stride_x; - if ((y + 1) % GOB_SIZE_Y == 0) - y_address += GOB_SIZE; - } - z_address += xy_block_size; - } -} + const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); + const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); + const u32 slice_size = + Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size; -/** - * This function unswizzles or swizzles a texture by mapping Linear to BlockLinear Textue. - * The body of this function takes care of splitting the swizzled texture into blocks, - * and managing the extents of it. Once all the parameters of a single block are obtained, - * the function calls 'ProcessBlock' to process that particular Block. - * - * Documentation for the memory layout and decoding can be found at: - * https://envytools.readthedocs.io/en/latest/hw/memory/g80-surface.html#blocklinear-surfaces - */ -template <bool fast> -void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle, - const u32 width, const u32 height, const u32 depth, const u32 bytes_per_pixel, - const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth, - const u32 width_spacing) { - auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); }; - const u32 stride_x = width * out_bytes_per_pixel; - const u32 layer_z = height * stride_x; - const u32 gob_elements_x = GOB_SIZE_X / bytes_per_pixel; - constexpr u32 gob_elements_y = GOB_SIZE_Y; - constexpr u32 gob_elements_z = GOB_SIZE_Z; - const u32 block_x_elements = gob_elements_x; - const u32 block_y_elements = gob_elements_y * block_height; - const u32 block_z_elements = gob_elements_z * block_depth; - const u32 aligned_width = Common::AlignUp(width, gob_elements_x * width_spacing); - const u32 blocks_on_x = div_ceil(aligned_width, block_x_elements); - const u32 blocks_on_y = div_ceil(height, block_y_elements); - const u32 blocks_on_z = div_ceil(depth, block_z_elements); - const u32 xy_block_size = GOB_SIZE * block_height; - const u32 block_size = xy_block_size * block_depth; - u32 tile_offset = 0; - for (u32 zb = 0; zb < blocks_on_z; zb++) { - const u32 z_start = zb * block_z_elements; - const u32 z_end = std::min(depth, z_start + block_z_elements); - for (u32 yb = 0; yb < blocks_on_y; yb++) { - const u32 y_start = yb * block_y_elements; - const u32 y_end = std::min(height, y_start + block_y_elements); - for (u32 xb = 0; xb < blocks_on_x; xb++) { - const u32 x_start = xb * block_x_elements; - const u32 x_end = std::min(width, x_start + block_x_elements); - if constexpr (fast) { - FastProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start, - z_start, x_end, y_end, z_end, tile_offset, xy_block_size, - layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel); - } else { - PreciseProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start, - z_start, x_end, y_end, z_end, tile_offset, xy_block_size, - layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel); - } - tile_offset += block_size; + const u32 block_height_mask = (1U << block_height) - 1; + const u32 block_depth_mask = (1U << block_depth) - 1; + const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth; + + for (u32 slice = 0; slice < depth; ++slice) { + const u32 z = slice + origin_z; + const u32 offset_z = (z >> block_depth) * slice_size + + ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height)); + for (u32 line = 0; line < height; ++line) { + const u32 y = line + origin_y; + const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; + + const u32 block_y = y >> GOB_SIZE_Y_SHIFT; + const u32 offset_y = (block_y >> block_height) * block_size + + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); + + for (u32 column = 0; column < width; ++column) { + const u32 x = (column + origin_x) * bytes_per_pixel; + const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; + + const u32 base_swizzled_offset = offset_z + offset_y + offset_x; + const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; + + const u32 unswizzled_offset = + slice * pitch * height + line * pitch + column * bytes_per_pixel; + + u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; + const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; + std::memcpy(dst, src, bytes_per_pixel); } } } } - } // Anonymous namespace -void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, - u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data, - bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) { - const u32 block_height_size{1U << block_height}; - const u32 block_depth_size{1U << block_depth}; - if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % FAST_SWIZZLE_ALIGN == 0) { - SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height_size, - block_depth_size, width_spacing); - } else { - SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height_size, - block_depth_size, width_spacing); - } +SwizzleTable MakeSwizzleTable() { + return SWIZZLE_TABLE; } -void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y, - u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, - u32 block_depth, u32 width_spacing) { - CopySwizzledData((width + tile_size_x - 1) / tile_size_x, - (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, - bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth, - width_spacing); +void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { + Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride_alignment); } -std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, - u32 width, u32 height, u32 depth, u32 block_height, - u32 block_depth, u32 width_spacing) { - std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); - UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel, - width, height, depth, block_height, block_depth, width_spacing); - return unswizzled_data; +void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment) { + Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, + stride_alignment); } void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, @@ -213,7 +120,7 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 const u32 gob_address_y = (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; - const auto& table = LEGACY_SWIZZLE_TABLE[dst_y % GOB_SIZE_Y]; + const auto& table = SWIZZLE_TABLE[dst_y % GOB_SIZE_Y]; for (u32 x = 0; x < subrect_width; ++x) { const u32 dst_x = x + offset_x; const u32 gob_address = @@ -235,11 +142,11 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height); const u32 block_height_mask = (1U << block_height) - 1; - const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height; + const u32 x_shift = GOB_SIZE_SHIFT + block_height; for (u32 line = 0; line < line_count; ++line) { const u32 src_y = line + origin_y; - const auto& table = LEGACY_SWIZZLE_TABLE[src_y % GOB_SIZE_Y]; + const auto& table = SWIZZLE_TABLE[src_y % GOB_SIZE_Y]; const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT; const u32 src_offset_y = (block_y >> block_height) * block_size + @@ -270,7 +177,7 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth; for (u32 line = 0; line < line_count; ++line) { - const auto& table = LEGACY_SWIZZLE_TABLE[line % GOB_SIZE_Y]; + const auto& table = SWIZZLE_TABLE[line % GOB_SIZE_Y]; const u32 block_y = line / GOB_SIZE_Y; const u32 dst_offset_y = (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE; @@ -293,7 +200,7 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 const std::size_t gob_address_y = (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; - const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y]; + const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; for (std::size_t x = dst_x; x < width && count < copy_size; ++x) { const std::size_t gob_address = gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height; @@ -310,9 +217,9 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { if (tiled) { - const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, GOB_SIZE_X_SHIFT); - const u32 aligned_height = Common::AlignBits(height, GOB_SIZE_Y_SHIFT + block_height); - const u32 aligned_depth = Common::AlignBits(depth, GOB_SIZE_Z_SHIFT + block_depth); + const u32 aligned_width = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT); + const u32 aligned_height = Common::AlignUpLog2(height, GOB_SIZE_Y_SHIFT + block_height); + const u32 aligned_depth = Common::AlignUpLog2(depth, GOB_SIZE_Z_SHIFT + block_depth); return aligned_width * aligned_height * aligned_depth; } else { return width * height * depth * bytes_per_pixel; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 01e156bc8..d7cdc81e8 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -4,7 +4,8 @@ #pragma once -#include <vector> +#include <span> + #include "common/common_types.h" #include "video_core/textures/texture.h" @@ -15,28 +16,25 @@ constexpr u32 GOB_SIZE_Y = 8; constexpr u32 GOB_SIZE_Z = 1; constexpr u32 GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z; -constexpr std::size_t GOB_SIZE_X_SHIFT = 6; -constexpr std::size_t GOB_SIZE_Y_SHIFT = 3; -constexpr std::size_t GOB_SIZE_Z_SHIFT = 0; -constexpr std::size_t GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; - -/// Unswizzles a swizzled texture without changing its format. -void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y, - u32 bytes_per_pixel, u32 width, u32 height, u32 depth, - u32 block_height = TICEntry::DefaultBlockHeight, - u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0); - -/// Unswizzles a swizzled texture without changing its format. -std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, - u32 width, u32 height, u32 depth, - u32 block_height = TICEntry::DefaultBlockHeight, - u32 block_depth = TICEntry::DefaultBlockHeight, - u32 width_spacing = 0); - -/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary. -void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, - u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, - bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing); +constexpr u32 GOB_SIZE_X_SHIFT = 6; +constexpr u32 GOB_SIZE_Y_SHIFT = 3; +constexpr u32 GOB_SIZE_Z_SHIFT = 0; +constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; + +using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>; + +/// Returns a z-order swizzle table +SwizzleTable MakeSwizzleTable(); + +/// Unswizzles a block linear texture into linear memory. +void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment = 1); + +/// Swizzles linear memory into a block linear texture. +void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, + u32 stride_alignment = 1); /// This function calculates the correct size of a texture depending if it's tiled or not. std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index 4171e3ef2..ae5621a7d 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp @@ -5,9 +5,13 @@ #include <algorithm> #include <array> +#include "common/cityhash.h" #include "core/settings.h" #include "video_core/textures/texture.h" +using Tegra::Texture::TICEntry; +using Tegra::Texture::TSCEntry; + namespace Tegra::Texture { namespace { @@ -65,7 +69,7 @@ unsigned SettingsMinimumAnisotropy() noexcept { } // Anonymous namespace -std::array<float, 4> TSCEntry::GetBorderColor() const noexcept { +std::array<float, 4> TSCEntry::BorderColor() const noexcept { if (!srgb_conversion) { return border_color; } @@ -73,8 +77,16 @@ std::array<float, 4> TSCEntry::GetBorderColor() const noexcept { SRGB_CONVERSION_LUT[srgb_border_color_b], border_color[3]}; } -float TSCEntry::GetMaxAnisotropy() const noexcept { +float TSCEntry::MaxAnisotropy() const noexcept { return static_cast<float>(std::max(1U << max_anisotropy, SettingsMinimumAnisotropy())); } } // namespace Tegra::Texture + +size_t std::hash<TICEntry>::operator()(const TICEntry& tic) const noexcept { + return Common::CityHash64(reinterpret_cast<const char*>(&tic), sizeof tic); +} + +size_t std::hash<TSCEntry>::operator()(const TSCEntry& tsc) const noexcept { + return Common::CityHash64(reinterpret_cast<const char*>(&tsc), sizeof tsc); +} diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 0574fef12..c1d14335e 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -53,27 +53,27 @@ enum class TextureFormat : u32 { BC4 = 0x27, BC5 = 0x28, S8D24 = 0x29, - X8Z24 = 0x2a, + X8D24 = 0x2a, D24S8 = 0x2b, - X4V4Z24__COV4R4V = 0x2c, - X4V4Z24__COV8R8V = 0x2d, - V8Z24__COV4R12V = 0x2e, + X4V4D24__COV4R4V = 0x2c, + X4V4D24__COV8R8V = 0x2d, + V8D24__COV4R12V = 0x2e, D32 = 0x2f, D32S8 = 0x30, - X8Z24_X20V4S8__COV4R4V = 0x31, - X8Z24_X20V4S8__COV8R8V = 0x32, - ZF32_X20V4X8__COV4R4V = 0x33, - ZF32_X20V4X8__COV8R8V = 0x34, - ZF32_X20V4S8__COV4R4V = 0x35, - ZF32_X20V4S8__COV8R8V = 0x36, - X8Z24_X16V8S8__COV4R12V = 0x37, - ZF32_X16V8X8__COV4R12V = 0x38, - ZF32_X16V8S8__COV4R12V = 0x39, + X8D24_X20V4S8__COV4R4V = 0x31, + X8D24_X20V4S8__COV8R8V = 0x32, + D32_X20V4X8__COV4R4V = 0x33, + D32_X20V4X8__COV8R8V = 0x34, + D32_X20V4S8__COV4R4V = 0x35, + D32_X20V4S8__COV8R8V = 0x36, + X8D24_X16V8S8__COV4R12V = 0x37, + D32_X16V8X8__COV4R12V = 0x38, + D32_X16V8S8__COV4R12V = 0x39, D16 = 0x3a, - V8Z24__COV8R24V = 0x3b, - X8Z24_X16V8S8__COV8R24V = 0x3c, - ZF32_X16V8X8__COV8R24V = 0x3d, - ZF32_X16V8S8__COV8R24V = 0x3e, + V8D24__COV8R24V = 0x3b, + X8D24_X16V8S8__COV8R24V = 0x3c, + D32_X16V8X8__COV8R24V = 0x3d, + D32_X16V8S8__COV8R24V = 0x3e, ASTC_2D_4X4 = 0x40, ASTC_2D_5X5 = 0x41, ASTC_2D_6X6 = 0x42, @@ -146,7 +146,7 @@ enum class MsaaMode : u32 { }; union TextureHandle { - TextureHandle(u32 raw) : raw{raw} {} + /* implicit */ constexpr TextureHandle(u32 raw_) : raw{raw_} {} u32 raw; BitField<0, 20, u32> tic_id; @@ -155,124 +155,124 @@ union TextureHandle { static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); struct TICEntry { - static constexpr u32 DefaultBlockHeight = 16; - static constexpr u32 DefaultBlockDepth = 1; - - union { - u32 raw; - BitField<0, 7, TextureFormat> format; - BitField<7, 3, ComponentType> r_type; - BitField<10, 3, ComponentType> g_type; - BitField<13, 3, ComponentType> b_type; - BitField<16, 3, ComponentType> a_type; - - BitField<19, 3, SwizzleSource> x_source; - BitField<22, 3, SwizzleSource> y_source; - BitField<25, 3, SwizzleSource> z_source; - BitField<28, 3, SwizzleSource> w_source; - }; - u32 address_low; union { - BitField<0, 16, u32> address_high; - BitField<21, 3, TICHeaderVersion> header_version; - }; - union { - BitField<0, 3, u32> block_width; - BitField<3, 3, u32> block_height; - BitField<6, 3, u32> block_depth; + struct { + union { + BitField<0, 7, TextureFormat> format; + BitField<7, 3, ComponentType> r_type; + BitField<10, 3, ComponentType> g_type; + BitField<13, 3, ComponentType> b_type; + BitField<16, 3, ComponentType> a_type; + + BitField<19, 3, SwizzleSource> x_source; + BitField<22, 3, SwizzleSource> y_source; + BitField<25, 3, SwizzleSource> z_source; + BitField<28, 3, SwizzleSource> w_source; + }; + u32 address_low; + union { + BitField<0, 16, u32> address_high; + BitField<16, 5, u32> layer_base_3_7; + BitField<21, 3, TICHeaderVersion> header_version; + BitField<24, 1, u32> load_store_hint; + BitField<25, 4, u32> view_coherency_hash; + BitField<29, 3, u32> layer_base_8_10; + }; + union { + BitField<0, 3, u32> block_width; + BitField<3, 3, u32> block_height; + BitField<6, 3, u32> block_depth; - BitField<10, 3, u32> tile_width_spacing; + BitField<10, 3, u32> tile_width_spacing; - // High 16 bits of the pitch value - BitField<0, 16, u32> pitch_high; - BitField<26, 1, u32> use_header_opt_control; - BitField<27, 1, u32> depth_texture; - BitField<28, 4, u32> max_mip_level; + // High 16 bits of the pitch value + BitField<0, 16, u32> pitch_high; + BitField<26, 1, u32> use_header_opt_control; + BitField<27, 1, u32> depth_texture; + BitField<28, 4, u32> max_mip_level; - BitField<0, 16, u32> buffer_high_width_minus_one; - }; - union { - BitField<0, 16, u32> width_minus_1; - BitField<22, 1, u32> srgb_conversion; - BitField<23, 4, TextureType> texture_type; - BitField<29, 3, u32> border_size; + BitField<0, 16, u32> buffer_high_width_minus_one; + }; + union { + BitField<0, 16, u32> width_minus_one; + BitField<16, 3, u32> layer_base_0_2; + BitField<22, 1, u32> srgb_conversion; + BitField<23, 4, TextureType> texture_type; + BitField<29, 3, u32> border_size; - BitField<0, 16, u32> buffer_low_width_minus_one; - }; - union { - BitField<0, 16, u32> height_minus_1; - BitField<16, 14, u32> depth_minus_1; - }; - union { - BitField<6, 13, u32> mip_lod_bias; - BitField<27, 3, u32> max_anisotropy; + BitField<0, 16, u32> buffer_low_width_minus_one; + }; + union { + BitField<0, 16, u32> height_minus_1; + BitField<16, 14, u32> depth_minus_1; + BitField<30, 1, u32> is_sparse; + BitField<31, 1, u32> normalized_coords; + }; + union { + BitField<6, 13, u32> mip_lod_bias; + BitField<27, 3, u32> max_anisotropy; + }; + union { + BitField<0, 4, u32> res_min_mip_level; + BitField<4, 4, u32> res_max_mip_level; + BitField<8, 4, MsaaMode> msaa_mode; + BitField<12, 12, u32> min_lod_clamp; + }; + }; + std::array<u64, 4> raw; }; - union { - BitField<0, 4, u32> res_min_mip_level; - BitField<4, 4, u32> res_max_mip_level; - BitField<8, 4, MsaaMode> msaa_mode; - BitField<12, 12, u32> min_lod_clamp; - }; + constexpr bool operator==(const TICEntry& rhs) const noexcept { + return raw == rhs.raw; + } - GPUVAddr Address() const { + constexpr bool operator!=(const TICEntry& rhs) const noexcept { + return raw != rhs.raw; + } + + constexpr GPUVAddr Address() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); } - u32 Pitch() const { + constexpr u32 Pitch() const { ASSERT(header_version == TICHeaderVersion::Pitch || header_version == TICHeaderVersion::PitchColorKey); // The pitch value is 21 bits, and is 32B aligned. return pitch_high << 5; } - u32 Width() const { + constexpr u32 Width() const { if (header_version != TICHeaderVersion::OneDBuffer) { - return width_minus_1 + 1; + return width_minus_one + 1; } - return ((buffer_high_width_minus_one << 16) | buffer_low_width_minus_one) + 1; + return (buffer_high_width_minus_one << 16 | buffer_low_width_minus_one) + 1; } - u32 Height() const { + constexpr u32 Height() const { return height_minus_1 + 1; } - u32 Depth() const { + constexpr u32 Depth() const { return depth_minus_1 + 1; } - u32 BlockWidth() const { - ASSERT(IsTiled()); - return block_width; - } - - u32 BlockHeight() const { - ASSERT(IsTiled()); - return block_height; - } - - u32 BlockDepth() const { - ASSERT(IsTiled()); - return block_depth; + constexpr u32 BaseLayer() const { + return layer_base_0_2 | layer_base_3_7 << 3 | layer_base_8_10 << 8; } - bool IsTiled() const { + constexpr bool IsBlockLinear() const { return header_version == TICHeaderVersion::BlockLinear || header_version == TICHeaderVersion::BlockLinearColorKey; } - bool IsLineal() const { + constexpr bool IsPitchLinear() const { return header_version == TICHeaderVersion::Pitch || header_version == TICHeaderVersion::PitchColorKey; } - bool IsBuffer() const { + constexpr bool IsBuffer() const { return header_version == TICHeaderVersion::OneDBuffer; } - - bool IsSrgbConversionEnabled() const { - return srgb_conversion != 0; - } }; static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); @@ -309,6 +309,12 @@ enum class TextureMipmapFilter : u32 { Linear = 3, }; +enum class SamplerReduction : u32 { + WeightedAverage = 0, + Min = 1, + Max = 2, +}; + enum class Anisotropy { Default, Filter2x, @@ -333,8 +339,12 @@ struct TSCEntry { BitField<0, 2, TextureFilter> mag_filter; BitField<4, 2, TextureFilter> min_filter; BitField<6, 2, TextureMipmapFilter> mipmap_filter; + BitField<8, 1, u32> cubemap_anisotropy; BitField<9, 1, u32> cubemap_interface_filtering; + BitField<10, 2, SamplerReduction> reduction_filter; BitField<12, 13, u32> mip_lod_bias; + BitField<25, 1, u32> float_coord_normalization; + BitField<26, 5, u32> trilin_opt; }; union { BitField<0, 12, u32> min_lod_clamp; @@ -347,32 +357,45 @@ struct TSCEntry { }; std::array<f32, 4> border_color; }; - std::array<u8, 0x20> raw; + std::array<u64, 4> raw; }; - std::array<float, 4> GetBorderColor() const noexcept; + constexpr bool operator==(const TSCEntry& rhs) const noexcept { + return raw == rhs.raw; + } + + constexpr bool operator!=(const TSCEntry& rhs) const noexcept { + return raw != rhs.raw; + } + + std::array<float, 4> BorderColor() const noexcept; - float GetMaxAnisotropy() const noexcept; + float MaxAnisotropy() const noexcept; - float GetMinLod() const { + float MinLod() const { return static_cast<float>(min_lod_clamp) / 256.0f; } - float GetMaxLod() const { + float MaxLod() const { return static_cast<float>(max_lod_clamp) / 256.0f; } - float GetLodBias() const { + float LodBias() const { // Sign extend the 13-bit value. - constexpr u32 mask = 1U << (13 - 1); + static constexpr u32 mask = 1U << (13 - 1); return static_cast<float>(static_cast<s32>((mip_lod_bias ^ mask) - mask)) / 256.0f; } }; static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size"); -struct FullTextureInfo { - TICEntry tic; - TSCEntry tsc; +} // namespace Tegra::Texture + +template <> +struct std::hash<Tegra::Texture::TICEntry> { + size_t operator()(const Tegra::Texture::TICEntry& tic) const noexcept; }; -} // namespace Tegra::Texture +template <> +struct std::hash<Tegra::Texture::TSCEntry> { + size_t operator()(const Tegra::Texture::TSCEntry& tsc) const noexcept; +}; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 4e3a092c7..e1b38c6ac 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -7,13 +7,9 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" -#include "video_core/gpu_asynch.h" -#include "video_core/gpu_synch.h" #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/renderer_opengl.h" -#ifdef HAS_VULKAN #include "video_core/renderer_vulkan/renderer_vulkan.h" -#endif #include "video_core/video_core.h" namespace { @@ -21,15 +17,16 @@ namespace { std::unique_ptr<VideoCore::RendererBase> CreateRenderer( Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu, std::unique_ptr<Core::Frontend::GraphicsContext> context) { + auto& telemetry_session = system.TelemetrySession(); + auto& cpu_memory = system.Memory(); + switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: - return std::make_unique<OpenGL::RendererOpenGL>(system, emu_window, gpu, - std::move(context)); -#ifdef HAS_VULKAN + return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory, + gpu, std::move(context)); case Settings::RendererBackend::Vulkan: - return std::make_unique<Vulkan::RendererVulkan>(system, emu_window, gpu, - std::move(context)); -#endif + return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory, + gpu, std::move(context)); default: return nullptr; } @@ -40,23 +37,19 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer( namespace VideoCore { std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) { - std::unique_ptr<Tegra::GPU> gpu; - if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - gpu = std::make_unique<VideoCommon::GPUAsynch>(system); - } else { - gpu = std::make_unique<VideoCommon::GPUSynch>(system); - } - + const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue(); + const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); + auto gpu = std::make_unique<Tegra::GPU>(system, use_async, use_nvdec); auto context = emu_window.CreateSharedContext(); - const auto scope = context->Acquire(); - - auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context)); - if (!renderer->Init()) { + auto scope = context->Acquire(); + try { + auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context)); + gpu->BindRenderer(std::move(renderer)); + return gpu; + } catch (const std::runtime_error& exception) { + LOG_ERROR(HW_GPU, "Failed to initialize GPU: {}", exception.what()); return nullptr; } - - gpu->BindRenderer(std::move(renderer)); - return gpu; } u16 GetResolutionScaleFactor(const RendererBase& renderer) { diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp index 5b01020ec..7a9d00d4f 100644 --- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp @@ -12,40 +12,22 @@ #include <fmt/format.h> -#define VK_NO_PROTOTYPES -#include <vulkan/vulkan.h> - -#include <GFSDK_Aftermath.h> -#include <GFSDK_Aftermath_Defines.h> -#include <GFSDK_Aftermath_GpuCrashDump.h> -#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h> - #include "common/common_paths.h" #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/scope_exit.h" - -#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h" +#include "video_core/vulkan_common/nsight_aftermath_tracker.h" namespace Vulkan { static constexpr char AFTERMATH_LIB_NAME[] = "GFSDK_Aftermath_Lib.x64.dll"; -NsightAftermathTracker::NsightAftermathTracker() = default; - -NsightAftermathTracker::~NsightAftermathTracker() { - if (initialized) { - (void)GFSDK_Aftermath_DisableGpuCrashDumps(); - } -} - -bool NsightAftermathTracker::Initialize() { +NsightAftermathTracker::NsightAftermathTracker() { if (!dl.Open(AFTERMATH_LIB_NAME)) { LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath DLL"); - return false; + return; } - if (!dl.GetSymbol("GFSDK_Aftermath_DisableGpuCrashDumps", &GFSDK_Aftermath_DisableGpuCrashDumps) || !dl.GetSymbol("GFSDK_Aftermath_EnableGpuCrashDumps", @@ -62,29 +44,30 @@ bool NsightAftermathTracker::Initialize() { !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GetJSON", &GFSDK_Aftermath_GpuCrashDump_GetJSON)) { LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers"); - return false; + return; } - dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash"; - (void)Common::FS::DeleteDirRecursively(dump_dir); + void(Common::FS::DeleteDirRecursively(dump_dir)); if (!Common::FS::CreateDir(dump_dir)) { LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); - return false; + return; } - if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps( GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan, GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback, ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) { LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); - return false; + return; } - LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir); - initialized = true; - return true; +} + +NsightAftermathTracker::~NsightAftermathTracker() { + if (initialized) { + (void)GFSDK_Aftermath_DisableGpuCrashDumps(); + } } void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const { diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h b/src/video_core/vulkan_common/nsight_aftermath_tracker.h index afe7ae99e..1ce8d4e8e 100644 --- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.h @@ -8,8 +8,9 @@ #include <string> #include <vector> -#define VK_NO_PROTOTYPES -#include <vulkan/vulkan.h> +#include "common/common_types.h" +#include "common/dynamic_library.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" #ifdef HAS_NSIGHT_AFTERMATH #include <GFSDK_Aftermath_Defines.h> @@ -17,9 +18,6 @@ #include <GFSDK_Aftermath_GpuCrashDumpDecoding.h> #endif -#include "common/common_types.h" -#include "common/dynamic_library.h" - namespace Vulkan { class NsightAftermathTracker { @@ -34,8 +32,6 @@ public: NsightAftermathTracker(NsightAftermathTracker&&) = delete; NsightAftermathTracker& operator=(NsightAftermathTracker&&) = delete; - bool Initialize(); - void SaveShader(const std::vector<u32>& spirv) const; private: @@ -78,9 +74,6 @@ private: #ifndef HAS_NSIGHT_AFTERMATH inline NsightAftermathTracker::NsightAftermathTracker() = default; inline NsightAftermathTracker::~NsightAftermathTracker() = default; -inline bool NsightAftermathTracker::Initialize() { - return false; -} inline void NsightAftermathTracker::SaveShader(const std::vector<u32>&) const {} #endif diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp new file mode 100644 index 000000000..5c64c9bf7 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp @@ -0,0 +1,46 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <string_view> +#include "common/logging/log.h" +#include "video_core/vulkan_common/vulkan_debug_callback.h" + +namespace Vulkan { +namespace { +VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + [[maybe_unused]] void* user_data) { + const std::string_view message{data->pMessage}; + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + LOG_CRITICAL(Render_Vulkan, "{}", message); + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + LOG_WARNING(Render_Vulkan, "{}", message); + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + LOG_INFO(Render_Vulkan, "{}", message); + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + LOG_DEBUG(Render_Vulkan, "{}", message); + } + return VK_FALSE; +} +} // Anonymous namespace + +vk::DebugUtilsMessenger CreateDebugCallback(const vk::Instance& instance) { + return instance.CreateDebugUtilsMessenger(VkDebugUtilsMessengerCreateInfoEXT{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = Callback, + .pUserData = nullptr, + }); +} + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h new file mode 100644 index 000000000..b0519f132 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_debug_callback.h @@ -0,0 +1,13 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +vk::DebugUtilsMessenger CreateDebugCallback(const vk::Instance& instance); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 030b4dbd3..34d396434 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -13,39 +13,47 @@ #include "common/assert.h" #include "core/settings.h" -#include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/nsight_aftermath_tracker.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { - namespace { - namespace Alternatives { - -constexpr std::array Depth24UnormS8_UINT{ +constexpr std::array DEPTH24_UNORM_STENCIL8_UINT{ VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, - VkFormat{}, + VK_FORMAT_UNDEFINED, }; -constexpr std::array Depth16UnormS8_UINT{ +constexpr std::array DEPTH16_UNORM_STENCIL8_UINT{ VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, - VkFormat{}, + VK_FORMAT_UNDEFINED, }; - } // namespace Alternatives constexpr std::array REQUIRED_EXTENSIONS{ - VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_MAINTENANCE1_EXTENSION_NAME, + VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, + VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME, VK_KHR_16BIT_STORAGE_EXTENSION_NAME, VK_KHR_8BIT_STORAGE_EXTENSION_NAME, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME, + VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, + VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME, VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME, + VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, +#ifdef _WIN32 + VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, +#endif +#ifdef __linux__ + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, +#endif }; template <typename T> @@ -57,9 +65,9 @@ void SetNext(void**& next, T& data) { constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { switch (format) { case VK_FORMAT_D24_UNORM_S8_UINT: - return Alternatives::Depth24UnormS8_UINT.data(); + return Alternatives::DEPTH24_UNORM_STENCIL8_UINT.data(); case VK_FORMAT_D16_UNORM_S8_UINT: - return Alternatives::Depth16UnormS8_UINT.data(); + return Alternatives::DEPTH16_UNORM_STENCIL8_UINT.data(); default: return nullptr; } @@ -78,8 +86,7 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType } } -std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( - vk::PhysicalDevice physical, const vk::InstanceDispatch& dld) { +std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::PhysicalDevice physical) { static constexpr std::array formats{ VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_UINT_PACK32, @@ -103,6 +110,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_SFLOAT, + VK_FORMAT_R16G16_SINT, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UINT, VK_FORMAT_R8G8B8A8_SRGB, @@ -142,18 +150,32 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( 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_8x8_SRGB_BLOCK, - VK_FORMAT_ASTC_8x5_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_10x8_UNORM_BLOCK, - VK_FORMAT_ASTC_10x8_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, @@ -171,84 +193,87 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( } // Anonymous namespace -VKDevice::VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface, - const vk::InstanceDispatch& dld) - : dld{dld}, physical{physical}, properties{physical.GetProperties()}, - format_properties{GetFormatProperties(physical, dld)} { +Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, + const vk::InstanceDispatch& dld_) + : instance{instance_}, dld{dld_}, physical{physical_}, properties{physical.GetProperties()}, + format_properties{GetFormatProperties(physical)} { + CheckSuitability(surface != nullptr); SetupFamilies(surface); SetupFeatures(); -} - -VKDevice::~VKDevice() = default; -bool VKDevice::Create() { const auto queue_cis = GetDeviceQueueCreateInfos(); - const std::vector extensions = LoadExtensions(); + const std::vector extensions = LoadExtensions(surface != nullptr); VkPhysicalDeviceFeatures2 features2{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = nullptr, + .features{ + .robustBufferAccess = true, + .fullDrawIndexUint32 = false, + .imageCubeArray = true, + .independentBlend = true, + .geometryShader = true, + .tessellationShader = true, + .sampleRateShading = false, + .dualSrcBlend = false, + .logicOp = false, + .multiDrawIndirect = false, + .drawIndirectFirstInstance = false, + .depthClamp = true, + .depthBiasClamp = true, + .fillModeNonSolid = false, + .depthBounds = false, + .wideLines = false, + .largePoints = true, + .alphaToOne = false, + .multiViewport = true, + .samplerAnisotropy = true, + .textureCompressionETC2 = false, + .textureCompressionASTC_LDR = is_optimal_astc_supported, + .textureCompressionBC = false, + .occlusionQueryPrecise = true, + .pipelineStatisticsQuery = false, + .vertexPipelineStoresAndAtomics = true, + .fragmentStoresAndAtomics = true, + .shaderTessellationAndGeometryPointSize = false, + .shaderImageGatherExtended = true, + .shaderStorageImageExtendedFormats = false, + .shaderStorageImageMultisample = is_shader_storage_image_multisample, + .shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported, + .shaderStorageImageWriteWithoutFormat = true, + .shaderUniformBufferArrayDynamicIndexing = false, + .shaderSampledImageArrayDynamicIndexing = false, + .shaderStorageBufferArrayDynamicIndexing = false, + .shaderStorageImageArrayDynamicIndexing = false, + .shaderClipDistance = false, + .shaderCullDistance = false, + .shaderFloat64 = false, + .shaderInt64 = false, + .shaderInt16 = false, + .shaderResourceResidency = false, + .shaderResourceMinLod = false, + .sparseBinding = false, + .sparseResidencyBuffer = false, + .sparseResidencyImage2D = false, + .sparseResidencyImage3D = false, + .sparseResidency2Samples = false, + .sparseResidency4Samples = false, + .sparseResidency8Samples = false, + .sparseResidency16Samples = false, + .sparseResidencyAliased = false, + .variableMultisampleRate = false, + .inheritedQueries = false, + }, }; const void* first_next = &features2; void** next = &features2.pNext; - features2.features = { - .robustBufferAccess = false, - .fullDrawIndexUint32 = false, - .imageCubeArray = false, - .independentBlend = true, - .geometryShader = true, - .tessellationShader = true, - .sampleRateShading = false, - .dualSrcBlend = false, - .logicOp = false, - .multiDrawIndirect = false, - .drawIndirectFirstInstance = false, - .depthClamp = true, - .depthBiasClamp = true, - .fillModeNonSolid = false, - .depthBounds = false, - .wideLines = false, - .largePoints = true, - .alphaToOne = false, - .multiViewport = true, - .samplerAnisotropy = true, - .textureCompressionETC2 = false, - .textureCompressionASTC_LDR = is_optimal_astc_supported, - .textureCompressionBC = false, - .occlusionQueryPrecise = true, - .pipelineStatisticsQuery = false, - .vertexPipelineStoresAndAtomics = true, - .fragmentStoresAndAtomics = true, - .shaderTessellationAndGeometryPointSize = false, - .shaderImageGatherExtended = true, - .shaderStorageImageExtendedFormats = false, - .shaderStorageImageMultisample = false, - .shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported, - .shaderStorageImageWriteWithoutFormat = true, - .shaderUniformBufferArrayDynamicIndexing = false, - .shaderSampledImageArrayDynamicIndexing = false, - .shaderStorageBufferArrayDynamicIndexing = false, - .shaderStorageImageArrayDynamicIndexing = false, - .shaderClipDistance = false, - .shaderCullDistance = false, - .shaderFloat64 = false, - .shaderInt64 = false, - .shaderInt16 = false, - .shaderResourceResidency = false, - .shaderResourceMinLod = false, - .sparseBinding = false, - .sparseResidencyBuffer = false, - .sparseResidencyImage2D = false, - .sparseResidencyImage3D = false, - .sparseResidency2Samples = false, - .sparseResidency4Samples = false, - .sparseResidency8Samples = false, - .sparseResidency16Samples = false, - .sparseResidencyAliased = false, - .variableMultisampleRate = false, - .inheritedQueries = false, + VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline_semaphore{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR, + .pNext = nullptr, + .timelineSemaphore = true, }; + SetNext(next, timeline_semaphore); VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR, @@ -271,6 +296,7 @@ bool VKDevice::Create() { VkPhysicalDeviceHostQueryResetFeaturesEXT host_query_reset{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT, + .pNext = nullptr, .hostQueryReset = true, }; SetNext(next, host_query_reset); @@ -360,7 +386,7 @@ bool VKDevice::Create() { VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv; if (nv_device_diagnostics_config) { - nsight_aftermath_tracker.Initialize(); + nsight_aftermath_tracker = std::make_unique<NsightAftermathTracker>(); diagnostics_nv = { .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV, @@ -371,24 +397,33 @@ bool VKDevice::Create() { }; first_next = &diagnostics_nv; } - logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld); - if (!logical) { - LOG_ERROR(Render_Vulkan, "Failed to create logical device"); - return false; - } CollectTelemetryParameters(); + CollectToolingInfo(); + + if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_MESA_RADV) { + LOG_WARNING( + Render_Vulkan, + "Blacklisting RADV for VK_EXT_extended_dynamic state, likely due to a bug in yuzu"); + ext_extended_dynamic_state = false; + } + if (is_float16_supported && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { + // Intel's compiler crashes when using fp16 on Astral Chain, disable it for the time being. + LOG_WARNING(Render_Vulkan, "Blacklisting Intel proprietary from float16 math"); + is_float16_supported = false; + } graphics_queue = logical.GetQueue(graphics_family); present_queue = logical.GetQueue(present_family); use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue(); - return true; } -VkFormat VKDevice::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, - FormatType format_type) const { +Device::~Device() = default; + +VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, + FormatType format_type) const { if (IsFormatSupported(wanted_format, wanted_usage, format_type)) { return wanted_format; } @@ -419,18 +454,20 @@ VkFormat VKDevice::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFla return wanted_format; } -void VKDevice::ReportLoss() const { +void Device::ReportLoss() const { LOG_CRITICAL(Render_Vulkan, "Device loss occured!"); // Wait for the log to flush and for Nsight Aftermath to dump the results - std::this_thread::sleep_for(std::chrono::seconds{3}); + std::this_thread::sleep_for(std::chrono::seconds{15}); } -void VKDevice::SaveShader(const std::vector<u32>& spirv) const { - nsight_aftermath_tracker.SaveShader(spirv); +void Device::SaveShader(const std::vector<u32>& spirv) const { + if (nsight_aftermath_tracker) { + nsight_aftermath_tracker->SaveShader(spirv); + } } -bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const { +bool Device::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const { // Disable for now to avoid converting ASTC twice. static constexpr std::array astc_formats = { VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, @@ -456,16 +493,26 @@ bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; for (const auto format : astc_formats) { - const auto format_properties{physical.GetFormatProperties(format)}; - if (!(format_properties.optimalTilingFeatures & format_feature_usage)) { + const auto physical_format_properties{physical.GetFormatProperties(format)}; + if ((physical_format_properties.optimalTilingFeatures & format_feature_usage) == 0) { return false; } } return true; } -bool VKDevice::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, - FormatType format_type) const { +bool Device::TestDepthStencilBlits() const { + static constexpr VkFormatFeatureFlags required_features = + VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; + const auto test_features = [](VkFormatProperties props) { + return (props.optimalTilingFeatures & required_features) == required_features; + }; + return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) && + test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT)); +} + +bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, + FormatType format_type) const { const auto it = format_properties.find(wanted_format); if (it == format_properties.end()) { UNIMPLEMENTED_MSG("Unimplemented format query={}", wanted_format); @@ -475,65 +522,65 @@ bool VKDevice::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wa return (supported_usage & wanted_usage) == wanted_usage; } -bool VKDevice::IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface) { - bool is_suitable = true; +void Device::CheckSuitability(bool requires_swapchain) const { std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions; - - for (const auto& prop : physical.EnumerateDeviceExtensionProperties()) { - for (std::size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) { + bool has_swapchain = false; + for (const VkExtensionProperties& property : physical.EnumerateDeviceExtensionProperties()) { + const std::string_view name{property.extensionName}; + for (size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) { if (available_extensions[i]) { continue; } - const std::string_view name{prop.extensionName}; available_extensions[i] = name == REQUIRED_EXTENSIONS[i]; } + has_swapchain = has_swapchain || name == VK_KHR_SWAPCHAIN_EXTENSION_NAME; } - if (!available_extensions.all()) { - for (std::size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) { - if (available_extensions[i]) { - continue; - } - LOG_ERROR(Render_Vulkan, "Missing required extension: {}", REQUIRED_EXTENSIONS[i]); - is_suitable = false; - } - } - - bool has_graphics{}, has_present{}; - const std::vector queue_family_properties = physical.GetQueueFamilyProperties(); - for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) { - const auto& family = queue_family_properties[i]; - if (family.queueCount == 0) { + for (size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) { + if (available_extensions[i]) { continue; } - has_graphics |= family.queueFlags & VK_QUEUE_GRAPHICS_BIT; - has_present |= physical.GetSurfaceSupportKHR(i, surface); + LOG_ERROR(Render_Vulkan, "Missing required extension: {}", REQUIRED_EXTENSIONS[i]); + throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT); } - if (!has_graphics || !has_present) { - LOG_ERROR(Render_Vulkan, "Device lacks a graphics and present queue"); - is_suitable = false; + if (requires_swapchain && !has_swapchain) { + LOG_ERROR(Render_Vulkan, "Missing required extension: VK_KHR_swapchain"); + throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT); } - // TODO(Rodrigo): Check if the device matches all requeriments. - const auto properties{physical.GetProperties()}; - const auto& limits{properties.limits}; - - constexpr u32 required_ubo_size = 65536; - if (limits.maxUniformBufferRange < required_ubo_size) { - LOG_ERROR(Render_Vulkan, "Device UBO size {} is too small, {} is required", - limits.maxUniformBufferRange, required_ubo_size); - is_suitable = false; + struct LimitTuple { + u32 minimum; + u32 value; + const char* name; + }; + const VkPhysicalDeviceLimits& limits{properties.limits}; + const std::array limits_report{ + LimitTuple{65536, limits.maxUniformBufferRange, "maxUniformBufferRange"}, + LimitTuple{16, limits.maxViewports, "maxViewports"}, + LimitTuple{8, limits.maxColorAttachments, "maxColorAttachments"}, + LimitTuple{8, limits.maxClipDistances, "maxClipDistances"}, + }; + for (const auto& tuple : limits_report) { + if (tuple.value < tuple.minimum) { + LOG_ERROR(Render_Vulkan, "{} has to be {} or greater but it is {}", tuple.name, + tuple.minimum, tuple.value); + throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); + } } + VkPhysicalDeviceRobustness2FeaturesEXT robustness2{}; + robustness2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT; - constexpr u32 required_num_viewports = 16; - if (limits.maxViewports < required_num_viewports) { - LOG_INFO(Render_Vulkan, "Device number of viewports {} is too small, {} is required", - limits.maxViewports, required_num_viewports); - is_suitable = false; - } + VkPhysicalDeviceFeatures2 features2{}; + features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features2.pNext = &robustness2; + + physical.GetFeatures2KHR(features2); - const auto features{physical.GetFeatures()}; - const std::array feature_report = { + const VkPhysicalDeviceFeatures& features{features2.features}; + const std::array feature_report{ + std::make_pair(features.robustBufferAccess, "robustBufferAccess"), std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"), + std::make_pair(features.robustBufferAccess, "robustBufferAccess"), + std::make_pair(features.imageCubeArray, "imageCubeArray"), std::make_pair(features.independentBlend, "independentBlend"), std::make_pair(features.depthClamp, "depthClamp"), std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"), @@ -547,76 +594,70 @@ bool VKDevice::IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface) { std::make_pair(features.shaderImageGatherExtended, "shaderImageGatherExtended"), std::make_pair(features.shaderStorageImageWriteWithoutFormat, "shaderStorageImageWriteWithoutFormat"), + std::make_pair(robustness2.robustBufferAccess2, "robustBufferAccess2"), + std::make_pair(robustness2.robustImageAccess2, "robustImageAccess2"), + std::make_pair(robustness2.nullDescriptor, "nullDescriptor"), }; - for (const auto& [supported, name] : feature_report) { - if (supported) { + for (const auto& [is_supported, name] : feature_report) { + if (is_supported) { continue; } LOG_ERROR(Render_Vulkan, "Missing required feature: {}", name); - is_suitable = false; + throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); } - - if (!is_suitable) { - LOG_ERROR(Render_Vulkan, "{} is not suitable", properties.deviceName); - } - - return is_suitable; } -std::vector<const char*> VKDevice::LoadExtensions() { +std::vector<const char*> Device::LoadExtensions(bool requires_surface) { std::vector<const char*> extensions; - const auto Test = [&](const VkExtensionProperties& extension, - std::optional<std::reference_wrapper<bool>> status, const char* name, - bool push) { - if (extension.extensionName != std::string_view(name)) { - return; - } - if (push) { - extensions.push_back(name); - } - if (status) { - status->get() = true; - } - }; - - extensions.reserve(7 + REQUIRED_EXTENSIONS.size()); + extensions.reserve(8 + REQUIRED_EXTENSIONS.size()); extensions.insert(extensions.begin(), REQUIRED_EXTENSIONS.begin(), REQUIRED_EXTENSIONS.end()); + if (requires_surface) { + extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + } bool has_khr_shader_float16_int8{}; bool has_ext_subgroup_size_control{}; bool has_ext_transform_feedback{}; bool has_ext_custom_border_color{}; bool has_ext_extended_dynamic_state{}; - for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) { - Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true); - Test(extension, khr_uniform_buffer_standard_layout, + for (const VkExtensionProperties& extension : physical.EnumerateDeviceExtensionProperties()) { + const auto test = [&](std::optional<std::reference_wrapper<bool>> status, const char* name, + bool push) { + if (extension.extensionName != std::string_view(name)) { + return; + } + if (push) { + extensions.push_back(name); + } + if (status) { + status->get() = true; + } + }; + test(nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true); + test(khr_uniform_buffer_standard_layout, VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true); - Test(extension, has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, - false); - Test(extension, ext_depth_range_unrestricted, - VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true); - Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true); - Test(extension, ext_shader_viewport_index_layer, - VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true); - Test(extension, has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, - false); - Test(extension, has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, - false); - Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, - false); - Test(extension, has_ext_extended_dynamic_state, - VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false); + test(has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false); + test(ext_depth_range_unrestricted, VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true); + test(ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true); + test(ext_sampler_filter_minmax, VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME, true); + test(ext_shader_viewport_index_layer, VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, + true); + test(ext_tooling_info, VK_EXT_TOOLING_INFO_EXTENSION_NAME, true); + test(ext_shader_stencil_export, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, true); + test(has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, false); + test(has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, false); + test(has_ext_extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false); + test(has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false); if (Settings::values.renderer_debug) { - Test(extension, nv_device_diagnostics_config, - VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true); + test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, + true); } } - VkPhysicalDeviceFeatures2KHR features; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; - VkPhysicalDeviceProperties2KHR properties; - properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; + VkPhysicalDeviceProperties2KHR physical_properties; + physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; if (has_khr_shader_float16_int8) { VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8_features; @@ -628,7 +669,6 @@ std::vector<const char*> VKDevice::LoadExtensions() { is_float16_supported = float16_int8_features.shaderFloat16; extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); } - if (has_ext_subgroup_size_control) { VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroup_features; subgroup_features.sType = @@ -641,8 +681,8 @@ std::vector<const char*> VKDevice::LoadExtensions() { subgroup_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT; subgroup_properties.pNext = nullptr; - properties.pNext = &subgroup_properties; - physical.GetProperties2KHR(properties); + physical_properties.pNext = &subgroup_properties; + physical.GetProperties2KHR(physical_properties); is_warp_potentially_bigger = subgroup_properties.maxSubgroupSize > GuestWarpSize; @@ -655,7 +695,6 @@ std::vector<const char*> VKDevice::LoadExtensions() { } else { is_warp_potentially_bigger = true; } - if (has_ext_transform_feedback) { VkPhysicalDeviceTransformFeedbackFeaturesEXT tfb_features; tfb_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT; @@ -666,8 +705,8 @@ std::vector<const char*> VKDevice::LoadExtensions() { VkPhysicalDeviceTransformFeedbackPropertiesEXT tfb_properties; tfb_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT; tfb_properties.pNext = nullptr; - properties.pNext = &tfb_properties; - physical.GetProperties2KHR(properties); + physical_properties.pNext = &tfb_properties; + physical.GetProperties2KHR(physical_properties); if (tfb_features.transformFeedback && tfb_features.geometryStreams && tfb_properties.maxTransformFeedbackStreams >= 4 && @@ -677,7 +716,6 @@ std::vector<const char*> VKDevice::LoadExtensions() { ext_transform_feedback = true; } } - if (has_ext_custom_border_color) { VkPhysicalDeviceCustomBorderColorFeaturesEXT border_features; border_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; @@ -690,13 +728,7 @@ std::vector<const char*> VKDevice::LoadExtensions() { ext_custom_border_color = true; } } - - if (has_ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_AMD_PROPRIETARY) { - // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but the <stride> field - // seems to be bugged. Blacklisting it for now. - LOG_WARNING(Render_Vulkan, - "Blacklisting AMD proprietary from VK_EXT_extended_dynamic_state"); - } else if (has_ext_extended_dynamic_state) { + if (has_ext_extended_dynamic_state) { VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state; dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; dynamic_state.pNext = nullptr; @@ -708,52 +740,64 @@ std::vector<const char*> VKDevice::LoadExtensions() { ext_extended_dynamic_state = true; } } - return extensions; } -void VKDevice::SetupFamilies(VkSurfaceKHR surface) { - std::optional<u32> graphics_family_, present_family_; - +void Device::SetupFamilies(VkSurfaceKHR surface) { const std::vector queue_family_properties = physical.GetQueueFamilyProperties(); - for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) { - if (graphics_family_ && present_family_) + std::optional<u32> graphics; + std::optional<u32> present; + for (u32 index = 0; index < static_cast<u32>(queue_family_properties.size()); ++index) { + if (graphics && (present || !surface)) { break; - - const auto& queue_family = queue_family_properties[i]; - if (queue_family.queueCount == 0) + } + const VkQueueFamilyProperties& queue_family = queue_family_properties[index]; + if (queue_family.queueCount == 0) { continue; - + } if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - graphics_family_ = i; + graphics = index; } - if (physical.GetSurfaceSupportKHR(i, surface)) { - present_family_ = i; + if (surface && physical.GetSurfaceSupportKHR(index, surface)) { + present = index; } } - ASSERT(graphics_family_ && present_family_); - - graphics_family = *graphics_family_; - present_family = *present_family_; + if (!graphics) { + LOG_ERROR(Render_Vulkan, "Device lacks a graphics queue"); + throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); + } + if (surface && !present) { + LOG_ERROR(Render_Vulkan, "Device lacks a present queue"); + throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); + } + graphics_family = *graphics; + present_family = *present; } -void VKDevice::SetupFeatures() { +void Device::SetupFeatures() { const auto supported_features{physical.GetFeatures()}; is_formatless_image_load_supported = supported_features.shaderStorageImageReadWithoutFormat; + is_shader_storage_image_multisample = supported_features.shaderStorageImageMultisample; + is_blit_depth_stencil_supported = TestDepthStencilBlits(); is_optimal_astc_supported = IsOptimalAstcSupported(supported_features); } -void VKDevice::CollectTelemetryParameters() { +void Device::CollectTelemetryParameters() { VkPhysicalDeviceDriverPropertiesKHR driver{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR, .pNext = nullptr, + .driverID = {}, + .driverName = {}, + .driverInfo = {}, + .conformanceVersion = {}, }; - VkPhysicalDeviceProperties2KHR properties{ + VkPhysicalDeviceProperties2KHR device_properties{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR, .pNext = &driver, + .properties = {}, }; - physical.GetProperties2KHR(properties); + physical.GetProperties2KHR(device_properties); driver_id = driver.driverID; vendor_name = driver.driverName; @@ -765,7 +809,33 @@ void VKDevice::CollectTelemetryParameters() { } } -std::vector<VkDeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const { +void Device::CollectToolingInfo() { + if (!ext_tooling_info) { + return; + } + const auto vkGetPhysicalDeviceToolPropertiesEXT = + reinterpret_cast<PFN_vkGetPhysicalDeviceToolPropertiesEXT>( + dld.vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceToolPropertiesEXT")); + if (!vkGetPhysicalDeviceToolPropertiesEXT) { + return; + } + u32 tool_count = 0; + if (vkGetPhysicalDeviceToolPropertiesEXT(physical, &tool_count, nullptr) != VK_SUCCESS) { + return; + } + std::vector<VkPhysicalDeviceToolPropertiesEXT> tools(tool_count); + if (vkGetPhysicalDeviceToolPropertiesEXT(physical, &tool_count, tools.data()) != VK_SUCCESS) { + return; + } + for (const VkPhysicalDeviceToolPropertiesEXT& tool : tools) { + const std::string_view name = tool.name; + LOG_INFO(Render_Vulkan, "{}", name); + has_renderdoc = has_renderdoc || name == "RenderDoc"; + has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; + } +} + +std::vector<VkDeviceQueueCreateInfo> Device::GetDeviceQueueCreateInfos() const { static constexpr float QUEUE_PRIORITY = 1.0f; std::unordered_set<u32> unique_queue_families{graphics_family, present_family}; diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/vulkan_common/vulkan_device.h index 26a233db1..67d70cd22 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -10,11 +10,12 @@ #include <vector> #include "common/common_types.h" -#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { +class NsightAftermathTracker; + /// Format usage descriptor. enum class FormatType { Linear, Optimal, Buffer }; @@ -22,14 +23,11 @@ enum class FormatType { Linear, Optimal, Buffer }; const u32 GuestWarpSize = 32; /// Handles data specific to a physical device. -class VKDevice final { +class Device { public: - explicit VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface, - const vk::InstanceDispatch& dld); - ~VKDevice(); - - /// Initializes the device. Returns true on success. - bool Create(); + explicit Device(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface, + const vk::InstanceDispatch& dld); + ~Device(); /** * Returns a format supported by the device for the passed requeriments. @@ -83,7 +81,7 @@ public: } /// Returns the current Vulkan API version provided in Vulkan-formatted version numbers. - u32 GetApiVersion() const { + u32 ApiVersion() const { return properties.apiVersion; } @@ -152,6 +150,11 @@ public: return is_formatless_image_load_supported; } + /// Returns true when blitting from and to depth stencil images is supported. + bool IsBlitDepthStencilSupported() const { + return is_blit_depth_stencil_supported; + } + /// Returns true if the device supports VK_NV_viewport_swizzle. bool IsNvViewportSwizzleSupported() const { return nv_viewport_swizzle; @@ -167,6 +170,11 @@ public: return ext_index_type_uint8; } + /// Returns true if the device supports VK_EXT_sampler_filter_minmax. + bool IsExtSamplerFilterMinmaxSupported() const { + return ext_sampler_filter_minmax; + } + /// Returns true if the device supports VK_EXT_depth_range_unrestricted. bool IsExtDepthRangeUnrestrictedSupported() const { return ext_depth_range_unrestricted; @@ -192,6 +200,16 @@ public: return ext_extended_dynamic_state; } + /// Returns true if the device supports VK_EXT_shader_stencil_export. + bool IsExtShaderStencilExportSupported() const { + return ext_shader_stencil_export; + } + + /// Returns true when a known debugging tool is attached. + bool HasDebuggingToolAttached() const { + return has_renderdoc || has_nsight_graphics; + } + /// Returns the vendor name reported from Vulkan. std::string_view GetVendorName() const { return vendor_name; @@ -207,12 +225,12 @@ public: return use_asynchronous_shaders; } +private: /// Checks if the physical device is suitable. - static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface); + void CheckSuitability(bool requires_swapchain) const; -private: /// Loads extensions into a vector and stores available ones in this object. - std::vector<const char*> LoadExtensions(); + std::vector<const char*> LoadExtensions(bool requires_surface); /// Sets up queue families. void SetupFamilies(VkSurfaceKHR surface); @@ -223,22 +241,30 @@ private: /// Collects telemetry information from the device. void CollectTelemetryParameters(); + /// Collects information about attached tools. + void CollectToolingInfo(); + /// Returns a list of queue initialization descriptors. std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const; /// Returns true if ASTC textures are natively supported. bool IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const; + /// Returns true if the device natively supports blitting depth stencil images. + bool TestDepthStencilBlits() const; + /// Returns true if a format is supported. bool IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, FormatType format_type) const; + VkInstance instance; ///< Vulkan instance. vk::DeviceDispatch dld; ///< Device function pointers. vk::PhysicalDevice physical; ///< Physical device. VkPhysicalDeviceProperties properties; ///< Device properties. vk::Device logical; ///< Logical device. vk::Queue graphics_queue; ///< Main graphics queue. vk::Queue present_queue; ///< Main present queue. + u32 instance_version{}; ///< Vulkan onstance version. u32 graphics_family{}; ///< Main graphics queue family index. u32 present_family{}; ///< Main present queue family index. VkDriverIdKHR driver_id{}; ///< Driver ID. @@ -246,16 +272,23 @@ private: bool is_optimal_astc_supported{}; ///< Support for native ASTC. bool is_float16_supported{}; ///< Support for float16 arithmetics. bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest. - bool is_formatless_image_load_supported{}; ///< Support for shader image read without format. - bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle. - bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs. - bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. - bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted. - bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer. - bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback. - bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color. - bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. - bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. + bool is_formatless_image_load_supported{}; ///< Support for shader image read without format. + bool is_shader_storage_image_multisample{}; ///< Support for image operations on MSAA images. + bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil. + bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle. + bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs. + bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. + bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax. + bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted. + bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer. + bool ext_tooling_info{}; ///< Support for VK_EXT_tooling_info. + bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback. + bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color. + bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. + bool ext_shader_stencil_export{}; ///< Support for VK_EXT_shader_stencil_export. + bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. + bool has_renderdoc{}; ///< Has RenderDoc attached + bool has_nsight_graphics{}; ///< Has Nsight Graphics attached // Asynchronous Graphics Pipeline setting bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline @@ -268,7 +301,7 @@ private: std::unordered_map<VkFormat, VkFormatProperties> format_properties; /// Nsight Aftermath GPU crash tracker - NsightAftermathTracker nsight_aftermath_tracker; + std::unique_ptr<NsightAftermathTracker> nsight_aftermath_tracker; }; } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp new file mode 100644 index 000000000..bfd6e6add --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_instance.cpp @@ -0,0 +1,155 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <future> +#include <optional> +#include <span> +#include <utility> +#include <vector> + +#include "common/common_types.h" +#include "common/dynamic_library.h" +#include "common/logging/log.h" +#include "core/frontend/emu_window.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +// Include these late to avoid polluting previous headers +#ifdef _WIN32 +#include <windows.h> +// ensure include order +#include <vulkan/vulkan_win32.h> +#endif + +#if !defined(_WIN32) && !defined(__APPLE__) +#include <X11/Xlib.h> +#include <vulkan/vulkan_wayland.h> +#include <vulkan/vulkan_xlib.h> +#endif + +namespace Vulkan { +namespace { +[[nodiscard]] std::vector<const char*> RequiredExtensions( + Core::Frontend::WindowSystemType window_type, bool enable_debug_utils) { + std::vector<const char*> extensions; + extensions.reserve(6); + switch (window_type) { + case Core::Frontend::WindowSystemType::Headless: + break; +#ifdef _WIN32 + case Core::Frontend::WindowSystemType::Windows: + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + break; +#endif +#if !defined(_WIN32) && !defined(__APPLE__) + case Core::Frontend::WindowSystemType::X11: + extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + break; + case Core::Frontend::WindowSystemType::Wayland: + extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + break; +#endif + default: + LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); + break; + } + if (window_type != Core::Frontend::WindowSystemType::Headless) { + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + } + if (enable_debug_utils) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + return extensions; +} + +[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld, + std::span<const char* const> extensions) { + const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld); + if (!properties) { + LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); + return false; + } + for (const char* extension : extensions) { + const auto it = std::ranges::find_if(*properties, [extension](const auto& prop) { + return std::strcmp(extension, prop.extensionName) == 0; + }); + if (it == properties->end()) { + LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension); + return false; + } + } + return true; +} + +[[nodiscard]] std::vector<const char*> Layers(bool enable_layers) { + std::vector<const char*> layers; + if (enable_layers) { + layers.push_back("VK_LAYER_KHRONOS_validation"); + } + return layers; +} + +void RemoveUnavailableLayers(const vk::InstanceDispatch& dld, std::vector<const char*>& layers) { + const std::optional layer_properties = vk::EnumerateInstanceLayerProperties(dld); + if (!layer_properties) { + LOG_ERROR(Render_Vulkan, "Failed to query layer properties, disabling layers"); + layers.clear(); + } + std::erase_if(layers, [&layer_properties](const char* layer) { + const auto comp = [layer](const VkLayerProperties& layer_property) { + return std::strcmp(layer, layer_property.layerName) == 0; + }; + const auto it = std::ranges::find_if(*layer_properties, comp); + if (it == layer_properties->end()) { + LOG_ERROR(Render_Vulkan, "Layer {} not available, removing it", layer); + return true; + } + return false; + }); +} +} // Anonymous namespace + +vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceDispatch& dld, + u32 required_version, Core::Frontend::WindowSystemType window_type, + bool enable_debug_utils, bool enable_layers) { + if (!library.IsOpen()) { + LOG_ERROR(Render_Vulkan, "Vulkan library not available"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + if (!library.GetSymbol("vkGetInstanceProcAddr", &dld.vkGetInstanceProcAddr)) { + LOG_ERROR(Render_Vulkan, "vkGetInstanceProcAddr not present in Vulkan"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + if (!vk::Load(dld)) { + LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + const std::vector<const char*> extensions = RequiredExtensions(window_type, enable_debug_utils); + if (!AreExtensionsSupported(dld, extensions)) { + throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT); + } + std::vector<const char*> layers = Layers(enable_layers); + RemoveUnavailableLayers(dld, layers); + + const u32 available_version = vk::AvailableVersion(dld); + if (available_version < required_version) { + LOG_ERROR(Render_Vulkan, "Vulkan {}.{} is not supported, {}.{} is required", + VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version), + VK_VERSION_MAJOR(required_version), VK_VERSION_MINOR(required_version)); + throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER); + } + vk::Instance instance = + std::async([&] { + return vk::Instance::Create(required_version, layers, extensions, dld); + }).get(); + if (!vk::Load(*instance, dld)) { + LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + return instance; +} + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_instance.h b/src/video_core/vulkan_common/vulkan_instance.h new file mode 100644 index 000000000..e5e3a7144 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_instance.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/dynamic_library.h" +#include "core/frontend/emu_window.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +/** + * Create a Vulkan instance + * + * @param library Dynamic library to load the Vulkan instance from + * @param dld Dispatch table to load function pointers into + * @param required_version Required Vulkan version (for example, VK_API_VERSION_1_1) + * @param window_type Window system type's enabled extension + * @param enable_debug_utils Whether to enable VK_EXT_debug_utils_extension_name or not + * @param enable_layers Whether to enable Vulkan validation layers or not + * + * @return A new Vulkan instance + * @throw vk::Exception on failure + */ +[[nodiscard]] vk::Instance CreateInstance( + const Common::DynamicLibrary& library, vk::InstanceDispatch& dld, u32 required_version, + Core::Frontend::WindowSystemType window_type = Core::Frontend::WindowSystemType::Headless, + bool enable_debug_utils = false, bool enable_layers = false); + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp new file mode 100644 index 000000000..557871d81 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_library.cpp @@ -0,0 +1,36 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstdlib> +#include <string> + +#include "common/dynamic_library.h" +#include "common/file_util.h" +#include "video_core/vulkan_common/vulkan_library.h" + +namespace Vulkan { + +Common::DynamicLibrary OpenLibrary() { + Common::DynamicLibrary library; +#ifdef __APPLE__ + // Check if a path to a specific Vulkan library has been specified. + char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); + if (!libvulkan_env || !library.Open(libvulkan_env)) { + // Use the libvulkan.dylib from the application bundle. + const std::string filename = + Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; + void(library.Open(filename.c_str())); + } +#else + std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); + if (!library.Open(filename.c_str())) { + // Android devices may not have libvulkan.so.1, only libvulkan.so. + filename = Common::DynamicLibrary::GetVersionedFilename("vulkan"); + void(library.Open(filename.c_str())); + } +#endif + return library; +} + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_library.h b/src/video_core/vulkan_common/vulkan_library.h new file mode 100644 index 000000000..8b28b0e17 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_library.h @@ -0,0 +1,13 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/dynamic_library.h" + +namespace Vulkan { + +Common::DynamicLibrary OpenLibrary(); + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp new file mode 100644 index 000000000..2a8b7a907 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -0,0 +1,326 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <bit> +#include <optional> +#include <vector> + +#include <glad/glad.h> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { +namespace { +struct Range { + u64 begin; + u64 end; + + [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept { + return iterator < end && begin < iterator + size; + } +}; + +[[nodiscard]] u64 AllocationChunkSize(u64 required_size) { + static constexpr std::array sizes{ + 0x1000ULL << 10, 0x1400ULL << 10, 0x1800ULL << 10, 0x1c00ULL << 10, 0x2000ULL << 10, + 0x3200ULL << 10, 0x4000ULL << 10, 0x6000ULL << 10, 0x8000ULL << 10, 0xA000ULL << 10, + 0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10, + }; + static_assert(std::is_sorted(sizes.begin(), sizes.end())); + + const auto it = std::ranges::lower_bound(sizes, required_size); + return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20); +} + +[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + case MemoryUsage::Upload: + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + case MemoryUsage::Download: + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + } + UNREACHABLE_MSG("Invalid memory usage={}", usage); + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +} +} // Anonymous namespace + +class MemoryAllocation { +public: + explicit MemoryAllocation(vk::DeviceMemory memory_, VkMemoryPropertyFlags properties, + u64 allocation_size_, u32 type) + : memory{std::move(memory_)}, allocation_size{allocation_size_}, property_flags{properties}, + shifted_memory_type{1U << type} {} + +#if defined(_WIN32) || defined(__linux__) + ~MemoryAllocation() { + if (owning_opengl_handle != 0) { + glDeleteMemoryObjectsEXT(1, &owning_opengl_handle); + } + } +#endif + + MemoryAllocation& operator=(const MemoryAllocation&) = delete; + MemoryAllocation(const MemoryAllocation&) = delete; + + MemoryAllocation& operator=(MemoryAllocation&&) = delete; + MemoryAllocation(MemoryAllocation&&) = delete; + + [[nodiscard]] std::optional<MemoryCommit> Commit(VkDeviceSize size, VkDeviceSize alignment) { + const std::optional<u64> alloc = FindFreeRegion(size, alignment); + if (!alloc) { + // Signal out of memory, it'll try to do more allocations. + return std::nullopt; + } + const Range range{ + .begin = *alloc, + .end = *alloc + size, + }; + commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range); + return std::make_optional<MemoryCommit>(this, *memory, *alloc, *alloc + size); + } + + void Free(u64 begin) { + const auto it = std::ranges::find(commits, begin, &Range::begin); + ASSERT_MSG(it != commits.end(), "Invalid commit"); + commits.erase(it); + } + + [[nodiscard]] std::span<u8> Map() { + if (memory_mapped_span.empty()) { + u8* const raw_pointer = memory.Map(0, allocation_size); + memory_mapped_span = std::span<u8>(raw_pointer, allocation_size); + } + return memory_mapped_span; + } + +#ifdef _WIN32 + [[nodiscard]] u32 ExportOpenGLHandle() { + if (!owning_opengl_handle) { + glCreateMemoryObjectsEXT(1, &owning_opengl_handle); + glImportMemoryWin32HandleEXT(owning_opengl_handle, allocation_size, + GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, + memory.GetMemoryWin32HandleKHR()); + } + return owning_opengl_handle; + } +#elif __linux__ + [[nodiscard]] u32 ExportOpenGLHandle() { + if (!owning_opengl_handle) { + glCreateMemoryObjectsEXT(1, &owning_opengl_handle); + glImportMemoryFdEXT(owning_opengl_handle, allocation_size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, + memory.GetMemoryFdKHR()); + } + return owning_opengl_handle; + } +#else + [[nodiscard]] u32 ExportOpenGLHandle() { + return 0; + } +#endif + + /// Returns whether this allocation is compatible with the arguments. + [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const { + return (flags & property_flags) && (type_mask & shifted_memory_type) != 0; + } + +private: + [[nodiscard]] static constexpr u32 ShiftType(u32 type) { + return 1U << type; + } + + [[nodiscard]] std::optional<u64> FindFreeRegion(u64 size, u64 alignment) noexcept { + ASSERT(std::has_single_bit(alignment)); + const u64 alignment_log2 = std::countr_zero(alignment); + std::optional<u64> candidate; + u64 iterator = 0; + auto commit = commits.begin(); + while (iterator + size <= allocation_size) { + candidate = candidate.value_or(iterator); + if (commit == commits.end()) { + break; + } + if (commit->Contains(*candidate, size)) { + candidate = std::nullopt; + } + iterator = Common::AlignUpLog2(commit->end, alignment_log2); + ++commit; + } + return candidate; + } + + const vk::DeviceMemory memory; ///< Vulkan memory allocation handler. + const u64 allocation_size; ///< Size of this allocation. + const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags. + const u32 shifted_memory_type; ///< Shifted Vulkan memory type. + std::vector<Range> commits; ///< All commit ranges done from this allocation. + std::span<u8> memory_mapped_span; ///< Memory mapped span. Empty if not queried before. +#if defined(_WIN32) || defined(__linux__) + u32 owning_opengl_handle{}; ///< Owning OpenGL memory object handle. +#endif +}; + +MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_, + u64 end_) noexcept + : allocation{allocation_}, memory{memory_}, begin{begin_}, end{end_} {} + +MemoryCommit::~MemoryCommit() { + Release(); +} + +MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept { + Release(); + allocation = std::exchange(rhs.allocation, nullptr); + memory = rhs.memory; + begin = rhs.begin; + end = rhs.end; + span = std::exchange(rhs.span, std::span<u8>{}); + return *this; +} + +MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept + : allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory}, begin{rhs.begin}, + end{rhs.end}, span{std::exchange(rhs.span, std::span<u8>{})} {} + +std::span<u8> MemoryCommit::Map() { + if (span.empty()) { + span = allocation->Map().subspan(begin, end - begin); + } + return span; +} + +u32 MemoryCommit::ExportOpenGLHandle() const { + return allocation->ExportOpenGLHandle(); +} + +void MemoryCommit::Release() { + if (allocation) { + allocation->Free(begin); + } +} + +MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_) + : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()}, + export_allocations{export_allocations_} {} + +MemoryAllocator::~MemoryAllocator() = default; + +MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) { + // Find the fastest memory flags we can afford with the current requirements + const VkMemoryPropertyFlags flags = MemoryPropertyFlags(requirements.memoryTypeBits, usage); + if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) { + return std::move(*commit); + } + // Commit has failed, allocate more memory. + // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory. + AllocMemory(flags, requirements.memoryTypeBits, AllocationChunkSize(requirements.size)); + + // Commit again, this time it won't fail since there's a fresh allocation above. + // If it does, there's a bug. + return TryCommit(requirements, flags).value(); +} + +MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage) { + auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), usage); + buffer.BindMemory(commit.Memory(), commit.Offset()); + return commit; +} + +MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) { + auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), usage); + image.BindMemory(commit.Memory(), commit.Offset()); + return commit; +} + +void MemoryAllocator::AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) { + const u32 type = FindType(flags, type_mask).value(); + const VkExportMemoryAllocateInfo export_allocate_info{ + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = nullptr, +#ifdef _WIN32 + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT, +#elif __linux__ + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, +#else + .handleTypes = 0, +#endif + }; + vk::DeviceMemory memory = device.GetLogical().AllocateMemory({ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = export_allocations ? &export_allocate_info : nullptr, + .allocationSize = size, + .memoryTypeIndex = type, + }); + allocations.push_back(std::make_unique<MemoryAllocation>(std::move(memory), flags, size, type)); +} + +std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements, + VkMemoryPropertyFlags flags) { + for (auto& allocation : allocations) { + if (!allocation->IsCompatible(flags, requirements.memoryTypeBits)) { + continue; + } + if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) { + return commit; + } + } + return std::nullopt; +} + +VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const { + return MemoryPropertyFlags(type_mask, MemoryUsagePropertyFlags(usage)); +} + +VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, + VkMemoryPropertyFlags flags) const { + if (FindType(flags, type_mask)) { + // Found a memory type with those requirements + return flags; + } + if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) { + // Remove host cached bit in case it's not supported + return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + } + if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { + // Remove device local, if it's not supported by the requested resource + return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + } + UNREACHABLE_MSG("No compatible memory types found"); + return 0; +} + +std::optional<u32> MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 type_mask) const { + for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) { + const VkMemoryPropertyFlags type_flags = properties.memoryTypes[type_index].propertyFlags; + if ((type_mask & (1U << type_index)) && (type_flags & flags)) { + // The type matches in type and in the wanted properties. + return type_index; + } + } + // Failed to find index + return std::nullopt; +} + +bool IsHostVisible(MemoryUsage usage) noexcept { + switch (usage) { + case MemoryUsage::DeviceLocal: + return false; + case MemoryUsage::Upload: + case MemoryUsage::Download: + return true; + } + UNREACHABLE_MSG("Invalid memory usage={}", usage); + return false; +} + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h new file mode 100644 index 000000000..d1ce29450 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -0,0 +1,129 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <span> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class MemoryMap; +class MemoryAllocation; + +/// Hints and requirements for the backing memory type of a commit +enum class MemoryUsage { + DeviceLocal, ///< Hints device local usages, fastest memory type to read and write from the GPU + Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads + Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks +}; + +/// Ownership handle of a memory commitment. +/// Points to a subregion of a memory allocation. +class MemoryCommit { +public: + explicit MemoryCommit() noexcept = default; + explicit MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_, + u64 end_) noexcept; + ~MemoryCommit(); + + MemoryCommit& operator=(MemoryCommit&&) noexcept; + MemoryCommit(MemoryCommit&&) noexcept; + + MemoryCommit& operator=(const MemoryCommit&) = delete; + MemoryCommit(const MemoryCommit&) = delete; + + /// Returns a host visible memory map. + /// It will map the backing allocation if it hasn't been mapped before. + std::span<u8> Map(); + + /// Returns an non-owning OpenGL handle, creating one if it doesn't exist. + u32 ExportOpenGLHandle() const; + + /// Returns the Vulkan memory handler. + VkDeviceMemory Memory() const { + return memory; + } + + /// Returns the start position of the commit relative to the allocation. + VkDeviceSize Offset() const { + return static_cast<VkDeviceSize>(begin); + } + +private: + void Release(); + + MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation. + VkDeviceMemory memory{}; ///< Vulkan device memory handler. + u64 begin{}; ///< Beginning offset in bytes to where the commit exists. + u64 end{}; ///< Offset in bytes where the commit ends. + std::span<u8> span; ///< Host visible memory span. Empty if not queried before. +}; + +/// Memory allocator container. +/// Allocates and releases memory allocations on demand. +class MemoryAllocator { +public: + /** + * Construct memory allocator + * + * @param device_ Device to allocate from + * @param export_allocations_ True when allocations have to be exported + * + * @throw vk::Exception on failure + */ + explicit MemoryAllocator(const Device& device_, bool export_allocations_); + ~MemoryAllocator(); + + MemoryAllocator& operator=(const MemoryAllocator&) = delete; + MemoryAllocator(const MemoryAllocator&) = delete; + + /** + * Commits a memory with the specified requirements. + * + * @param requirements Requirements returned from a Vulkan call. + * @param usage Indicates how the memory will be used. + * + * @returns A memory commit. + */ + MemoryCommit Commit(const VkMemoryRequirements& requirements, MemoryUsage usage); + + /// Commits memory required by the buffer and binds it. + MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage); + + /// Commits memory required by the image and binds it. + MemoryCommit Commit(const vk::Image& image, MemoryUsage usage); + +private: + /// Allocates a chunk of memory. + void AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size); + + /// Tries to allocate a memory commit. + std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements, + VkMemoryPropertyFlags flags); + + /// Returns the fastest compatible memory property flags from a wanted usage. + VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const; + + /// Returns the fastest compatible memory property flags from the wanted flags. + VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, VkMemoryPropertyFlags flags) const; + + /// Returns index to the fastest memory type compatible with the passed requirements. + std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const; + + const Device& device; ///< Device handle. + const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties. + const bool export_allocations; ///< True when memory allocations have to be exported. + std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. +}; + +/// Returns true when a memory usage is guaranteed to be host visible. +bool IsHostVisible(MemoryUsage usage) noexcept; + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp new file mode 100644 index 000000000..3c3238f96 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -0,0 +1,81 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/frontend/emu_window.h" +#include "video_core/vulkan_common/vulkan_surface.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +// Include these late to avoid polluting previous headers +#ifdef _WIN32 +#include <windows.h> +// ensure include order +#include <vulkan/vulkan_win32.h> +#endif + +#if !defined(_WIN32) && !defined(__APPLE__) +#include <X11/Xlib.h> +#include <vulkan/vulkan_wayland.h> +#include <vulkan/vulkan_xlib.h> +#endif + +namespace Vulkan { + +vk::SurfaceKHR CreateSurface(const vk::Instance& instance, + const Core::Frontend::EmuWindow& emu_window) { + [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch(); + [[maybe_unused]] const auto& window_info = emu_window.GetWindowInfo(); + VkSurfaceKHR unsafe_surface = nullptr; + +#ifdef _WIN32 + if (window_info.type == Core::Frontend::WindowSystemType::Windows) { + const HWND hWnd = static_cast<HWND>(window_info.render_surface); + const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + nullptr, 0, nullptr, hWnd}; + const auto vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>( + dld.vkGetInstanceProcAddr(*instance, "vkCreateWin32SurfaceKHR")); + if (!vkCreateWin32SurfaceKHR || + vkCreateWin32SurfaceKHR(*instance, &win32_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + } +#endif +#if !defined(_WIN32) && !defined(__APPLE__) + if (window_info.type == Core::Frontend::WindowSystemType::X11) { + const VkXlibSurfaceCreateInfoKHR xlib_ci{ + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0, + static_cast<Display*>(window_info.display_connection), + reinterpret_cast<Window>(window_info.render_surface)}; + const auto vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>( + dld.vkGetInstanceProcAddr(*instance, "vkCreateXlibSurfaceKHR")); + if (!vkCreateXlibSurfaceKHR || + vkCreateXlibSurfaceKHR(*instance, &xlib_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + } + if (window_info.type == Core::Frontend::WindowSystemType::Wayland) { + const VkWaylandSurfaceCreateInfoKHR wayland_ci{ + VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, + static_cast<wl_display*>(window_info.display_connection), + static_cast<wl_surface*>(window_info.render_surface)}; + const auto vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>( + dld.vkGetInstanceProcAddr(*instance, "vkCreateWaylandSurfaceKHR")); + if (!vkCreateWaylandSurfaceKHR || + vkCreateWaylandSurfaceKHR(*instance, &wayland_ci, nullptr, &unsafe_surface) != + VK_SUCCESS) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + } +#endif + if (!unsafe_surface) { + LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); + } + return vk::SurfaceKHR(unsafe_surface, *instance, dld); +} + +} // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_surface.h b/src/video_core/vulkan_common/vulkan_surface.h new file mode 100644 index 000000000..05a169e32 --- /dev/null +++ b/src/video_core/vulkan_common/vulkan_surface.h @@ -0,0 +1,18 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Core::Frontend { +class EmuWindow; +} + +namespace Vulkan { + +[[nodiscard]] vk::SurfaceKHR CreateSurface(const vk::Instance& instance, + const Core::Frontend::EmuWindow& emu_window); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 013865aa4..2aa0ffbe6 100644 --- a/src/video_core/renderer_vulkan/wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -6,32 +6,55 @@ #include <exception> #include <memory> #include <optional> +#include <string_view> #include <utility> #include <vector> #include "common/common_types.h" +#include "common/logging/log.h" -#include "video_core/renderer_vulkan/wrapper.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan::vk { namespace { +template <typename Func> +void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld, + Func&& func) { + // Calling GetProperties calls Vulkan more than needed. But they are supposed to be cheap + // functions. + std::stable_sort(devices.begin(), devices.end(), + [&dld, &func](VkPhysicalDevice lhs, VkPhysicalDevice rhs) { + return func(vk::PhysicalDevice(lhs, dld).GetProperties(), + vk::PhysicalDevice(rhs, dld).GetProperties()); + }); +} + +void SortPhysicalDevicesPerVendor(std::vector<VkPhysicalDevice>& devices, + const InstanceDispatch& dld, + std::initializer_list<u32> vendor_ids) { + for (auto it = vendor_ids.end(); it != vendor_ids.begin();) { + --it; + SortPhysicalDevices(devices, dld, [id = *it](const auto& lhs, const auto& rhs) { + return lhs.vendorID == id && rhs.vendorID != id; + }); + } +} + void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld) { - std::stable_sort(devices.begin(), devices.end(), [&](auto lhs, auto rhs) { - // This will call Vulkan more than needed, but these calls are cheap. - const auto lhs_properties = vk::PhysicalDevice(lhs, dld).GetProperties(); - const auto rhs_properties = vk::PhysicalDevice(rhs, dld).GetProperties(); - - // Prefer discrete GPUs, Nvidia over AMD, AMD over Intel, Intel over the rest. - const bool preferred = - (lhs_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && - rhs_properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) || - (lhs_properties.vendorID == 0x10DE && rhs_properties.vendorID != 0x10DE) || - (lhs_properties.vendorID == 0x1002 && rhs_properties.vendorID != 0x1002) || - (lhs_properties.vendorID == 0x8086 && rhs_properties.vendorID != 0x8086); - return !preferred; + // Sort by name, this will set a base and make GPUs with higher numbers appear first + // (e.g. GTX 1650 will intentionally be listed before a GTX 1080). + SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) { + return std::string_view{lhs.deviceName} > std::string_view{rhs.deviceName}; }); + // Prefer discrete over non-discrete + SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) { + return lhs.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + rhs.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + }); + // Prefer Nvidia over AMD, AMD over Intel, Intel over the rest. + SortPhysicalDevicesPerVendor(devices, dld, {0x10DE, 0x1002, 0x8086}); } template <typename T> @@ -58,6 +81,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdBeginQuery); X(vkCmdBeginRenderPass); X(vkCmdBeginTransformFeedbackEXT); + X(vkCmdBeginDebugUtilsLabelEXT); X(vkCmdBindDescriptorSets); X(vkCmdBindIndexBuffer); X(vkCmdBindPipeline); @@ -75,6 +99,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdEndQuery); X(vkCmdEndRenderPass); X(vkCmdEndTransformFeedbackEXT); + X(vkCmdEndDebugUtilsLabelEXT); X(vkCmdFillBuffer); X(vkCmdPipelineBarrier); X(vkCmdPushConstants); @@ -98,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetPrimitiveTopologyEXT); X(vkCmdSetStencilOpEXT); X(vkCmdSetStencilTestEnableEXT); + X(vkCmdResolveImage); X(vkCreateBuffer); X(vkCreateBufferView); X(vkCreateCommandPool); @@ -142,23 +168,44 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkFreeCommandBuffers); X(vkFreeDescriptorSets); X(vkFreeMemory); - X(vkGetBufferMemoryRequirements); + X(vkGetBufferMemoryRequirements2); X(vkGetDeviceQueue); X(vkGetEventStatus); X(vkGetFenceStatus); X(vkGetImageMemoryRequirements); + X(vkGetMemoryFdKHR); +#ifdef _WIN32 + X(vkGetMemoryWin32HandleKHR); +#endif X(vkGetQueryPoolResults); + X(vkGetSemaphoreCounterValueKHR); X(vkMapMemory); X(vkQueueSubmit); X(vkResetFences); X(vkResetQueryPoolEXT); + X(vkSetDebugUtilsObjectNameEXT); + X(vkSetDebugUtilsObjectTagEXT); X(vkUnmapMemory); X(vkUpdateDescriptorSetWithTemplateKHR); X(vkUpdateDescriptorSets); X(vkWaitForFences); + X(vkWaitSemaphoresKHR); #undef X } +template <typename T> +void SetObjectName(const DeviceDispatch* dld, VkDevice device, T handle, VkObjectType type, + const char* name) { + const VkDebugUtilsObjectNameInfoEXT name_info{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = nullptr, + .objectType = VK_OBJECT_TYPE_IMAGE, + .objectHandle = reinterpret_cast<u64>(handle), + .pObjectName = name, + }; + Check(dld->vkSetDebugUtilsObjectNameEXT(device, &name_info)); +} + } // Anonymous namespace bool Load(InstanceDispatch& dld) noexcept { @@ -262,6 +309,22 @@ const char* ToString(VkResult result) noexcept { return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT"; case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; + case VkResult::VK_ERROR_UNKNOWN: + return "VK_ERROR_UNKNOWN"; + case VkResult::VK_ERROR_INCOMPATIBLE_VERSION_KHR: + return "VK_ERROR_INCOMPATIBLE_VERSION_KHR"; + case VkResult::VK_THREAD_IDLE_KHR: + return "VK_THREAD_IDLE_KHR"; + case VkResult::VK_THREAD_DONE_KHR: + return "VK_THREAD_DONE_KHR"; + case VkResult::VK_OPERATION_DEFERRED_KHR: + return "VK_OPERATION_DEFERRED_KHR"; + case VkResult::VK_OPERATION_NOT_DEFERRED_KHR: + return "VK_OPERATION_NOT_DEFERRED_KHR"; + case VkResult::VK_PIPELINE_COMPILE_REQUIRED_EXT: + return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; + case VkResult::VK_RESULT_MAX_ENUM: + return "VK_RESULT_MAX_ENUM"; } return "Unknown"; } @@ -375,18 +438,17 @@ VkResult Free(VkDevice device, VkCommandPool handle, Span<VkCommandBuffer> buffe return VK_SUCCESS; } -Instance Instance::Create(Span<const char*> layers, Span<const char*> extensions, - InstanceDispatch& dld) noexcept { - static constexpr VkApplicationInfo application_info{ +Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char*> extensions, + InstanceDispatch& dispatch) { + const VkApplicationInfo application_info{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "yuzu Emulator", .applicationVersion = VK_MAKE_VERSION(0, 1, 0), .pEngineName = "yuzu Emulator", .engineVersion = VK_MAKE_VERSION(0, 1, 0), - .apiVersion = VK_API_VERSION_1_1, + .apiVersion = version, }; - const VkInstanceCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, @@ -397,66 +459,94 @@ Instance Instance::Create(Span<const char*> layers, Span<const char*> extensions .enabledExtensionCount = extensions.size(), .ppEnabledExtensionNames = extensions.data(), }; - VkInstance instance; - if (dld.vkCreateInstance(&ci, nullptr, &instance) != VK_SUCCESS) { - // Failed to create the instance. - return {}; - } - if (!Proc(dld.vkDestroyInstance, dld, "vkDestroyInstance", instance)) { + Check(dispatch.vkCreateInstance(&ci, nullptr, &instance)); + if (!Proc(dispatch.vkDestroyInstance, dispatch, "vkDestroyInstance", instance)) { // We successfully created an instance but the destroy function couldn't be loaded. // This is a good moment to panic. - return {}; + throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); } - - return Instance(instance, dld); + return Instance(instance, dispatch); } -std::optional<std::vector<VkPhysicalDevice>> Instance::EnumeratePhysicalDevices() { +std::vector<VkPhysicalDevice> Instance::EnumeratePhysicalDevices() const { u32 num; - if (dld->vkEnumeratePhysicalDevices(handle, &num, nullptr) != VK_SUCCESS) { - return std::nullopt; - } + Check(dld->vkEnumeratePhysicalDevices(handle, &num, nullptr)); std::vector<VkPhysicalDevice> physical_devices(num); - if (dld->vkEnumeratePhysicalDevices(handle, &num, physical_devices.data()) != VK_SUCCESS) { - return std::nullopt; - } + Check(dld->vkEnumeratePhysicalDevices(handle, &num, physical_devices.data())); SortPhysicalDevices(physical_devices, *dld); - return std::make_optional(std::move(physical_devices)); + return physical_devices; } -DebugCallback Instance::TryCreateDebugCallback( - PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept { - const VkDebugUtilsMessengerCreateInfoEXT ci{ - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .pNext = nullptr, - .flags = 0, - .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, - .pfnUserCallback = callback, - .pUserData = nullptr, - }; - - VkDebugUtilsMessengerEXT messenger; - if (dld->vkCreateDebugUtilsMessengerEXT(handle, &ci, nullptr, &messenger) != VK_SUCCESS) { - return {}; - } - return DebugCallback(messenger, handle, *dld); +DebugUtilsMessenger Instance::CreateDebugUtilsMessenger( + const VkDebugUtilsMessengerCreateInfoEXT& create_info) const { + VkDebugUtilsMessengerEXT object; + Check(dld->vkCreateDebugUtilsMessengerEXT(handle, &create_info, nullptr, &object)); + return DebugUtilsMessenger(object, handle, *dld); } void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const { Check(dld->vkBindBufferMemory(owner, handle, memory, offset)); } +void Buffer::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER, name); +} + +void BufferView::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name); +} + void Image::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const { Check(dld->vkBindImageMemory(owner, handle, memory, offset)); } +void Image::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name); +} + +void ImageView::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE_VIEW, name); +} + +int DeviceMemory::GetMemoryFdKHR() const { + const VkMemoryGetFdInfoKHR get_fd_info{ + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .pNext = nullptr, + .memory = handle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + }; + int fd; + Check(dld->vkGetMemoryFdKHR(owner, &get_fd_info, &fd)); + return fd; +} + +#ifdef _WIN32 +HANDLE DeviceMemory::GetMemoryWin32HandleKHR() const { + const VkMemoryGetWin32HandleInfoKHR get_win32_handle_info{ + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR, + .pNext = nullptr, + .memory = handle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR, + }; + HANDLE win32_handle; + Check(dld->vkGetMemoryWin32HandleKHR(owner, &get_win32_handle_info, &win32_handle)); + return win32_handle; +} +#endif + +void DeviceMemory::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_DEVICE_MEMORY, name); +} + +void Fence::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_FENCE, name); +} + +void Framebuffer::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_FRAMEBUFFER, name); +} + DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) const { const std::size_t num = ai.descriptorSetCount; std::unique_ptr sets = std::make_unique<VkDescriptorSet[]>(num); @@ -470,6 +560,10 @@ DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) c } } +void DescriptorPool::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_DESCRIPTOR_POOL, name); +} + CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLevel level) const { const VkCommandBufferAllocateInfo ai{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, @@ -490,6 +584,10 @@ CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLev } } +void CommandPool::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_COMMAND_POOL, name); +} + std::vector<VkImage> SwapchainKHR::GetImages() const { u32 num; Check(dld->vkGetSwapchainImagesKHR(owner, handle, &num, nullptr)); @@ -498,9 +596,21 @@ std::vector<VkImage> SwapchainKHR::GetImages() const { return images; } +void Event::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_EVENT, name); +} + +void ShaderModule::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_SHADER_MODULE, name); +} + +void Semaphore::SetObjectNameEXT(const char* name) const { + SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_SEMAPHORE, name); +} + Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci, Span<const char*> enabled_extensions, const void* next, - DeviceDispatch& dld) noexcept { + DeviceDispatch& dispatch) { const VkDeviceCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = next, @@ -513,13 +623,10 @@ Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreate .ppEnabledExtensionNames = enabled_extensions.data(), .pEnabledFeatures = nullptr, }; - VkDevice device; - if (dld.vkCreateDevice(physical_device, &ci, nullptr, &device) != VK_SUCCESS) { - return {}; - } - Load(device, dld); - return Device(device, dld); + Check(dispatch.vkCreateDevice(physical_device, &ci, nullptr, &device)); + Load(device, dispatch); + return Device(device, dispatch); } Queue Device::GetQueue(u32 family_index) const noexcept { @@ -558,7 +665,10 @@ Semaphore Device::CreateSemaphore() const { .pNext = nullptr, .flags = 0, }; + return CreateSemaphore(ci); +} +Semaphore Device::CreateSemaphore(const VkSemaphoreCreateInfo& ci) const { VkSemaphore object; Check(dld->vkCreateSemaphore(handle, &ci, nullptr, &object)); return Semaphore(object, handle, *dld); @@ -644,7 +754,7 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons return ShaderModule(object, handle, *dld); } -Event Device::CreateNewEvent() const { +Event Device::CreateEvent() const { static constexpr VkEventCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, .pNext = nullptr, @@ -676,10 +786,20 @@ DeviceMemory Device::AllocateMemory(const VkMemoryAllocateInfo& ai) const { return DeviceMemory(memory, handle, *dld); } -VkMemoryRequirements Device::GetBufferMemoryRequirements(VkBuffer buffer) const noexcept { - VkMemoryRequirements requirements; - dld->vkGetBufferMemoryRequirements(handle, buffer, &requirements); - return requirements; +VkMemoryRequirements Device::GetBufferMemoryRequirements(VkBuffer buffer, + void* pnext) const noexcept { + const VkBufferMemoryRequirementsInfo2 info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, + .pNext = nullptr, + .buffer = buffer, + }; + VkMemoryRequirements2 requirements{ + .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + .pNext = pnext, + .memoryRequirements{}, + }; + dld->vkGetBufferMemoryRequirements2(handle, &info, &requirements); + return requirements.memoryRequirements; } VkMemoryRequirements Device::GetImageMemoryRequirements(VkImage image) const noexcept { @@ -775,6 +895,21 @@ VkPhysicalDeviceMemoryProperties PhysicalDevice::GetMemoryProperties() const noe return properties; } +u32 AvailableVersion(const InstanceDispatch& dld) noexcept { + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion; + if (!Proc(vkEnumerateInstanceVersion, dld, "vkEnumerateInstanceVersion")) { + // If the procedure is not found, Vulkan 1.0 is assumed + return VK_API_VERSION_1_0; + } + u32 version; + if (const VkResult result = vkEnumerateInstanceVersion(&version); result != VK_SUCCESS) { + LOG_ERROR(Render_Vulkan, "vkEnumerateInstanceVersion returned {}, assuming Vulkan 1.1", + ToString(result)); + return VK_API_VERSION_1_1; + } + return version; +} + std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties( const InstanceDispatch& dld) { u32 num; @@ -786,7 +921,7 @@ std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProp VK_SUCCESS) { return std::nullopt; } - return std::move(properties); + return properties; } std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties( diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index b9d3fedc1..3e36d356a 100644 --- a/src/video_core/renderer_vulkan/wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -9,15 +9,31 @@ #include <limits> #include <memory> #include <optional> +#include <span> #include <type_traits> #include <utility> #include <vector> #define VK_NO_PROTOTYPES +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#endif #include <vulkan/vulkan.h> +// Sanitize macros +#ifdef CreateEvent +#undef CreateEvent +#endif +#ifdef CreateSemaphore +#undef CreateSemaphore +#endif + #include "common/common_types.h" +#ifdef _MSC_VER +#pragma warning(disable : 26812) // Disable prefer enum class over enum +#endif + namespace Vulkan::vk { /** @@ -41,6 +57,9 @@ public: /// Construct an empty span. constexpr Span() noexcept = default; + /// Construct an empty span + constexpr Span(std::nullptr_t) noexcept {} + /// Construct a span from a single element. constexpr Span(const T& value) noexcept : ptr{&value}, num{1} {} @@ -52,7 +71,7 @@ public: /// Construct a span from a pointer and a size. /// This is inteded for subranges. - constexpr Span(const T* ptr, std::size_t num) noexcept : ptr{ptr}, num{num} {} + constexpr Span(const T* ptr_, std::size_t num_) noexcept : ptr{ptr_}, num{num_} {} /// Returns the data pointer by the span. constexpr const T* data() const noexcept { @@ -136,145 +155,156 @@ inline VkResult Filter(VkResult result) { /// Table holding Vulkan instance function pointers. struct InstanceDispatch { - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; - - PFN_vkCreateInstance vkCreateInstance; - PFN_vkDestroyInstance vkDestroyInstance; - PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties; - PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties; - - PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT; - PFN_vkCreateDevice vkCreateDevice; - PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT; - PFN_vkDestroyDevice vkDestroyDevice; - PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; - PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; - PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; - PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR; - PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties; - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; - PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; - PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR; - PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; - PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; - PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; - PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; - PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; - PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; - PFN_vkQueuePresentKHR vkQueuePresentKHR; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; + + PFN_vkCreateInstance vkCreateInstance{}; + PFN_vkDestroyInstance vkDestroyInstance{}; + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties{}; + PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties{}; + + PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT{}; + PFN_vkCreateDevice vkCreateDevice{}; + PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT{}; + PFN_vkDestroyDevice vkDestroyDevice{}; + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR{}; + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties{}; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices{}; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr{}; + PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR{}; + PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{}; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{}; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{}; + PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR{}; + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties{}; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR{}; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR{}; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR{}; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR{}; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR{}; + PFN_vkQueuePresentKHR vkQueuePresentKHR{}; }; /// Table holding Vulkan device function pointers. -struct DeviceDispatch : public InstanceDispatch { - PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; - PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; - PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets; - PFN_vkAllocateMemory vkAllocateMemory; - PFN_vkBeginCommandBuffer vkBeginCommandBuffer; - PFN_vkBindBufferMemory vkBindBufferMemory; - PFN_vkBindImageMemory vkBindImageMemory; - PFN_vkCmdBeginQuery vkCmdBeginQuery; - PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; - PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT; - PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; - PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; - PFN_vkCmdBindPipeline vkCmdBindPipeline; - PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT; - PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; - PFN_vkCmdBlitImage vkCmdBlitImage; - PFN_vkCmdClearAttachments vkCmdClearAttachments; - PFN_vkCmdCopyBuffer vkCmdCopyBuffer; - PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; - PFN_vkCmdCopyImage vkCmdCopyImage; - PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer; - PFN_vkCmdDispatch vkCmdDispatch; - PFN_vkCmdDraw vkCmdDraw; - PFN_vkCmdDrawIndexed vkCmdDrawIndexed; - PFN_vkCmdEndQuery vkCmdEndQuery; - PFN_vkCmdEndRenderPass vkCmdEndRenderPass; - PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT; - PFN_vkCmdFillBuffer vkCmdFillBuffer; - PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; - PFN_vkCmdPushConstants vkCmdPushConstants; - PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants; - PFN_vkCmdSetDepthBias vkCmdSetDepthBias; - PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds; - PFN_vkCmdSetEvent vkCmdSetEvent; - PFN_vkCmdSetScissor vkCmdSetScissor; - PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask; - PFN_vkCmdSetStencilReference vkCmdSetStencilReference; - PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask; - PFN_vkCmdSetViewport vkCmdSetViewport; - PFN_vkCmdWaitEvents vkCmdWaitEvents; - PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT; - PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT; - PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT; - PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT; - PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT; - PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT; - PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT; - PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT; - PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT; - PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT; - PFN_vkCreateBuffer vkCreateBuffer; - PFN_vkCreateBufferView vkCreateBufferView; - PFN_vkCreateCommandPool vkCreateCommandPool; - PFN_vkCreateComputePipelines vkCreateComputePipelines; - PFN_vkCreateDescriptorPool vkCreateDescriptorPool; - PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout; - PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR; - PFN_vkCreateEvent vkCreateEvent; - PFN_vkCreateFence vkCreateFence; - PFN_vkCreateFramebuffer vkCreateFramebuffer; - PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines; - PFN_vkCreateImage vkCreateImage; - PFN_vkCreateImageView vkCreateImageView; - PFN_vkCreatePipelineLayout vkCreatePipelineLayout; - PFN_vkCreateQueryPool vkCreateQueryPool; - PFN_vkCreateRenderPass vkCreateRenderPass; - PFN_vkCreateSampler vkCreateSampler; - PFN_vkCreateSemaphore vkCreateSemaphore; - PFN_vkCreateShaderModule vkCreateShaderModule; - PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; - PFN_vkDestroyBuffer vkDestroyBuffer; - PFN_vkDestroyBufferView vkDestroyBufferView; - PFN_vkDestroyCommandPool vkDestroyCommandPool; - PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool; - PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout; - PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR; - PFN_vkDestroyEvent vkDestroyEvent; - PFN_vkDestroyFence vkDestroyFence; - PFN_vkDestroyFramebuffer vkDestroyFramebuffer; - PFN_vkDestroyImage vkDestroyImage; - PFN_vkDestroyImageView vkDestroyImageView; - PFN_vkDestroyPipeline vkDestroyPipeline; - PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout; - PFN_vkDestroyQueryPool vkDestroyQueryPool; - PFN_vkDestroyRenderPass vkDestroyRenderPass; - PFN_vkDestroySampler vkDestroySampler; - PFN_vkDestroySemaphore vkDestroySemaphore; - PFN_vkDestroyShaderModule vkDestroyShaderModule; - PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; - PFN_vkDeviceWaitIdle vkDeviceWaitIdle; - PFN_vkEndCommandBuffer vkEndCommandBuffer; - PFN_vkFreeCommandBuffers vkFreeCommandBuffers; - PFN_vkFreeDescriptorSets vkFreeDescriptorSets; - PFN_vkFreeMemory vkFreeMemory; - PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; - PFN_vkGetDeviceQueue vkGetDeviceQueue; - PFN_vkGetEventStatus vkGetEventStatus; - PFN_vkGetFenceStatus vkGetFenceStatus; - PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; - PFN_vkGetQueryPoolResults vkGetQueryPoolResults; - PFN_vkMapMemory vkMapMemory; - PFN_vkQueueSubmit vkQueueSubmit; - PFN_vkResetFences vkResetFences; - PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT; - PFN_vkUnmapMemory vkUnmapMemory; - PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR; - PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; - PFN_vkWaitForFences vkWaitForFences; +struct DeviceDispatch : InstanceDispatch { + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR{}; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers{}; + PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets{}; + PFN_vkAllocateMemory vkAllocateMemory{}; + PFN_vkBeginCommandBuffer vkBeginCommandBuffer{}; + PFN_vkBindBufferMemory vkBindBufferMemory{}; + PFN_vkBindImageMemory vkBindImageMemory{}; + PFN_vkCmdBeginQuery vkCmdBeginQuery{}; + PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{}; + PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT{}; + PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{}; + PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets{}; + PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer{}; + PFN_vkCmdBindPipeline vkCmdBindPipeline{}; + PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT{}; + PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers{}; + PFN_vkCmdBlitImage vkCmdBlitImage{}; + PFN_vkCmdClearAttachments vkCmdClearAttachments{}; + PFN_vkCmdCopyBuffer vkCmdCopyBuffer{}; + PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{}; + PFN_vkCmdCopyImage vkCmdCopyImage{}; + PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; + PFN_vkCmdDispatch vkCmdDispatch{}; + PFN_vkCmdDraw vkCmdDraw{}; + PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; + PFN_vkCmdEndQuery vkCmdEndQuery{}; + PFN_vkCmdEndRenderPass vkCmdEndRenderPass{}; + PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{}; + PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; + PFN_vkCmdFillBuffer vkCmdFillBuffer{}; + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{}; + PFN_vkCmdPushConstants vkCmdPushConstants{}; + PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; + PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; + PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; + PFN_vkCmdSetEvent vkCmdSetEvent{}; + PFN_vkCmdSetScissor vkCmdSetScissor{}; + PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask{}; + PFN_vkCmdSetStencilReference vkCmdSetStencilReference{}; + PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask{}; + PFN_vkCmdSetViewport vkCmdSetViewport{}; + PFN_vkCmdWaitEvents vkCmdWaitEvents{}; + PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT{}; + PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; + PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; + PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; + PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT{}; + PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{}; + PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT{}; + PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{}; + PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT{}; + PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{}; + PFN_vkCmdResolveImage vkCmdResolveImage{}; + PFN_vkCreateBuffer vkCreateBuffer{}; + PFN_vkCreateBufferView vkCreateBufferView{}; + PFN_vkCreateCommandPool vkCreateCommandPool{}; + PFN_vkCreateComputePipelines vkCreateComputePipelines{}; + PFN_vkCreateDescriptorPool vkCreateDescriptorPool{}; + PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout{}; + PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR{}; + PFN_vkCreateEvent vkCreateEvent{}; + PFN_vkCreateFence vkCreateFence{}; + PFN_vkCreateFramebuffer vkCreateFramebuffer{}; + PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines{}; + PFN_vkCreateImage vkCreateImage{}; + PFN_vkCreateImageView vkCreateImageView{}; + PFN_vkCreatePipelineLayout vkCreatePipelineLayout{}; + PFN_vkCreateQueryPool vkCreateQueryPool{}; + PFN_vkCreateRenderPass vkCreateRenderPass{}; + PFN_vkCreateSampler vkCreateSampler{}; + PFN_vkCreateSemaphore vkCreateSemaphore{}; + PFN_vkCreateShaderModule vkCreateShaderModule{}; + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR{}; + PFN_vkDestroyBuffer vkDestroyBuffer{}; + PFN_vkDestroyBufferView vkDestroyBufferView{}; + PFN_vkDestroyCommandPool vkDestroyCommandPool{}; + PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool{}; + PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout{}; + PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR{}; + PFN_vkDestroyEvent vkDestroyEvent{}; + PFN_vkDestroyFence vkDestroyFence{}; + PFN_vkDestroyFramebuffer vkDestroyFramebuffer{}; + PFN_vkDestroyImage vkDestroyImage{}; + PFN_vkDestroyImageView vkDestroyImageView{}; + PFN_vkDestroyPipeline vkDestroyPipeline{}; + PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout{}; + PFN_vkDestroyQueryPool vkDestroyQueryPool{}; + PFN_vkDestroyRenderPass vkDestroyRenderPass{}; + PFN_vkDestroySampler vkDestroySampler{}; + PFN_vkDestroySemaphore vkDestroySemaphore{}; + PFN_vkDestroyShaderModule vkDestroyShaderModule{}; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR{}; + PFN_vkDeviceWaitIdle vkDeviceWaitIdle{}; + PFN_vkEndCommandBuffer vkEndCommandBuffer{}; + PFN_vkFreeCommandBuffers vkFreeCommandBuffers{}; + PFN_vkFreeDescriptorSets vkFreeDescriptorSets{}; + PFN_vkFreeMemory vkFreeMemory{}; + PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{}; + PFN_vkGetDeviceQueue vkGetDeviceQueue{}; + PFN_vkGetEventStatus vkGetEventStatus{}; + PFN_vkGetFenceStatus vkGetFenceStatus{}; + PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{}; + PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{}; +#ifdef _WIN32 + PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{}; +#endif + PFN_vkGetQueryPoolResults vkGetQueryPoolResults{}; + PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR{}; + PFN_vkMapMemory vkMapMemory{}; + PFN_vkQueueSubmit vkQueueSubmit{}; + PFN_vkResetFences vkResetFences{}; + PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT{}; + PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; + PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT{}; + PFN_vkUnmapMemory vkUnmapMemory{}; + PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR{}; + PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets{}; + PFN_vkWaitForFences vkWaitForFences{}; + PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR{}; }; /// Loads instance agnostic function pointers. @@ -329,6 +359,9 @@ public: /// Construct an empty handle. Handle() = default; + /// Construct an empty handle. + Handle(std::nullptr_t) {} + /// Copying Vulkan objects is not supported and will never be. Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; @@ -467,9 +500,10 @@ public: PoolAllocations() = default; /// Construct an allocation. Errors are reported through IsOutOfPoolMemory(). - explicit PoolAllocations(std::unique_ptr<AllocationType[]> allocations, std::size_t num, - VkDevice device, PoolType pool, const DeviceDispatch& dld) noexcept - : allocations{std::move(allocations)}, num{num}, device{device}, pool{pool}, dld{&dld} {} + explicit PoolAllocations(std::unique_ptr<AllocationType[]> allocations_, std::size_t num_, + VkDevice device_, PoolType pool_, const DeviceDispatch& dld_) noexcept + : allocations{std::move(allocations_)}, num{num_}, device{device_}, pool{pool_}, + dld{&dld_} {} /// Copying Vulkan allocations is not supported and will never be. PoolAllocations(const PoolAllocations&) = delete; @@ -539,19 +573,14 @@ private: const DeviceDispatch* dld = nullptr; }; -using BufferView = Handle<VkBufferView, VkDevice, DeviceDispatch>; -using DebugCallback = Handle<VkDebugUtilsMessengerEXT, VkInstance, InstanceDispatch>; +using DebugUtilsMessenger = Handle<VkDebugUtilsMessengerEXT, VkInstance, InstanceDispatch>; using DescriptorSetLayout = Handle<VkDescriptorSetLayout, VkDevice, DeviceDispatch>; using DescriptorUpdateTemplateKHR = Handle<VkDescriptorUpdateTemplateKHR, VkDevice, DeviceDispatch>; -using Framebuffer = Handle<VkFramebuffer, VkDevice, DeviceDispatch>; -using ImageView = Handle<VkImageView, VkDevice, DeviceDispatch>; using Pipeline = Handle<VkPipeline, VkDevice, DeviceDispatch>; using PipelineLayout = Handle<VkPipelineLayout, VkDevice, DeviceDispatch>; using QueryPool = Handle<VkQueryPool, VkDevice, DeviceDispatch>; using RenderPass = Handle<VkRenderPass, VkDevice, DeviceDispatch>; using Sampler = Handle<VkSampler, VkDevice, DeviceDispatch>; -using Semaphore = Handle<VkSemaphore, VkDevice, DeviceDispatch>; -using ShaderModule = Handle<VkShaderModule, VkDevice, DeviceDispatch>; using SurfaceKHR = Handle<VkSurfaceKHR, VkInstance, InstanceDispatch>; using DescriptorSets = PoolAllocations<VkDescriptorSet, VkDescriptorPool>; @@ -562,16 +591,25 @@ class Instance : public Handle<VkInstance, NoOwner, InstanceDispatch> { using Handle<VkInstance, NoOwner, InstanceDispatch>::Handle; public: - /// Creates a Vulkan instance. Use "operator bool" for error handling. - static Instance Create(Span<const char*> layers, Span<const char*> extensions, - InstanceDispatch& dld) noexcept; + /// Creates a Vulkan instance. + /// @throw Exception on initialization error. + static Instance Create(u32 version, Span<const char*> layers, Span<const char*> extensions, + InstanceDispatch& dispatch); /// Enumerates physical devices. /// @return Physical devices and an empty handle on failure. - std::optional<std::vector<VkPhysicalDevice>> EnumeratePhysicalDevices(); + /// @throw Exception on Vulkan error. + std::vector<VkPhysicalDevice> EnumeratePhysicalDevices() const; + + /// Creates a debug callback messenger. + /// @throw Exception on creation failure. + DebugUtilsMessenger CreateDebugUtilsMessenger( + const VkDebugUtilsMessengerCreateInfoEXT& create_info) const; - /// Tries to create a debug callback messenger. Returns an empty handle on failure. - DebugCallback TryCreateDebugCallback(PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept; + /// Returns dispatch table. + const InstanceDispatch& Dispatch() const noexcept { + return *dld; + } }; class Queue { @@ -580,9 +618,11 @@ public: constexpr Queue() noexcept = default; /// Construct a queue handle. - constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {} + constexpr Queue(VkQueue queue_, const DeviceDispatch& dld_) noexcept + : queue{queue_}, dld{&dld_} {} - VkResult Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const noexcept { + VkResult Submit(Span<VkSubmitInfo> submit_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence); } @@ -601,6 +641,17 @@ class Buffer : public Handle<VkBuffer, VkDevice, DeviceDispatch> { public: /// Attaches a memory allocation. void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const; + + /// Set object name. + void SetObjectNameEXT(const char* name) const; +}; + +class BufferView : public Handle<VkBufferView, VkDevice, DeviceDispatch> { + using Handle<VkBufferView, VkDevice, DeviceDispatch>::Handle; + +public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; }; class Image : public Handle<VkImage, VkDevice, DeviceDispatch> { @@ -609,12 +660,32 @@ class Image : public Handle<VkImage, VkDevice, DeviceDispatch> { public: /// Attaches a memory allocation. void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const; + + /// Set object name. + void SetObjectNameEXT(const char* name) const; +}; + +class ImageView : public Handle<VkImageView, VkDevice, DeviceDispatch> { + using Handle<VkImageView, VkDevice, DeviceDispatch>::Handle; + +public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; }; class DeviceMemory : public Handle<VkDeviceMemory, VkDevice, DeviceDispatch> { using Handle<VkDeviceMemory, VkDevice, DeviceDispatch>::Handle; public: + int GetMemoryFdKHR() const; + +#ifdef _WIN32 + HANDLE GetMemoryWin32HandleKHR() const; +#endif + + /// Set object name. + void SetObjectNameEXT(const char* name) const; + u8* Map(VkDeviceSize offset, VkDeviceSize size) const { void* data; Check(dld->vkMapMemory(owner, handle, offset, size, 0, &data)); @@ -630,6 +701,9 @@ class Fence : public Handle<VkFence, VkDevice, DeviceDispatch> { using Handle<VkFence, VkDevice, DeviceDispatch>::Handle; public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; + VkResult Wait(u64 timeout = std::numeric_limits<u64>::max()) const noexcept { return dld->vkWaitForFences(owner, 1, &handle, true, timeout); } @@ -643,11 +717,22 @@ public: } }; +class Framebuffer : public Handle<VkFramebuffer, VkDevice, DeviceDispatch> { + using Handle<VkFramebuffer, VkDevice, DeviceDispatch>::Handle; + +public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; +}; + class DescriptorPool : public Handle<VkDescriptorPool, VkDevice, DeviceDispatch> { using Handle<VkDescriptorPool, VkDevice, DeviceDispatch>::Handle; public: DescriptorSets Allocate(const VkDescriptorSetAllocateInfo& ai) const; + + /// Set object name. + void SetObjectNameEXT(const char* name) const; }; class CommandPool : public Handle<VkCommandPool, VkDevice, DeviceDispatch> { @@ -656,6 +741,9 @@ class CommandPool : public Handle<VkCommandPool, VkDevice, DeviceDispatch> { public: CommandBuffers Allocate(std::size_t num_buffers, VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY) const; + + /// Set object name. + void SetObjectNameEXT(const char* name) const; }; class SwapchainKHR : public Handle<VkSwapchainKHR, VkDevice, DeviceDispatch> { @@ -669,18 +757,70 @@ class Event : public Handle<VkEvent, VkDevice, DeviceDispatch> { using Handle<VkEvent, VkDevice, DeviceDispatch>::Handle; public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; + VkResult GetStatus() const noexcept { return dld->vkGetEventStatus(owner, handle); } }; +class ShaderModule : public Handle<VkShaderModule, VkDevice, DeviceDispatch> { + using Handle<VkShaderModule, VkDevice, DeviceDispatch>::Handle; + +public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; +}; + +class Semaphore : public Handle<VkSemaphore, VkDevice, DeviceDispatch> { + using Handle<VkSemaphore, VkDevice, DeviceDispatch>::Handle; + +public: + /// Set object name. + void SetObjectNameEXT(const char* name) const; + + [[nodiscard]] u64 GetCounter() const { + u64 value; + Check(dld->vkGetSemaphoreCounterValueKHR(owner, handle, &value)); + return value; + } + + /** + * Waits for a timeline semaphore on the host. + * + * @param value Value to wait + * @param timeout Time in nanoseconds to timeout + * @return True on successful wait, false on timeout + */ + bool Wait(u64 value, u64 timeout = std::numeric_limits<u64>::max()) const { + const VkSemaphoreWaitInfoKHR wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .semaphoreCount = 1, + .pSemaphores = &handle, + .pValues = &value, + }; + const VkResult result = dld->vkWaitSemaphoresKHR(owner, &wait_info, timeout); + switch (result) { + case VK_SUCCESS: + return true; + case VK_TIMEOUT: + return false; + default: + throw Exception(result); + } + } +}; + class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> { using Handle<VkDevice, NoOwner, DeviceDispatch>::Handle; public: static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci, Span<const char*> enabled_extensions, const void* next, - DeviceDispatch& dld) noexcept; + DeviceDispatch& dispatch); Queue GetQueue(u32 family_index) const noexcept; @@ -694,6 +834,8 @@ public: Semaphore CreateSemaphore() const; + Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const; + Fence CreateFence(const VkFenceCreateInfo& ci) const; DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const; @@ -721,7 +863,7 @@ public: ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; - Event CreateNewEvent() const; + Event CreateEvent() const; SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; @@ -729,7 +871,8 @@ public: DeviceMemory AllocateMemory(const VkMemoryAllocateInfo& ai) const; - VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer) const noexcept; + VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer, + void* pnext = nullptr) const noexcept; VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept; @@ -767,8 +910,9 @@ class PhysicalDevice { public: constexpr PhysicalDevice() noexcept = default; - constexpr PhysicalDevice(VkPhysicalDevice physical_device, const InstanceDispatch& dld) noexcept - : physical_device{physical_device}, dld{&dld} {} + constexpr PhysicalDevice(VkPhysicalDevice physical_device_, + const InstanceDispatch& dld_) noexcept + : physical_device{physical_device_}, dld{&dld_} {} constexpr operator VkPhysicalDevice() const noexcept { return physical_device; @@ -807,8 +951,8 @@ class CommandBuffer { public: CommandBuffer() noexcept = default; - explicit CommandBuffer(VkCommandBuffer handle, const DeviceDispatch& dld) noexcept - : handle{handle}, dld{&dld} {} + explicit CommandBuffer(VkCommandBuffer handle_, const DeviceDispatch& dld_) noexcept + : handle{handle_}, dld{&dld_} {} const VkCommandBuffer* address() const noexcept { return &handle; @@ -887,6 +1031,12 @@ public: regions.data(), filter); } + void ResolveImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image, + VkImageLayout dst_layout, Span<VkImageResolve> regions) { + dld->vkCmdResolveImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(), + regions.data()); + } + void Dispatch(u32 x, u32 y, u32 z) const noexcept { dld->vkCmdDispatch(handle, x, y, z); } @@ -901,6 +1051,29 @@ public: image_barriers.size(), image_barriers.data()); } + void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, + VkDependencyFlags dependency_flags = 0) const noexcept { + PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, {}); + } + + void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, + VkDependencyFlags dependency_flags, + const VkMemoryBarrier& memory_barrier) const noexcept { + PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, memory_barrier, {}, {}); + } + + void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, + VkDependencyFlags dependency_flags, + const VkBufferMemoryBarrier& buffer_barrier) const noexcept { + PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, buffer_barrier, {}); + } + + void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, + VkDependencyFlags dependency_flags, + const VkImageMemoryBarrier& image_barrier) const noexcept { + PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, image_barrier); + } + void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout, Span<VkBufferImageCopy> regions) const noexcept { dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(), @@ -934,6 +1107,13 @@ public: dld->vkCmdPushConstants(handle, layout, flags, offset, size, values); } + template <typename T> + void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags, + const T& data) const noexcept { + static_assert(std::is_trivially_copyable_v<T>, "<data> is not trivially copyable"); + dld->vkCmdPushConstants(handle, layout, flags, 0, static_cast<u32>(sizeof(T)), &data); + } + void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept { dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data()); } @@ -1043,11 +1223,27 @@ public: counter_buffers, counter_buffer_offsets); } + void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept { + const VkDebugUtilsLabelEXT label_info{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pNext = nullptr, + .pLabelName = label, + .color{color[0], color[1], color[2], color[3]}, + }; + dld->vkCmdBeginDebugUtilsLabelEXT(handle, &label_info); + } + + void EndDebugUtilsLabelEXT() const noexcept { + dld->vkCmdEndDebugUtilsLabelEXT(handle); + } + private: VkCommandBuffer handle; const DeviceDispatch* dld; }; +u32 AvailableVersion(const InstanceDispatch& dld) noexcept; + std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties( const InstanceDispatch& dld); diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index 7e484b906..ae85a72ea 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -9,4 +9,4 @@ add_library(web_service STATIC ) create_target_directory_groups(web_service) -target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib lurlparser) +target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib) diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 74e287045..67183e64c 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -7,7 +7,6 @@ #include <mutex> #include <string> -#include <LUrlParser.h> #include <fmt/format.h> #include <httplib.h> @@ -19,9 +18,6 @@ namespace WebService { constexpr std::array<const char, 1> API_VERSION{'1'}; -constexpr int HTTP_PORT = 80; -constexpr int HTTPS_PORT = 443; - constexpr std::size_t TIMEOUT_SECONDS = 30; struct Client::Impl { @@ -67,28 +63,17 @@ struct Client::Impl { const std::string& jwt = "", const std::string& username = "", const std::string& token = "") { if (cli == nullptr) { - auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); - int port; - if (parsedUrl.m_Scheme == "http") { - if (!parsedUrl.GetPort(&port)) { - port = HTTP_PORT; - } - cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port); - } else if (parsedUrl.m_Scheme == "https") { - if (!parsedUrl.GetPort(&port)) { - port = HTTPS_PORT; - } - cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port); - } else { - LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); - return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""}; - } + cli = std::make_unique<httplib::Client>(host.c_str()); } - if (cli == nullptr) { - LOG_ERROR(WebService, "Invalid URL {}", host + path); - return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""}; + + if (!cli->is_valid()) { + LOG_ERROR(WebService, "Client is invalid, skipping request!"); + return {}; } - cli->set_timeout_sec(TIMEOUT_SECONDS); + + cli->set_connection_timeout(TIMEOUT_SECONDS); + cli->set_read_timeout(TIMEOUT_SECONDS); + cli->set_write_timeout(TIMEOUT_SECONDS); httplib::Headers params; if (!jwt.empty()) { diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 6987e85e1..b025ced1c 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -9,6 +9,9 @@ add_executable(yuzu about_dialog.cpp about_dialog.h aboutdialog.ui + applets/controller.cpp + applets/controller.h + applets/controller.ui applets/error.cpp applets/error.h applets/profile_select.cpp @@ -62,12 +65,20 @@ add_executable(yuzu configuration/configure_input.cpp configuration/configure_input.h configuration/configure_input.ui - configuration/configure_input_player.cpp - configuration/configure_input_player.h - configuration/configure_input_player.ui configuration/configure_input_advanced.cpp configuration/configure_input_advanced.h configuration/configure_input_advanced.ui + configuration/configure_input_player.cpp + configuration/configure_input_player.h + configuration/configure_input_player.ui + configuration/configure_input_player_widget.cpp + configuration/configure_input_player_widget.h + configuration/configure_input_profile_dialog.cpp + configuration/configure_input_profile_dialog.h + configuration/configure_input_profile_dialog.ui + configuration/configure_motion_touch.cpp + configuration/configure_motion_touch.h + configuration/configure_motion_touch.ui configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h configuration/configure_mouse_advanced.ui @@ -86,17 +97,28 @@ add_executable(yuzu configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui + configuration/configure_touch_from_button.cpp + configuration/configure_touch_from_button.h + configuration/configure_touch_from_button.ui configuration/configure_touchscreen_advanced.cpp configuration/configure_touchscreen_advanced.h configuration/configure_touchscreen_advanced.ui + configuration/configure_touch_widget.h configuration/configure_ui.cpp configuration/configure_ui.h configuration/configure_ui.ui + configuration/configure_vibration.cpp + configuration/configure_vibration.h + configuration/configure_vibration.ui configuration/configure_web.cpp configuration/configure_web.h configuration/configure_web.ui + configuration/input_profiles.cpp + configuration/input_profiles.h debugger/console.cpp debugger/console.h + debugger/controller.cpp + debugger/controller.h debugger/profiler.cpp debugger/profiler.h debugger/wait_tree.cpp @@ -123,10 +145,13 @@ add_executable(yuzu util/limitable_input_dialog.h util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h + util/url_request_interceptor.cpp + util/url_request_interceptor.h util/util.cpp util/util.h compatdb.cpp compatdb.h + yuzu.qrc yuzu.rc ) @@ -199,7 +224,8 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core) target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -if (ENABLE_VULKAN AND NOT WIN32) +target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) +if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() @@ -251,17 +277,12 @@ endif() if (MSVC) include(CopyYuzuQt5Deps) include(CopyYuzuSDLDeps) - include(CopyYuzuUnicornDeps) + include(CopyYuzuFFmpegDeps) copy_yuzu_Qt5_deps(yuzu) copy_yuzu_SDL_deps(yuzu) - copy_yuzu_unicorn_deps(yuzu) + copy_yuzu_FFmpeg_deps(yuzu) endif() if (NOT APPLE) target_compile_definitions(yuzu PRIVATE HAS_OPENGL) endif() - -if (ENABLE_VULKAN) - target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) - target_compile_definitions(yuzu PRIVATE HAS_VULKAN) -endif() diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index f122ba39d..1b320630c 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -160,32 +160,12 @@ p, li { white-space: pre-wrap; } <signal>accepted()</signal> <receiver>AboutDialog</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>AboutDialog</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp new file mode 100644 index 000000000..b92cd6886 --- /dev/null +++ b/src/yuzu/applets/controller.cpp @@ -0,0 +1,679 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <thread> + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "ui_controller.h" +#include "yuzu/applets/controller.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/main.h" + +namespace { + +constexpr std::size_t HANDHELD_INDEX = 8; + +constexpr std::array<std::array<bool, 4>, 8> led_patterns{{ + {true, false, false, false}, + {true, true, false, false}, + {true, true, true, false}, + {true, true, true, true}, + {true, false, false, true}, + {true, false, true, false}, + {true, false, true, true}, + {false, true, true, false}, +}}; + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, + bool connected) { + Core::System& system{Core::System::GetInstance()}; + + if (!system.IsPoweredOn()) { + return; + } + + Service::SM::ServiceManager& sm = system.ServiceManager(); + + auto& npad = + sm.GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + + npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +// Returns true if the given controller type is compatible with the given parameters. +bool IsControllerCompatible(Settings::ControllerType controller_type, + Core::Frontend::ControllerParameters parameters) { + switch (controller_type) { + case Settings::ControllerType::ProController: + return parameters.allow_pro_controller; + case Settings::ControllerType::DualJoyconDetached: + return parameters.allow_dual_joycons; + case Settings::ControllerType::LeftJoycon: + return parameters.allow_left_joycon; + case Settings::ControllerType::RightJoycon: + return parameters.allow_right_joycon; + case Settings::ControllerType::Handheld: + return parameters.enable_single_mode && parameters.allow_handheld; + case Settings::ControllerType::GameCube: + return parameters.allow_gamecube_controller; + default: + return false; + } +} + +} // namespace + +QtControllerSelectorDialog::QtControllerSelectorDialog( + QWidget* parent, Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), + parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, + input_profiles(std::make_unique<InputProfiles>()) { + ui->setupUi(this); + + player_widgets = { + ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, + ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, + }; + + player_groupboxes = { + ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, + ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, + ui->groupPlayer7Connected, ui->groupPlayer8Connected, + }; + + connected_controller_icons = { + ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, + ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, + }; + + led_patterns_boxes = {{ + {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, + ui->checkboxPlayer1LED4}, + {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, + ui->checkboxPlayer2LED4}, + {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, + ui->checkboxPlayer3LED4}, + {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, + ui->checkboxPlayer4LED4}, + {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, + ui->checkboxPlayer5LED4}, + {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, + ui->checkboxPlayer6LED4}, + {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, + ui->checkboxPlayer7LED4}, + {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, + ui->checkboxPlayer8LED4}, + }}; + + explain_text_labels = { + ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, + ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, + ui->labelPlayer7Explain, ui->labelPlayer8Explain, + }; + + emulated_controllers = { + ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, + ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, + ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, + }; + + player_labels = { + ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, + ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, + }; + + connected_controller_labels = { + ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, + ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, + ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, + }; + + connected_controller_checkboxes = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; + + // Setup/load everything prior to setting up connections. + // This avoids unintentionally changing the states of elements while loading them in. + SetSupportedControllers(); + DisableUnsupportedPlayers(); + + for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + SetEmulatedControllers(player_index); + } + + LoadConfiguration(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + SetExplainText(i); + UpdateControllerIcon(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { + if (checked) { + for (std::size_t index = 0; index <= i; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } else { + for (std::size_t index = i; index < NUM_PLAYERS; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } + }); + + connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), + [this, i](int) { + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + CheckIfParametersMet(); + }); + + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); + + if (i == 0) { + connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), + [this, i](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index, i) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->vibrationButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureVibrationDialog); + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputProfileDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + // Enhancement: Check if the parameters have already been met before disconnecting controllers. + // If all the parameters are met AND only allows a single player, + // stop the constructor here as we do not need to continue. + if (CheckIfParametersMet() && parameters.enable_single_mode) { + return; + } + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +int QtControllerSelectorDialog::exec() { + if (parameters_met && parameters.enable_single_mode) { + return QDialog::Accepted; + } + return QDialog::exec(); +} + +void QtControllerSelectorDialog::ApplyConfiguration() { + const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); + Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); + + Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); + Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto connected = + Settings::values.players.GetValue()[index].connected || + (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + player_groupboxes[index]->setChecked(connected); + connected_controller_checkboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( + Settings::values.players.GetValue()[index].controller_type, index)); + } + + UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); + ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); +} + +void QtControllerSelectorDialog::CallConfigureVibrationDialog() { + ConfigureVibration dialog(this); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + +void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { + ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get()); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); +} + +bool QtControllerSelectorDialog::CheckIfParametersMet() { + // Here, we check and validate the current configuration against all applicable parameters. + const auto num_connected_players = static_cast<int>( + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [this](const QGroupBox* player) { return player->isChecked(); })); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + // First, check against the number of connected players. + if (num_connected_players < min_supported_players || + num_connected_players > max_supported_players) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; + } + + // Next, check against all connected controllers. + const auto all_controllers_compatible = [this] { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + // Skip controllers that are not used, we only care about the currently connected ones. + if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { + continue; + } + + const auto compatible = IsControllerCompatible( + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), + parameters); + + // If any controller is found to be incompatible, return false early. + if (!compatible) { + return false; + } + } + + // Reaching here means all currently connected controllers are compatible. + return true; + }(); + + parameters_met = all_controllers_compatible; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + if (parameters.enable_single_mode && parameters.allow_handheld) { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); + } else { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); + } + + if (parameters.allow_dual_joycons) { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); + } else { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); + } + + if (parameters.allow_left_joycon) { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); + } else { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); + } + + if (parameters.allow_right_joycon) { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); + } else { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); + } + + if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); + } else { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") + .arg(theme)); + } + + // enable_single_mode overrides min_players and max_players. + if (parameters.enable_single_mode) { + ui->numberSupportedLabel->setText(QStringLiteral("1")); + return; + } + + if (parameters.min_players == parameters.max_players) { + ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); + } else { + ui->numberSupportedLabel->setText( + QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); + } +} + +void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { + auto& pairs = index_controller_type_pairs[player_index]; + + pairs.clear(); + emulated_controllers[player_index]->clear(); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::ProController); + emulated_controllers[player_index]->addItem(tr("Pro Controller")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::DualJoyconDetached); + emulated_controllers[player_index]->addItem(tr("Dual Joycons")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::LeftJoycon); + emulated_controllers[player_index]->addItem(tr("Left Joycon")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::RightJoycon); + emulated_controllers[player_index]->addItem(tr("Right Joycon")); + + if (player_index == 0) { + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::Handheld); + emulated_controllers[player_index]->addItem(tr("Handheld")); + } + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::GameCube); + emulated_controllers[player_index]->addItem(tr("GameCube Controller")); +} + +Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( + int index, std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [index](const auto& pair) { return pair.first == index; }); + + if (it == pairs.end()) { + return Settings::ControllerType::ProController; + } + + return it->second; +} + +int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, + std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [type](const auto& pair) { return pair.second == type; }); + + if (it == pairs.end()) { + return 0; + } + + return it->first; +} + +void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString stylesheet = [this, player_index] { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index)) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/applet_handheld%0); "); + default: + return QString{}; + } + }(); + + if (stylesheet.isEmpty()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); + player_labels[player_index]->hide(); +} + +void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { + auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = GetControllerTypeFromIndex( + emulated_controllers[player_index]->currentIndex(), player_index); + const auto player_connected = player_groupboxes[player_index]->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); + + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + // Handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + if (controller_type == Settings::ControllerType::Handheld) { + handheld = player; + } + handheld.connected = player_groupboxes[player_index]->isChecked() && + controller_type == Settings::ControllerType::Handheld; + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); + } + + if (!player.connected) { + return; + } + + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + using namespace std::chrono_literals; + std::this_thread::sleep_for(60ms); + + UpdateController(controller_type, player_index, player_connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index) == Settings::ControllerType::Handheld) { + led_patterns_boxes[player_index][0]->setChecked(false); + led_patterns_boxes[player_index][1]->setChecked(false); + led_patterns_boxes[player_index][2]->setChecked(false); + led_patterns_boxes[player_index][3]->setChecked(false); + return; + } + + led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); + led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); + led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); + led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); +} + +void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { + if (!parameters.enable_border_color || + player_index >= static_cast<std::size_t>(parameters.max_players) || + player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { + return; + } + + player_groupboxes[player_index]->setStyleSheet( + player_groupboxes[player_index]->styleSheet().append( + QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " + "{ border: 1px solid rgba(%2, %3, %4, %5); }") + .arg(player_index + 1) + .arg(parameters.border_colors[player_index][0]) + .arg(parameters.border_colors[player_index][1]) + .arg(parameters.border_colors[player_index][2]) + .arg(parameters.border_colors[player_index][3]))); +} + +void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { + if (!parameters.enable_explain_text || + player_index >= static_cast<std::size_t>(parameters.max_players)) { + return; + } + + explain_text_labels[player_index]->setText(QString::fromStdString( + Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), + parameters.explain_text[player_index].size()))); +} + +void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { + // Disallow changing the console mode if the controller type is handheld. + ui->radioDocked->setEnabled(!is_handheld); + ui->radioUndocked->setEnabled(!is_handheld); + + ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); + + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); + } +} + +void QtControllerSelectorDialog::DisableUnsupportedPlayers() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + switch (max_supported_players) { + case 0: + default: + UNREACHABLE(); + return; + case 1: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + ui->widgetSpacer4->hide(); + break; + case 2: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + break; + case 3: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + break; + case 4: + ui->widgetSpacer->hide(); + break; + case 5: + case 6: + case 7: + case 8: + break; + } + + for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { + // Disconnect any unsupported players here and disable or hide them if applicable. + Settings::values.players.GetValue()[index].connected = false; + UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false); + // Hide the player widgets when max_supported_controllers is less than or equal to 4. + if (max_supported_players <= 4) { + player_widgets[index]->hide(); + } + + // Disable and hide the following to prevent these from interaction. + player_widgets[index]->setDisabled(true); + connected_controller_checkboxes[index]->setDisabled(true); + connected_controller_labels[index]->hide(); + connected_controller_checkboxes[index]->hide(); + } +} + +QtControllerSelector::QtControllerSelector(GMainWindow& parent) { + connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, + &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, + &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); +} + +QtControllerSelector::~QtControllerSelector() = default; + +void QtControllerSelector::ReconfigureControllers( + std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const { + callback = std::move(callback_); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard lock(HLE::g_hle_lock); + callback(); +} diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h new file mode 100644 index 000000000..3518eed56 --- /dev/null +++ b/src/yuzu/applets/controller.h @@ -0,0 +1,161 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> +#include "core/frontend/applets/controller.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Settings { +enum class ControllerType; +} + +namespace Ui { +class QtControllerSelectorDialog; +} + +class QtControllerSelectorDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtControllerSelectorDialog(QWidget* parent, + Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_); + ~QtControllerSelectorDialog() override; + + int exec() override; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + // Initializes the "Configure Vibration" Dialog. + void CallConfigureVibrationDialog(); + + // Initializes the "Create Input Profile" Dialog. + void CallConfigureInputProfileDialog(); + + // Checks the current configuration against the given parameters. + // This sets and returns the value of parameters_met. + bool CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // Sets the emulated controllers per player. + void SetEmulatedControllers(std::size_t player_index); + + // Gets the Controller Type for a given controller combobox index per player. + Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; + + // Gets the controller combobox index for a given Controller Type per player. + int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; + + // Updates the controller icons per player. + void UpdateControllerIcon(std::size_t player_index); + + // Updates the controller state (type and connection status) per player. + void UpdateControllerState(std::size_t player_index); + + // Updates the LED pattern per player. + void UpdateLEDPattern(std::size_t player_index); + + // Updates the border color per player. + void UpdateBorderColor(std::size_t player_index); + + // Sets the "Explain Text" per player. + void SetExplainText(std::size_t player_index); + + // Updates the console mode. + void UpdateDockedState(bool is_handheld); + + // Disables and disconnects unsupported players based on the given parameters. + void DisableUnsupportedPlayers(); + + std::unique_ptr<Ui::QtControllerSelectorDialog> ui; + + // Parameters sent in from the backend HLE applet. + Core::Frontend::ControllerParameters parameters; + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr<InputProfiles> input_profiles; + + // This is true if and only if all parameters are met. Otherwise, this is false. + // This determines whether the "OK" button can be clicked to exit the applet. + bool parameters_met{false}; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Widgets encapsulating the groupboxes and comboboxes per player. + std::array<QWidget*, NUM_PLAYERS> player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes; + + // Icons for currently connected controllers/players. + std::array<QWidget*, NUM_PLAYERS> connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array<QLabel*, NUM_PLAYERS> player_labels; + + // LED patterns for currently connected controllers/players. + std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes; + + // Labels representing additional information known as "Explain Text" per player. + std::array<QLabel*, NUM_PLAYERS> explain_text_labels; + + // Comboboxes with a list of emulated controllers per player. + std::array<QComboBox*, NUM_PLAYERS> emulated_controllers; + + /// Pairs of emulated controller index and Controller Type enum per player. + std::array<std::vector<std::pair<int, Settings::ControllerType>>, NUM_PLAYERS> + index_controller_type_pairs; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array<QLabel*, NUM_PLAYERS> connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes; +}; + +class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { + Q_OBJECT + +public: + explicit QtControllerSelector(GMainWindow& parent); + ~QtControllerSelector() override; + + void ReconfigureControllers( + std::function<void()> callback_, + const Core::Frontend::ControllerParameters& parameters) const override; + +signals: + void MainWindowReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function<void()> callback; +}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui new file mode 100644 index 000000000..c8cb6bcf3 --- /dev/null +++ b/src/yuzu/applets/controller.ui @@ -0,0 +1,2653 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtControllerSelectorDialog</class> + <widget class="QDialog" name="QtControllerSelectorDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>839</width> + <height>630</height> + </rect> + </property> + <property name="windowTitle"> + <string>Controller Applet</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="mainControllerApplet" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="topControllerApplet" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QWidget" name="controllersSupported" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_21"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="controllersSupportedLabel"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Supported Controller Types:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported1" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported2" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported3" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported4" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported5" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="playersSupported" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_20"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>16</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>16</number> + </property> + <item> + <widget class="QLabel" name="maxSupportedLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Players:</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="numberSupportedLabel"> + <property name="font"> + <font> + <pointsize>14</pointsize> + </font> + </property> + <property name="text"> + <string>1 - 8</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="controllerAppletHorizontalSpacer3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="middleControllerApplet" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="spacing"> + <number>5</number> + </property> + <item row="1" column="7"> + <widget class="QWidget" name="widgetPlayer4" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_27"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer4Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer4" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_15"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer4"> + <property name="text"> + <string>P4</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player4LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player4Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_39"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer4Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer4Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer4Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="3"> + <widget class="QWidget" name="widgetPlayer2" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_29"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer2Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer2" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_13"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer2"> + <property name="text"> + <string>P2</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player2LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player2Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_37"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer2Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer2Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer2Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QWidget" name="widgetPlayer1" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_30"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer1Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer1" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_12"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer1"> + <property name="text"> + <string>P1</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player1LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED1"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player1Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_36"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer1Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer1Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Handheld</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer1Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="8"> + <widget class="QWidget" name="widgetSpacer2" native="true"> + <property name="minimumSize"> + <size> + <width>25</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_31"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="4"> + <widget class="QWidget" name="widgetSpacer4" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_33"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="6"> + <widget class="QWidget" name="widgetSpacer3" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_32"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="5"> + <widget class="QWidget" name="widgetPlayer3" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_28"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer3Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer3" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_14"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer3"> + <property name="text"> + <string>P3</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player3LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player3Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_38"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer3Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer3Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer3Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QWidget" name="widgetSpacer5" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_34"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="6" column="5"> + <widget class="QWidget" name="widgetPlayer7" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_25"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer7Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer7" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_18"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer7"> + <property name="text"> + <string>P7</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player7LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player7Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_42"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer7Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer7Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer7Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="7"> + <widget class="QWidget" name="widgetPlayer8" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_26"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer8Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer8" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_19"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer8"> + <property name="text"> + <string>P8</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player8LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player8Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_35"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer8Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer8Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer8Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="1"> + <widget class="QWidget" name="widgetPlayer5" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_23"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer5Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer5" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_16"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer5"> + <property name="text"> + <string>P5</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player5LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED1"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player5Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_40"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer5Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer5Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer5Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="3"> + <widget class="QWidget" name="widgetPlayer6" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_24"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer6Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer6" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_17"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer6"> + <property name="text"> + <string>P6</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player6LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="Player6Explain" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_41"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer6Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer6Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer6Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="10" column="1"> + <widget class="QWidget" name="widgetSpacer" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_22"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="2"> + <widget class="QWidget" name="widgetSpacer6" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QWidget" name="widgetSpacer7" native="true"> + <property name="minimumSize"> + <size> + <width>25</width> + <height>0</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QWidget" name="widgetSpacer9" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_17"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="bottomControllerApplet" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <property name="spacing"> + <number>15</number> + </property> + <property name="leftMargin"> + <number>15</number> + </property> + <property name="topMargin"> + <number>8</number> + </property> + <property name="rightMargin"> + <number>15</number> + </property> + <property name="bottomMargin"> + <number>15</number> + </property> + <item> + <widget class="QGroupBox" name="handheldGroup"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="title"> + <string>Console Mode</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>8</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QRadioButton" name="radioDocked"> + <property name="text"> + <string>Docked</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioUndocked"> + <property name="text"> + <string>Undocked</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroup"> + <property name="title"> + <string>Vibration</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="vibrationButton"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="motionGroup"> + <property name="title"> + <string>Motion</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="motionButton"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="inputConfigGroup"> + <property name="title"> + <string>Profiles</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="inputConfigButton"> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Create</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="connectedControllers" native="true"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>3</number> + </property> + <item row="1" column="4"> + <widget class="QCheckBox" name="checkboxPlayer4Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelControllers"> + <property name="text"> + <string>Controllers</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="checkboxPlayer2Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelConnectedPlayer1"> + <property name="text"> + <string>1</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QCheckBox" name="checkboxPlayer3Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="checkboxPlayer1Connected"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="labelConnectedPlayer2"> + <property name="text"> + <string>2</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="labelConnectedPlayer4"> + <property name="text"> + <string>4</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="labelConnectedPlayer3"> + <property name="text"> + <string>3</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="labelConnected"> + <property name="text"> + <string>Connected</string> + </property> + </widget> + </item> + <item row="1" column="7"> + <widget class="QCheckBox" name="checkboxPlayer7Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QLabel" name="labelConnectedPlayer5"> + <property name="text"> + <string>5</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="6"> + <widget class="QCheckBox" name="checkboxPlayer6Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QLabel" name="labelConnectedPlayer7"> + <property name="text"> + <string>7</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QCheckBox" name="checkboxPlayer5Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="6"> + <widget class="QLabel" name="labelConnectedPlayer6"> + <property name="text"> + <string>6</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="8"> + <widget class="QLabel" name="labelConnectedPlayer8"> + <property name="text"> + <string>8</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="8"> + <widget class="QCheckBox" name="checkboxPlayer8Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="controllerAppletHorizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignBottom"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QtControllerSelectorDialog</receiver> + <slot>accept()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp index 08ed57355..8ee03ddb3 100644 --- a/src/yuzu/applets/error.cpp +++ b/src/yuzu/applets/error.cpp @@ -17,9 +17,9 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { QtErrorDisplay::~QtErrorDisplay() = default; void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occured.\nPlease try again or contact the developer of the " + tr("An error has occurred.\nPlease try again or contact the developer of the " "software.\n\nError Code: %1-%2 (0x%3)") .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) @@ -28,11 +28,11 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( - tr("An error occured on %1 at %2.\nPlease try again or contact the " + tr("An error occurred on %1 at %2.\nPlease try again or contact the " "developer of the software.\n\nError Code: %3-%4 (0x%5)") .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) .arg(date_time.toString(QStringLiteral("h:mm:ss A"))) @@ -44,9 +44,9 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") + tr("An error has occurred.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) .arg(error.raw, 8, 16, QChar::fromLatin1('0')) diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index dca8835ed..0a4c48b3d 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp @@ -93,7 +93,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) const auto& profiles = profile_manager->GetAllUsers(); for (const auto& user : profiles) { - Service::Account::ProfileBase profile; + Service::Account::ProfileBase profile{}; if (!profile_manager->GetProfileBase(user, profile)) continue; @@ -114,6 +114,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; +int QtProfileSelectionDialog::exec() { + // Skip profile selection when there's only one. + if (profile_manager->GetUserCount() == 1) { + user_index = 0; + return QDialog::Accepted; + } + return QDialog::exec(); +} + void QtProfileSelectionDialog::accept() { QDialog::accept(); } @@ -141,8 +150,8 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) { QtProfileSelector::~QtProfileSelector() = default; void QtProfileSelector::SelectProfile( - std::function<void(std::optional<Common::UUID>)> callback) const { - this->callback = std::move(callback); + std::function<void(std::optional<Common::UUID>)> callback_) const { + callback = std::move(callback_); emit MainWindowSelectProfile(); } diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h index cee886a77..4e9037488 100644 --- a/src/yuzu/applets/profile_select.h +++ b/src/yuzu/applets/profile_select.h @@ -27,6 +27,7 @@ public: explicit QtProfileSelectionDialog(QWidget* parent); ~QtProfileSelectionDialog() override; + int exec() override; void accept() override; void reject() override; @@ -59,7 +60,7 @@ public: explicit QtProfileSelector(GMainWindow& parent); ~QtProfileSelector() override; - void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override; + void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override; signals: void MainWindowSelectProfile() const; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index af36f07c6..ab8cfd8ee 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -135,8 +135,8 @@ void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16st } void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const { - this->finished_check = std::move(finished_check); + std::function<void()> finished_check_) const { + finished_check = std::move(finished_check_); emit MainWindowTextCheckDialog(error_message); } diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 44bcece75..9e1094cce 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -61,7 +61,7 @@ public: void RequestText(std::function<void(std::optional<std::u16string>)> out, Core::Frontend::SoftwareKeyboardParameters parameters) const override; void SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const override; + std::function<void()> finished_check_) const override; signals: void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 33f1c385d..e482ba029 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -1,115 +1,414 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <mutex> - +#ifdef YUZU_USE_QT_WEB_ENGINE #include <QKeyEvent> -#include "core/hle/lock.h" +#include <QWebEngineProfile> +#include <QWebEngineScript> +#include <QWebEngineScriptCollection> +#include <QWebEngineSettings> +#include <QWebEngineUrlScheme> +#endif + +#include "common/file_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" #include "yuzu/applets/web_browser.h" +#include "yuzu/applets/web_browser_scripts.h" #include "yuzu/main.h" +#include "yuzu/util/url_request_interceptor.h" #ifdef YUZU_USE_QT_WEB_ENGINE -constexpr char NX_SHIM_INJECT_SCRIPT[] = R"( - window.nx = {}; - window.nx.playReport = {}; - window.nx.playReport.setCounterSetIdentifier = function () { - console.log("nx.playReport.setCounterSetIdentifier called - unimplemented"); - }; +namespace { - window.nx.playReport.incrementCounter = function () { - console.log("nx.playReport.incrementCounter called - unimplemented"); - }; +constexpr int HIDButtonToKey(HIDButton button) { + switch (button) { + case HIDButton::DLeft: + case HIDButton::LStickLeft: + return Qt::Key_Left; + case HIDButton::DUp: + case HIDButton::LStickUp: + return Qt::Key_Up; + case HIDButton::DRight: + case HIDButton::LStickRight: + return Qt::Key_Right; + case HIDButton::DDown: + case HIDButton::LStickDown: + return Qt::Key_Down; + default: + return 0; + } +} + +} // Anonymous namespace + +QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_) + : QWebEngineView(parent), input_subsystem{input_subsystem_}, + url_interceptor(std::make_unique<UrlRequestInterceptor>()), + input_interpreter(std::make_unique<InputInterpreter>(system)), + default_profile{QWebEngineProfile::defaultProfile()}, + global_settings{QWebEngineSettings::globalSettings()} { + QWebEngineScript gamepad; + QWebEngineScript window_nx; + + gamepad.setName(QStringLiteral("gamepad_script.js")); + window_nx.setName(QStringLiteral("window_nx_script.js")); + + gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); + window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); + + gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); + window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); + + gamepad.setWorldId(QWebEngineScript::MainWorld); + window_nx.setWorldId(QWebEngineScript::MainWorld); + + gamepad.setRunsOnSubFrames(true); + window_nx.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(gamepad); + default_profile->scripts()->insert(window_nx); + + default_profile->setRequestInterceptor(url_interceptor.get()); + + global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); + global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); + global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); + + global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); + + connect( + page(), &QWebEnginePage::windowCloseRequested, page(), + [this] { + if (page()->url() == url_interceptor->GetRequestedURL()) { + SetFinished(true); + SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); + } + }, + Qt::QueuedConnection); +} + +QtNXWebEngineView::~QtNXWebEngineView() { + SetFinished(true); + StopInputThread(); +} + +void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url, + std::string_view additional_args) { + is_local = true; + + LoadExtractedFonts(); + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() + + QString::fromStdString(std::string(additional_args)))); +} + +void QtNXWebEngineView::LoadExternalWebPage(std::string_view main_url, + std::string_view additional_args) { + is_local = false; + + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QString::fromStdString(std::string(main_url)) + + QString::fromStdString(std::string(additional_args)))); +} + +void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { + const QString user_agent_str = [user_agent] { + switch (user_agent) { + case UserAgent::WebApplet: + default: + return QStringLiteral("WebApplet"); + case UserAgent::ShopN: + return QStringLiteral("ShopN"); + case UserAgent::LoginApplet: + return QStringLiteral("LoginApplet"); + case UserAgent::ShareApplet: + return QStringLiteral("ShareApplet"); + case UserAgent::LobbyApplet: + return QStringLiteral("LobbyApplet"); + case UserAgent::WifiWebAuthApplet: + return QStringLiteral("WifiWebAuthApplet"); + } + }(); + + QWebEngineProfile::defaultProfile()->setHttpUserAgent( + QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " + "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") + .arg(user_agent_str)); +} + +bool QtNXWebEngineView::IsFinished() const { + return finished; +} + +void QtNXWebEngineView::SetFinished(bool finished_) { + finished = finished_; +} + +Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { + return exit_reason; +} + +void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { + exit_reason = exit_reason_; +} + +const std::string& QtNXWebEngineView::GetLastURL() const { + return last_url; +} + +void QtNXWebEngineView::SetLastURL(std::string last_url_) { + last_url = std::move(last_url_); +} + +QString QtNXWebEngineView::GetCurrentURL() const { + return url_interceptor->GetRequestedURL().toString(); +} + +void QtNXWebEngineView::hide() { + SetFinished(true); + StopInputThread(); - window.nx.footer = {}; - window.nx.footer.unsetAssign = function () { - console.log("nx.footer.unsetAssign called - unimplemented"); + QWidget::hide(); +} + +void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->ReleaseKey(event->key()); + } +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + page()->runJavaScript( + QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)), + [&](const QVariant& variant) { + if (variant.toBool()) { + switch (button) { + case HIDButton::A: + SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>(); + break; + case HIDButton::B: + SendKeyPressEvent(Qt::Key_B); + break; + case HIDButton::X: + SendKeyPressEvent(Qt::Key_X); + break; + case HIDButton::Y: + SendKeyPressEvent(Qt::Key_Y); + break; + default: + break; + } + } + }); + + page()->runJavaScript( + QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") + .arg(static_cast<u8>(button))); + } }; - var yuzu_key_callbacks = []; - window.nx.footer.setAssign = function(key, discard1, func, discard2) { - switch (key) { - case 'A': - yuzu_key_callbacks[0] = func; - break; - case 'B': - yuzu_key_callbacks[1] = func; - break; - case 'X': - yuzu_key_callbacks[2] = func; - break; - case 'Y': - yuzu_key_callbacks[3] = func; - break; - case 'L': - yuzu_key_callbacks[6] = func; - break; - case 'R': - yuzu_key_callbacks[7] = func; - break; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); } }; - var applet_done = false; - window.nx.endApplet = function() { - applet_done = true; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } }; - window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } }; -)"; + (f(T), ...); +} + +void QtNXWebEngineView::SendKeyPressEvent(int key) { + if (key == 0) { + return; + } + + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); +} + +void QtNXWebEngineView::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + input_thread = std::thread(&QtNXWebEngineView::InputThread, this); +} + +void QtNXWebEngineView::StopInputThread() { + if (is_local) { + QWidget::releaseKeyboard(); + } -QString GetNXShimInjectionScript() { - return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); + input_thread_running = false; + if (input_thread.joinable()) { + input_thread.join(); + } } -NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} +void QtNXWebEngineView::InputThread() { + // Wait for 1 second before allowing any inputs to be processed. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (is_local) { + QWidget::grabKeyboard(); + } + + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y, + HIDButton::L, HIDButton::R>(); + + HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, + HIDButton::DDown, HIDButton::LStickLeft, + HIDButton::LStickUp, HIDButton::LStickRight, + HIDButton::LStickDown>(); -void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { - parent()->event(event); + HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, + HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp, + HIDButton::LStickRight, HIDButton::LStickDown>(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } } -void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { - parent()->event(event); +void QtNXWebEngineView::LoadExtractedFonts() { + QWebEngineScript nx_font_css; + QWebEngineScript load_nx_font; + + const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); + + nx_font_css.setName(QStringLiteral("nx_font_css.js")); + load_nx_font.setName(QStringLiteral("load_nx_font.js")); + + nx_font_css.setSourceCode( + QString::fromStdString(NX_FONT_CSS) + .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) + .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) + .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) + .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) + .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); + load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); + + nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); + load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); + + nx_font_css.setWorldId(QWebEngineScript::MainWorld); + load_nx_font.setWorldId(QWebEngineScript::MainWorld); + + nx_font_css.setRunsOnSubFrames(true); + load_nx_font.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(nx_font_css); + default_profile->scripts()->insert(load_nx_font); + + connect( + url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), + [this] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); + }, + Qt::QueuedConnection); } #endif QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { - connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage, - Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this, - &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this, - &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection); + connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, + &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, + &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserClosed, this, + &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); } QtWebBrowser::~QtWebBrowser() = default; -void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { - this->unpack_romfs_callback = std::move(unpack_romfs_callback); - this->finished_callback = std::move(finished_callback); +void QtWebBrowser::OpenLocalWebPage( + std::string_view local_url, std::function<void()> extract_romfs_callback_, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const { + extract_romfs_callback = std::move(extract_romfs_callback_); + callback = std::move(callback_); + + const auto index = local_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(local_url, "", true); + } else { + emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); + } +} + +void QtWebBrowser::OpenExternalWebPage( + std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const { + callback = std::move(callback_); + + const auto index = external_url.find('?'); - const auto index = url.find('?'); if (index == std::string::npos) { - emit MainWindowOpenPage(url, ""); + emit MainWindowOpenWebPage(external_url, "", false); } else { - const auto front = url.substr(0, index); - const auto back = url.substr(index); - emit MainWindowOpenPage(front, back); + emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), + false); } } -void QtWebBrowser::MainWindowUnpackRomFS() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - unpack_romfs_callback(); +void QtWebBrowser::MainWindowExtractOfflineRomFS() { + extract_romfs_callback(); } -void QtWebBrowser::MainWindowFinishedBrowsing() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - finished_callback(); +void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url) { + callback(exit_reason, last_url); } diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index b38437e46..47f960d69 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -1,10 +1,13 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <functional> +#include <atomic> +#include <memory> +#include <thread> + #include <QObject> #ifdef YUZU_USE_QT_WEB_ENGINE @@ -13,19 +16,172 @@ #include "core/frontend/applets/web_browser.h" +enum class HIDButton : u8; + class GMainWindow; +class InputInterpreter; +class UrlRequestInterceptor; + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} #ifdef YUZU_USE_QT_WEB_ENGINE -QString GetNXShimInjectionScript(); +enum class UserAgent { + WebApplet, + ShopN, + LoginApplet, + ShareApplet, + LobbyApplet, + WifiWebAuthApplet, +}; + +class QWebEngineProfile; +class QWebEngineSettings; + +class QtNXWebEngineView : public QWebEngineView { + Q_OBJECT -class NXInputWebEngineView : public QWebEngineView { public: - explicit NXInputWebEngineView(QWidget* parent = nullptr); + explicit QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_); + ~QtNXWebEngineView() override; + + /** + * Loads a HTML document that exists locally. Cannot be used to load external websites. + * + * @param main_url The url to the file. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args); + + /** + * Loads an external website. Cannot be used to load local urls. + * + * @param main_url The url to the website. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadExternalWebPage(std::string_view main_url, std::string_view additional_args); + + /** + * Sets the background color of the web page. + * + * @param color The color to set. + */ + void SetBackgroundColor(QColor color); + + /** + * Sets the user agent of the web browser. + * + * @param user_agent The user agent enum. + */ + void SetUserAgent(UserAgent user_agent); + + [[nodiscard]] bool IsFinished() const; + void SetFinished(bool finished_); + + [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; + void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); + + [[nodiscard]] const std::string& GetLastURL() const; + void SetLastURL(std::string last_url_); + + /** + * This gets the current URL that has been requested by the webpage. + * This only applies to the main frame. Sub frames and other resources are ignored. + * + * @return Currently requested URL + */ + [[nodiscard]] QString GetCurrentURL() const; + +public slots: + void hide(); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + +private: + /** + * Handles button presses to execute functions assigned in yuzu_key_callbacks. + * yuzu_key_callbacks contains specialized functions for the buttons in the window footer + * that can be overriden by games to achieve desired functionality. + * + * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks + */ + template <HIDButton... T> + void HandleWindowFooterButtonPressedOnce(); + + /** + * Handles button presses and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template <HIDButton... T> + void HandleWindowKeyButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template <HIDButton... T> + void HandleWindowKeyButtonHold(); + + /** + * Sends a key press event to QWebEngineView. + * + * @param key Qt key code. + */ + void SendKeyPressEvent(int key); + + /** + * Sends multiple key press events to QWebEngineView. + * + * @tparam int Qt key code. + */ + template <int... T> + void SendMultipleKeyPressEvents() { + (SendKeyPressEvent(T), ...); + } + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + /// Loads the extracted fonts using JavaScript. + void LoadExtractedFonts(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr<UrlRequestInterceptor> url_interceptor; + + std::unique_ptr<InputInterpreter> input_interpreter; + + std::thread input_thread; + + std::atomic<bool> input_thread_running{}; + + std::atomic<bool> finished{}; + + Service::AM::Applets::WebExitReason exit_reason{ + Service::AM::Applets::WebExitReason::EndButtonPressed}; + + std::string last_url{"http://localhost/"}; + + bool is_local{}; + + QWebEngineProfile* default_profile; + QWebEngineSettings* global_settings; }; #endif @@ -34,19 +190,28 @@ class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserAppl Q_OBJECT public: - explicit QtWebBrowser(GMainWindow& main_window); + explicit QtWebBrowser(GMainWindow& parent); ~QtWebBrowser() override; - void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; + + void OpenExternalWebPage(std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; signals: - void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; + void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) const; private: - void MainWindowUnpackRomFS(); - void MainWindowFinishedBrowsing(); + void MainWindowExtractOfflineRomFS(); + + void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url); + + mutable std::function<void()> extract_romfs_callback; - std::function<void()> unpack_romfs_callback; - std::function<void()> finished_callback; + mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback; }; diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h new file mode 100644 index 000000000..992837a85 --- /dev/null +++ b/src/yuzu/applets/web_browser_scripts.h @@ -0,0 +1,193 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +constexpr char NX_FONT_CSS[] = R"( +(function() { + css = document.createElement('style'); + css.type = 'text/css'; + css.id = 'nx_font'; + css.innerText = ` +/* FontStandard */ +@font-face { + font-family: 'FontStandard'; + src: url('%1') format('truetype'); +} + +/* FontChineseSimplified */ +@font-face { + font-family: 'FontChineseSimplified'; + src: url('%2') format('truetype'); +} + +/* FontExtendedChineseSimplified */ +@font-face { + font-family: 'FontExtendedChineseSimplified'; + src: url('%3') format('truetype'); +} + +/* FontChineseTraditional */ +@font-face { + font-family: 'FontChineseTraditional'; + src: url('%4') format('truetype'); +} + +/* FontKorean */ +@font-face { + font-family: 'FontKorean'; + src: url('%5') format('truetype'); +} + +/* FontNintendoExtended */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%6') format('truetype'); +} + +/* FontNintendoExtended2 */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%7') format('truetype'); +} +`; + + document.head.appendChild(css); +})(); +)"; + +constexpr char LOAD_NX_FONT[] = R"( +(function() { + var elements = document.querySelectorAll("*"); + + for (var i = 0; i < elements.length; i++) { + var style = window.getComputedStyle(elements[i], null); + if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || + style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { + elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } else { + elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } + } +})(); +)"; + +constexpr char GAMEPAD_SCRIPT[] = R"( +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); +}); + +window.addEventListener("gamepaddisconnected", function(e) { + console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); +}); +)"; + +constexpr char WINDOW_NX_SCRIPT[] = R"( +var end_applet = false; +var yuzu_key_callbacks = []; + +(function() { + class WindowNX { + constructor() { + yuzu_key_callbacks[1] = function() { window.history.back(); }; + yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; + } + + addEventListener(type, listener, options) { + console.log("nx.addEventListener called, type=%s", type); + + window.addEventListener(type, listener, options); + } + + endApplet() { + console.log("nx.endApplet called"); + + end_applet = true; + } + + playSystemSe(system_se) { + console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); + } + + sendMessage(message) { + console.log("nx.sendMessage is not implemented, message=%s", message); + } + + setCursorScrollSpeed(scroll_speed) { + console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); + } + } + + class WindowNXFooter { + setAssign(key, label, func, option) { + console.log("nx.footer.setAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = func; + break; + case "B": + yuzu_key_callbacks[1] = func; + break; + case "X": + yuzu_key_callbacks[2] = func; + break; + case "Y": + yuzu_key_callbacks[3] = func; + break; + case "L": + yuzu_key_callbacks[6] = func; + break; + case "R": + yuzu_key_callbacks[7] = func; + break; + } + } + + setFixed(kind) { + console.log("nx.footer.setFixed is not implemented, kind=%s", kind); + } + + unsetAssign(key) { + console.log("nx.footer.unsetAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = function() {}; + break; + case "B": + yuzu_key_callbacks[1] = function() {}; + break; + case "X": + yuzu_key_callbacks[2] = function() {}; + break; + case "Y": + yuzu_key_callbacks[3] = function() {}; + break; + case "L": + yuzu_key_callbacks[6] = function() {}; + break; + case "R": + yuzu_key_callbacks[7] = function() {}; + break; + } + } + } + + class WindowNXPlayReport { + incrementCounter(counter_id) { + console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); + } + + setCounterSetIdentifier(counter_id) { + console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); + } + } + + window.nx = new WindowNX(); + window.nx.footer = new WindowNXFooter(); + window.nx.playReport = new WindowNXPlayReport(); +})(); +)"; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 32b548c56..1c61d419d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -10,6 +10,7 @@ #include <QMessageBox> #include <QPainter> #include <QScreen> +#include <QString> #include <QStringList> #include <QWindow> @@ -18,7 +19,7 @@ #include <QOpenGLContext> #endif -#if !defined(WIN32) && HAS_VULKAN +#if !defined(WIN32) #include <qpa/qplatformnativeinterface.h> #endif @@ -30,10 +31,11 @@ #include "common/scope_exit.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" +#include "core/hle/kernel/process.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/mouse/mouse_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -62,8 +64,9 @@ void EmuThread::run() { emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - system.Renderer().Rasterizer().LoadDiskResources( - stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + system.Renderer().ReadRasterizer()->LoadDiskResources( + system.CurrentProcess()->GetTitleID(), stop_run, + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); }); @@ -123,7 +126,7 @@ public: /// Create the original context that should be shared from explicit OpenGLSharedContext(QSurface* surface) : surface(surface) { QSurfaceFormat format; - format.setVersion(4, 3); + format.setVersion(4, 6); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); if (Settings::values.renderer_debug) { @@ -216,15 +219,6 @@ public: virtual ~RenderWidget() = default; - /// Called on the UI thread when this Widget is ready to draw - /// Dervied classes can override this to draw the latest frame. - virtual void Present() {} - - void paintEvent(QPaintEvent* event) override { - Present(); - update(); - } - QPaintEngine* paintEngine() const override { return nullptr; } @@ -243,30 +237,16 @@ public: context = std::move(context_); } - void Present() override { - if (!isVisible()) { - return; - } - - context->MakeCurrent(); - if (Core::System::GetInstance().Renderer().TryPresent(100)) { - context->SwapBuffers(); - glFinish(); - } - } - private: - std::unique_ptr<Core::Frontend::GraphicsContext> context{}; + std::unique_ptr<Core::Frontend::GraphicsContext> context; }; -#ifdef HAS_VULKAN class VulkanRenderWidget : public RenderWidget { public: explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { windowHandle()->setSurfaceType(QWindow::VulkanSurface); } }; -#endif static Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. @@ -286,7 +266,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* Core::Frontend::EmuWindow::WindowSystemInfo wsi; wsi.type = GetWindowSystemType(); -#ifdef HAS_VULKAN // Our Win32 Qt external doesn't have the private API. #if defined(WIN32) || defined(__APPLE__) wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; @@ -299,7 +278,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; #endif wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; -#endif return wsi; } @@ -312,21 +290,27 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, QString::fromUtf8(Common::g_scm_branch), QString::fromUtf8(Common::g_scm_desc))); setAttribute(Qt::WA_AcceptTouchEvents); - auto layout = new QHBoxLayout(this); - layout->setMargin(0); + auto* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); input_subsystem->Initialize(); this->setMouseTracking(true); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); + connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram, + Qt::QueuedConnection); +} + +void GRenderWindow::ExecuteProgram(std::size_t program_index) { + emit ExecuteProgramSignal(program_index); } GRenderWindow::~GRenderWindow() { input_subsystem->Shutdown(); } -void GRenderWindow::PollEvents() { +void GRenderWindow::OnFrameDisplayed() { if (!first_frame) { first_frame = true; emit FirstFrameDisplayed(); @@ -400,73 +384,116 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { - // touch input is handled in TouchBeginEvent + // Touch input is handled in TouchBeginEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } auto pos = event->pos(); + const auto [x, y] = ScaleTouch(pos); + input_subsystem->GetMouse()->PressButton(x, y, event->button()); + if (event->button() == Qt::LeftButton) { - const auto [x, y] = ScaleTouch(pos); - this->TouchPressed(x, y); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y()); + this->TouchPressed(x, y, 0); } - QWidget::mousePressEvent(event); + + emit MouseActivity(); } void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { - // touch input is handled in TouchUpdateEvent + // Touch input is handled in TouchUpdateEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } - auto pos = event->pos(); const auto [x, y] = ScaleTouch(pos); - this->TouchMoved(x, y); - input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y()); - QWidget::mouseMoveEvent(event); + const int center_x = width() / 2; + const int center_y = height() / 2; + input_subsystem->GetMouse()->MouseMove(x, y, center_x, center_y); + this->TouchMoved(x, y, 0); + + if (Settings::values.mouse_panning) { + QCursor::setPos(mapToGlobal({center_x, center_y})); + } + + emit MouseActivity(); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { - // touch input is handled in TouchEndEvent + // Touch input is handled in TouchEndEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } + input_subsystem->GetMouse()->ReleaseButton(event->button()); + if (event->button() == Qt::LeftButton) { - this->TouchReleased(); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->EndTilt(); + this->TouchReleased(0); } } void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { - // TouchBegin always has exactly one touch point, so take the .first() - const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); - this->TouchPressed(x, y); + QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints(); + for (const auto& touch_point : touch_points) { + if (!TouchUpdate(touch_point)) { + TouchStart(touch_point); + } + } } void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { - QPointF pos; - int active_points = 0; - - // average all active touch points - for (const auto tp : event->touchPoints()) { - if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { - active_points++; - pos += tp.pos(); + QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints(); + for (const auto& touch_point : touch_points) { + if (!TouchUpdate(touch_point)) { + TouchStart(touch_point); } } + // Release all inactive points + for (std::size_t id = 0; id < touch_ids.size(); ++id) { + if (!TouchExist(touch_ids[id], touch_points)) { + touch_ids[id] = 0; + this->TouchReleased(id + 1); + } + } +} - pos /= active_points; +void GRenderWindow::TouchEndEvent() { + for (std::size_t id = 0; id < touch_ids.size(); ++id) { + if (touch_ids[id] != 0) { + touch_ids[id] = 0; + this->TouchReleased(id + 1); + } + } +} - const auto [x, y] = ScaleTouch(pos); - this->TouchMoved(x, y); +bool GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) { + for (std::size_t id = 0; id < touch_ids.size(); ++id) { + if (touch_ids[id] == 0) { + touch_ids[id] = touch_point.id() + 1; + const auto [x, y] = ScaleTouch(touch_point.pos()); + this->TouchPressed(x, y, id + 1); + return true; + } + } + return false; } -void GRenderWindow::TouchEndEvent() { - this->TouchReleased(); +bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) { + for (std::size_t id = 0; id < touch_ids.size(); ++id) { + if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) { + const auto [x, y] = ScaleTouch(touch_point.pos()); + this->TouchMoved(x, y, id + 1); + return true; + } + } + return false; +} + +bool GRenderWindow::TouchExist(std::size_t id, + const QList<QTouchEvent::TouchPoint>& touch_points) const { + return std::any_of(touch_points.begin(), touch_points.end(), [id](const auto& point) { + return id == static_cast<std::size_t>(point.id() + 1); + }); } bool GRenderWindow::event(QEvent* event) { @@ -579,6 +606,10 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p layout); } +bool GRenderWindow::IsLoadingComplete() const { + return first_frame; +} + void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); } @@ -604,37 +635,45 @@ bool GRenderWindow::InitializeOpenGL() { } bool GRenderWindow::InitializeVulkan() { -#ifdef HAS_VULKAN auto child = new VulkanRenderWidget(this); child_widget = child; child_widget->windowHandle()->create(); main_context = std::make_unique<DummyContext>(); return true; -#else - QMessageBox::critical(this, tr("Vulkan not available!"), - tr("yuzu has not been compiled with Vulkan support.")); - return false; -#endif } bool GRenderWindow::LoadOpenGL() { auto context = CreateSharedContext(); auto scope = context->Acquire(); if (!gladLoadGL()) { - QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"), - tr("Your GPU may not support OpenGL 4.3, or you do not have the " - "latest graphics driver.")); + QMessageBox::warning( + this, tr("Error while initializing OpenGL!"), + tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver.")); + return false; + } + + const QString renderer = + QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER))); + + if (!GLAD_GL_VERSION_4_6) { + LOG_ERROR(Frontend, "GPU does not support OpenGL 4.6: {}", renderer.toStdString()); + QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"), + tr("Your GPU may not support OpenGL 4.6, or you do not have the " + "latest graphics driver.<br><br>GL Renderer:<br>%1") + .arg(renderer)); return false; } QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions(); if (!unsupported_gl_extensions.empty()) { - QMessageBox::critical( + QMessageBox::warning( this, tr("Error while initializing OpenGL!"), tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " - "have the latest graphics driver.<br><br>Unsupported extensions:<br>") + - unsupported_gl_extensions.join(QStringLiteral("<br>"))); + "have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported " + "extensions:<br>%2") + .arg(renderer) + .arg(unsupported_gl_extensions.join(QStringLiteral("<br>")))); return false; } return true; @@ -643,29 +682,21 @@ bool GRenderWindow::LoadOpenGL() { QStringList GRenderWindow::GetUnsupportedGLExtensions() const { QStringList unsupported_ext; - if (!GLAD_GL_ARB_buffer_storage) - unsupported_ext.append(QStringLiteral("ARB_buffer_storage")); - if (!GLAD_GL_ARB_direct_state_access) - unsupported_ext.append(QStringLiteral("ARB_direct_state_access")); - if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) - unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev")); - if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) - unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge")); - if (!GLAD_GL_ARB_multi_bind) - unsupported_ext.append(QStringLiteral("ARB_multi_bind")); - if (!GLAD_GL_ARB_clip_control) - unsupported_ext.append(QStringLiteral("ARB_clip_control")); - // Extensions required to support some texture formats. - if (!GLAD_GL_EXT_texture_compression_s3tc) + if (!GLAD_GL_EXT_texture_compression_s3tc) { unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc")); - if (!GLAD_GL_ARB_texture_compression_rgtc) + } + if (!GLAD_GL_ARB_texture_compression_rgtc) { unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc")); - if (!GLAD_GL_ARB_depth_buffer_float) - unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float")); + } - for (const QString& ext : unsupported_ext) - LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + if (!unsupported_ext.empty()) { + LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", + glGetString(GL_RENDERER)); + } + for (const QString& ext : unsupported_ext) { + LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + } return unsupported_ext; } @@ -685,3 +716,15 @@ void GRenderWindow::showEvent(QShowEvent* event) { connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, Qt::UniqueConnection); } + +bool GRenderWindow::eventFilter(QObject* object, QEvent* event) { + if (event->type() == QEvent::HoverMove) { + if (Settings::values.mouse_panning) { + auto* hover_event = static_cast<QMouseEvent*>(event); + mouseMoveEvent(hover_event); + return false; + } + emit MouseActivity(); + } + return false; +} diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index ca35cf831..b5ec7de07 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -11,6 +11,7 @@ #include <QImage> #include <QThread> +#include <QTouchEvent> #include <QWidget> #include <QWindow> @@ -21,7 +22,6 @@ class GRenderWindow; class GMainWindow; class QKeyEvent; -class QTouchEvent; class QStringList; namespace InputCommon { @@ -131,7 +131,7 @@ public: ~GRenderWindow() override; // EmuWindow implementation. - void PollEvents() override; + void OnFrameDisplayed() override; bool IsShown() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; @@ -162,10 +162,18 @@ public: /// Destroy the previous run's child_widget which should also destroy the child_window void ReleaseRenderTarget(); + bool IsLoadingComplete() const; + void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; + /** + * Instructs the window to re-launch the application using the specified program_index. + * @param program_index Specifies the index within the application of the program to launch. + */ + void ExecuteProgram(std::size_t program_index); + public slots: void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); @@ -175,12 +183,18 @@ signals: /// Emitted when the window is closed void Closed(); void FirstFrameDisplayed(); + void ExecuteProgramSignal(std::size_t program_index); + void MouseActivity(); private: void TouchBeginEvent(const QTouchEvent* event); void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); + bool TouchStart(const QTouchEvent::TouchPoint& touch_point); + bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point); + bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const; + void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; bool InitializeOpenGL(); @@ -205,6 +219,9 @@ private: bool first_frame = false; + std::array<std::size_t, 16> touch_ids{}; + protected: void showEvent(QShowEvent* event) override; + bool eventFilter(QObject* object, QEvent* event) override; }; diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 649912557..a470056ef 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -72,7 +72,7 @@ void CompatDB::Submit() { void CompatDB::OnTestcaseSubmitted() { if (!testcase_watcher.result()) { QMessageBox::critical(this, tr("Communication error"), - tr("An error occured while sending the Testcase")); + tr("An error occurred while sending the Testcase")); button(NextButton)->setEnabled(true); button(NextButton)->setText(tr("Next")); button(CancelButton)->setVisible(true); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 588bbd677..0635d13d0 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,7 +5,9 @@ #include <array> #include <QKeySequence> #include <QSettings> +#include "common/common_paths.h" #include "common/file_util.h" +#include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" @@ -14,14 +16,10 @@ namespace FS = Common::FS; -Config::Config(const std::string& config_file, bool is_global) { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file; - FS::CreateFullPath(qt_config_loc); - qt_config = - std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); - global = is_global; - Reload(); +Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { + global = config_type == ConfigType::GlobalConfig; + + Initialize(config_name); } Config::~Config() { @@ -36,6 +34,11 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, }; +const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = { + Qt::Key_7, + Qt::Key_8, +}; + const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ { Qt::Key_Up, @@ -217,7 +220,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ +const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, @@ -232,73 +235,158 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, + {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, }}; // clang-format on -void Config::ReadPlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - auto& player = Settings::values.players[p]; +void Config::Initialize(const std::string& config_name) { + switch (type) { + case ConfigType::GlobalConfig: + qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), + config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + Reload(); + break; + case ConfigType::PerGameConfig: + qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + Reload(); + break; + case ConfigType::InputProfile: + qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + break; + } +} + +void Config::ReadPlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); + + auto& player = Settings::values.players.GetValue()[player_index]; + + if (player_prefix.isEmpty()) { + const auto controller = static_cast<Settings::ControllerType>( + qt_config + ->value(QStringLiteral("%1type").arg(player_prefix), + static_cast<u8>(Settings::ControllerType::ProController)) + .toUInt()); + if (controller == Settings::ControllerType::LeftJoycon || + controller == Settings::ControllerType::RightJoycon) { + player.controller_type = controller; + } + } else { player.connected = - ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); + ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0) + .toBool(); player.controller_type = static_cast<Settings::ControllerType>( qt_config - ->value(QStringLiteral("player_%1_type").arg(p), + ->value(QStringLiteral("%1type").arg(player_prefix), static_cast<u8>(Settings::ControllerType::ProController)) .toUInt()); + player.vibration_enabled = + qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true) + .toBool(); + + player.vibration_strength = + qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100) + .toInt(); + player.body_color_left = qt_config - ->value(QStringLiteral("player_%1_body_color_left").arg(p), + ->value(QStringLiteral("%1body_color_left").arg(player_prefix), Settings::JOYCON_BODY_NEON_BLUE) .toUInt(); - player.body_color_right = qt_config - ->value(QStringLiteral("player_%1_body_color_right").arg(p), - Settings::JOYCON_BODY_NEON_RED) - .toUInt(); - player.button_color_left = qt_config - ->value(QStringLiteral("player_%1_button_color_left").arg(p), - Settings::JOYCON_BUTTONS_NEON_BLUE) - .toUInt(); + player.body_color_right = + qt_config + ->value(QStringLiteral("%1body_color_right").arg(player_prefix), + Settings::JOYCON_BODY_NEON_RED) + .toUInt(); + player.button_color_left = + qt_config + ->value(QStringLiteral("%1button_color_left").arg(player_prefix), + Settings::JOYCON_BUTTONS_NEON_BLUE) + .toUInt(); player.button_color_right = qt_config - ->value(QStringLiteral("player_%1_button_color_right").arg(p), + ->value(QStringLiteral("%1button_color_right").arg(player_prefix), Settings::JOYCON_BUTTONS_NEON_RED) .toUInt(); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - auto& player_buttons = player.buttons[i]; - - player_buttons = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeButton::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_buttons.empty()) { - player_buttons = default_param; - } + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; + + player_buttons = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeButton::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_buttons.empty()) { + player_buttons = default_param; + } + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; + + player_analogs = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_analogs.empty()) { + player_analogs = default_param; } + } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - auto& player_analogs = player.analogs[i]; - - player_analogs = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_analogs.empty()) { - player_analogs = default_param; - } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + auto& player_vibrations = player.vibrations[i]; + + player_vibrations = + qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeVibration::mapping[i]), + QString{}) + .toString() + .toStdString(); + } + + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; } } } @@ -377,13 +465,7 @@ void Config::ReadMouseValues() { void Config::ReadTouchscreenValues() { Settings::values.touchscreen.enabled = ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool(); - Settings::values.touchscreen.device = - ReadSetting(QStringLiteral("touchscreen_device"), QStringLiteral("engine:emu_window")) - .toString() - .toStdString(); - Settings::values.touchscreen.finger = - ReadSetting(QStringLiteral("touchscreen_finger"), 0).toUInt(); Settings::values.touchscreen.rotation_angle = ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt(); Settings::values.touchscreen.diameter_x = @@ -415,39 +497,91 @@ void Config::ReadAudioValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - ReadPlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + ReadPlayerValue(p); + } ReadDebugValues(); ReadKeyboardValues(); ReadMouseValues(); ReadTouchscreenValues(); + ReadMotionTouchValues(); + + Settings::values.emulate_analog_keyboard = + ReadSetting(QStringLiteral("emulate_analog_keyboard"), false).toBool(); + Settings::values.mouse_panning = ReadSetting(QStringLiteral("mouse_panning"), false).toBool(); + Settings::values.mouse_panning_sensitivity = + ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat(); + + ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true); + ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), + true); + ReadSettingGlobal(Settings::values.enable_accurate_vibrations, + QStringLiteral("enable_accurate_vibrations"), false); + ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true); + + qt_config->endGroup(); +} + +void Config::ReadMotionTouchValues() { + int num_touch_from_button_maps = + qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); + + if (num_touch_from_button_maps > 0) { + const auto append_touch_from_button_map = [this] { + Settings::TouchFromButtonMap map; + map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default")) + .toString() + .toStdString(); + const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries")); + map.buttons.reserve(num_touch_maps); + for (int i = 0; i < num_touch_maps; i++) { + qt_config->setArrayIndex(i); + std::string touch_mapping = + ReadSetting(QStringLiteral("bind")).toString().toStdString(); + map.buttons.emplace_back(std::move(touch_mapping)); + } + qt_config->endArray(); // entries + Settings::values.touch_from_button_maps.emplace_back(std::move(map)); + }; + + for (int i = 0; i < num_touch_from_button_maps; ++i) { + qt_config->setArrayIndex(i); + append_touch_from_button_map(); + } + } else { + Settings::values.touch_from_button_maps.emplace_back( + Settings::TouchFromButtonMap{"default", {}}); + num_touch_from_button_maps = 1; + } + qt_config->endArray(); - Settings::values.vibration_enabled = - ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); Settings::values.motion_device = ReadSetting(QStringLiteral("motion_device"), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) .toString() .toStdString(); - Settings::values.udp_input_address = - ReadSetting(QStringLiteral("udp_input_address"), - QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) + Settings::values.touch_device = + ReadSetting(QStringLiteral("touch_device"), + QStringLiteral("min_x:100,min_y:50,max_x:1800,max_y:850")) + .toString() + .toStdString(); + Settings::values.use_touch_from_button = + ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool(); + Settings::values.touch_from_button_map_index = + ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt(); + Settings::values.touch_from_button_map_index = + std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1); + Settings::values.udp_input_servers = + ReadSetting(QStringLiteral("udp_input_servers"), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV)) .toString() .toStdString(); - Settings::values.udp_input_port = static_cast<u16>( - ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT) - .toInt()); - Settings::values.udp_pad_index = - static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); - Settings::values.use_docked_mode = - ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); - - qt_config->endGroup(); } void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false); + ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), true); qt_config->endGroup(); } @@ -502,8 +636,6 @@ void Config::ReadDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini Settings::values.record_frame_times = qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); - Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); - Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); @@ -513,6 +645,8 @@ void Config::ReadDebuggingValues() { Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); Settings::values.disable_macro_jit = ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool(); + Settings::values.extended_logging = + ReadSetting(QStringLiteral("extended_logging"), false).toBool(); qt_config->endGroup(); } @@ -629,6 +763,8 @@ void Config::ReadCpuValues() { ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); Settings::values.cpuopt_unsafe_reduce_fp_error = ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); + Settings::values.cpuopt_unsafe_inaccurate_nan = + ReadSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true).toBool(); } qt_config->endGroup(); @@ -646,9 +782,11 @@ void Config::ReadRendererValues() { ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100); ReadSettingGlobal(Settings::values.use_disk_shader_cache, QStringLiteral("use_disk_shader_cache"), true); - ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0); + ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 1); ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation, - QStringLiteral("use_asynchronous_gpu_emulation"), false); + QStringLiteral("use_asynchronous_gpu_emulation"), true); + ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"), + true); ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), false); @@ -850,41 +988,65 @@ void Config::ReadValues() { ReadSystemValues(); } -void Config::SavePlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - const auto& player = Settings::values.players[p]; - - WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); - WriteSetting(QStringLiteral("player_%1_type").arg(p), - static_cast<u8>(player.controller_type), - static_cast<u8>(Settings::ControllerType::ProController)); - - WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, +void Config::SavePlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); + + const auto& player = Settings::values.players.GetValue()[player_index]; + + WriteSetting(QStringLiteral("%1type").arg(player_prefix), + static_cast<u8>(player.controller_type), + static_cast<u8>(Settings::ControllerType::ProController)); + + if (!player_prefix.isEmpty()) { + WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, + player_index == 0); + WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix), + player.vibration_enabled, true); + WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix), + player.vibration_strength, 100); + WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left, Settings::JOYCON_BODY_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right, - Settings::JOYCON_BODY_NEON_RED); - WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left, - Settings::JOYCON_BUTTONS_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), + WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix), + player.body_color_right, Settings::JOYCON_BODY_NEON_RED); + WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix), + player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE); + WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix), player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(player.buttons[i]), - QString::fromStdString(default_param)); - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(player.analogs[i]), - QString::fromStdString(default_param)); - } + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(player.buttons[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(player.analogs[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeVibration::mapping[i]), + QString::fromStdString(player.vibrations[i]), QString{}); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); } } @@ -925,15 +1087,46 @@ void Config::SaveTouchscreenValues() { const auto& touchscreen = Settings::values.touchscreen; WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true); - WriteSetting(QStringLiteral("touchscreen_device"), QString::fromStdString(touchscreen.device), - QStringLiteral("engine:emu_window")); - WriteSetting(QStringLiteral("touchscreen_finger"), touchscreen.finger, 0); WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0); WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15); WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); } +void Config::SaveMotionTouchValues() { + WriteSetting(QStringLiteral("motion_device"), + QString::fromStdString(Settings::values.motion_device), + QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); + WriteSetting(QStringLiteral("touch_device"), + QString::fromStdString(Settings::values.touch_device), + QStringLiteral("engine:emu_window")); + WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button, + false); + WriteSetting(QStringLiteral("touch_from_button_map"), + Settings::values.touch_from_button_map_index, 0); + WriteSetting(QStringLiteral("udp_input_servers"), + QString::fromStdString(Settings::values.udp_input_servers), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV)); + + qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps")); + for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { + qt_config->setArrayIndex(static_cast<int>(p)); + WriteSetting(QStringLiteral("name"), + QString::fromStdString(Settings::values.touch_from_button_maps[p].name), + QStringLiteral("default")); + qt_config->beginWriteArray(QStringLiteral("entries")); + for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); + ++q) { + qt_config->setArrayIndex(static_cast<int>(q)); + WriteSetting( + QStringLiteral("bind"), + QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q])); + } + qt_config->endArray(); + } + qt_config->endArray(); +} + void Config::SaveValues() { if (global) { SaveControlValues(); @@ -972,31 +1165,39 @@ void Config::SaveAudioValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - SavePlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + SavePlayerValue(p); + } SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); + SaveMotionTouchValues(); - WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); + WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, true); + WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, + true); + WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"), + Settings::values.enable_accurate_vibrations, false); + WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); WriteSetting(QStringLiteral("motion_device"), QString::fromStdString(Settings::values.motion_device), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); + WriteSetting(QStringLiteral("touch_device"), + QString::fromStdString(Settings::values.touch_device), + QStringLiteral("engine:emu_window")); WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); - WriteSetting(QStringLiteral("udp_input_address"), - QString::fromStdString(Settings::values.udp_input_address), - QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); - WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, - InputCommon::CemuhookUDP::DEFAULT_PORT); - WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); - WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); - + WriteSetting(QStringLiteral("emulate_analog_keyboard"), + Settings::values.emulate_analog_keyboard, false); + WriteSetting(QStringLiteral("mouse_panning"), Settings::values.mouse_panning, false); + WriteSetting(QStringLiteral("mouse_panning_sensitivity"), + Settings::values.mouse_panning_sensitivity, 1.0f); qt_config->endGroup(); } void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); + WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, true); qt_config->endGroup(); } @@ -1034,8 +1235,6 @@ void Config::SaveDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); - WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); - WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), QString::fromStdString(Settings::values.program_args), QString{}); WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); @@ -1129,6 +1328,8 @@ void Config::SaveCpuValues() { Settings::values.cpuopt_unsafe_unfuse_fma, true); WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), Settings::values.cpuopt_unsafe_reduce_fp_error, true); + WriteSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), + Settings::values.cpuopt_unsafe_inaccurate_nan, true); } qt_config->endGroup(); @@ -1150,9 +1351,11 @@ void Config::SaveRendererValues() { Settings::values.use_disk_shader_cache, true); WriteSettingGlobal(QStringLiteral("gpu_accuracy"), static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)), - Settings::values.gpu_accuracy.UsingGlobal(), 0); + Settings::values.gpu_accuracy.UsingGlobal(), 1); WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"), - Settings::values.use_asynchronous_gpu_emulation, false); + Settings::values.use_asynchronous_gpu_emulation, true); + WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation, + true); WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, false); @@ -1389,13 +1592,27 @@ void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool void Config::Reload() { ReadValues(); - Settings::Sanitize(); // To apply default value changes SaveValues(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } void Config::Save() { - Settings::Sanitize(); SaveValues(); } + +void Config::ReadControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + ReadPlayerValue(player_index); + qt_config->endGroup(); +} + +void Config::SaveControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + SavePlayerValue(player_index); + qt_config->endGroup(); +} + +const std::string& Config::GetConfigFilePath() const { + return qt_config_loc; +} diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index aa929d134..949c4eb13 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -16,28 +16,44 @@ class QSettings; class Config { public: - explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true); + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_name = "qt-config", + ConfigType config_type = ConfigType::GlobalConfig); ~Config(); void Reload(); void Save(); + void ReadControlPlayerValue(std::size_t player_index); + void SaveControlPlayerValue(std::size_t player_index); + + const std::string& GetConfigFilePath() const; + static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; + static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<int, 2> default_stick_mod; static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; - static const std::array<UISettings::Shortcut, 16> default_hotkeys; + static const std::array<UISettings::Shortcut, 17> default_hotkeys; private: + void Initialize(const std::string& config_name); + void ReadValues(); - void ReadPlayerValues(); + void ReadPlayerValue(std::size_t player_index); void ReadDebugValues(); void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); + void ReadMotionTouchValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -60,10 +76,11 @@ private: void ReadWebServiceValues(); void SaveValues(); - void SavePlayerValues(); + void SavePlayerValue(std::size_t player_index); void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); + void SaveMotionTouchValues(); // Save functions based off the respective config section names. void SaveAudioValues(); @@ -108,9 +125,9 @@ private: void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, const QVariant& default_value); + ConfigType type; std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; - bool global; }; diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index fcf42cdcb..f92c3aff3 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -275,32 +275,12 @@ <signal>accepted()</signal> <receiver>ConfigureDialog</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>220</x> - <y>380</y> - </hint> - <hint type="destinationlabel"> - <x>220</x> - <y>200</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureDialog</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>220</x> - <y>380</y> - </hint> - <hint type="destinationlabel"> - <x>220</x> - <y>200</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index fa9124ecf..db9518798 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -25,8 +25,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); - ui->volume_label->setVisible(Settings::configuring_global); - ui->volume_combo_box->setVisible(!Settings::configuring_global); + ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); + ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); SetupPerGameUI(); @@ -51,7 +51,7 @@ void ConfigureAudio::SetConfiguration() { ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); - if (!Settings::configuring_global) { + if (!Settings::IsConfiguringGlobal()) { if (Settings::values.volume.UsingGlobal()) { ui->volume_combo_box->setCurrentIndex(0); ui->volume_slider->setEnabled(false); @@ -99,7 +99,7 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); @@ -165,7 +165,7 @@ void ConfigureAudio::RetranslateUI() { } void ConfigureAudio::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); ui->toggle_audio_stretching->setEnabled( Settings::values.enable_audio_stretching.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 37fcd6adc..d055cbd60 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -36,6 +36,8 @@ void ConfigureCpu::SetConfiguration() { ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma); ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error); + ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); + ui->cpuopt_unsafe_inaccurate_nan->setChecked(Settings::values.cpuopt_unsafe_inaccurate_nan); } void ConfigureCpu::AccuracyUpdated(int index) { @@ -61,6 +63,7 @@ void ConfigureCpu::ApplyConfiguration() { static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked(); Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked(); + Settings::values.cpuopt_unsafe_inaccurate_nan = ui->cpuopt_unsafe_inaccurate_nan->isChecked(); } void ConfigureCpu::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index ebdd2e6e9..bcd0962e9 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -109,6 +109,18 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> + <property name="text"> + <string>Inaccurate NaN handling</string> + </property> + <property name="toolTip"> + <string> + <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> + </string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 2bfe2c306..121873f95 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -28,9 +28,6 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::SetConfiguration() { - ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); @@ -41,11 +38,10 @@ void ConfigureDebug::SetConfiguration() { ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); + ui->extended_logging->setChecked(Settings::values.extended_logging); } void ConfigureDebug::ApplyConfiguration() { - Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); - Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); @@ -53,6 +49,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); + Settings::values.extended_logging = ui->extended_logging->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 9d6feb9f7..9186aa732 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>400</width> - <height>467</height> + <height>486</height> </rect> </property> <property name="windowTitle"> @@ -15,57 +15,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_1"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>GDB</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_1"> - <item> - <widget class="QCheckBox" name="toggle_gdbstub"> - <property name="text"> - <string>Enable GDB Stub</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>Port:</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="gdbport_spinbox"> - <property name="maximum"> - <number>65536</number> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> <string>Logging</string> @@ -90,7 +39,7 @@ <item> <widget class="QCheckBox" name="toggle_console"> <property name="text"> - <string>Show Log Console (Windows Only)</string> + <string>Show Log in Console</string> </property> </widget> </item> @@ -103,6 +52,34 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="extended_logging"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> + </property> + <property name="text"> + <string>Enable Extended Logging</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>This will be reset automatically when yuzu closes.</string> + </property> + <property name="indent"> + <number>20</number> + </property> + </widget> + </item> </layout> </widget> </item> @@ -115,7 +92,7 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="label_4"> <property name="text"> <string>Arguments String</string> </property> @@ -140,8 +117,8 @@ <property name="enabled"> <bool>true</bool> </property> - <property name="whatsThis"> - <string>When checked, the graphics API enters in a slower debugging mode</string> + <property name="toolTip"> + <string>When checked, the graphics API enters a slower debugging mode</string> </property> <property name="text"> <string>Enable Graphics Debugging</string> @@ -153,8 +130,8 @@ <property name="enabled"> <bool>true</bool> </property> - <property name="whatsThis"> - <string>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</string> + <property name="toolTip"> + <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string> </property> <property name="text"> <string>Disable Macro JIT</string> @@ -169,7 +146,7 @@ <property name="title"> <string>Dump</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <layout class="QVBoxLayout" name="verticalLayout_7"> <item> <widget class="QCheckBox" name="reporting_services"> <property name="text"> @@ -178,7 +155,7 @@ </widget> </item> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="label_5"> <property name="font"> <font> <italic>true</italic> @@ -200,7 +177,7 @@ <property name="title"> <string>Advanced</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_7"> + <layout class="QVBoxLayout" name="verticalLayout_8"> <item> <widget class="QCheckBox" name="quest_flag"> <property name="text"> @@ -230,8 +207,6 @@ </layout> </widget> <tabstops> - <tabstop>toggle_gdbstub</tabstop> - <tabstop>gdbport_spinbox</tabstop> <tabstop>log_filter_edit</tabstop> <tabstop>toggle_console</tabstop> <tabstop>open_log_button</tabstop> @@ -241,22 +216,5 @@ <tabstop>quest_flag</tabstop> </tabstops> <resources/> - <connections> - <connection> - <sender>toggle_gdbstub</sender> - <signal>toggled(bool)</signal> - <receiver>gdbport_spinbox</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>84</x> - <y>157</y> - </hint> - <hint type="destinationlabel"> - <x>342</x> - <y>158</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp index 0097c9a29..a878ef9c6 100644 --- a/src/yuzu/configuration/configure_debug_controller.cpp +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -4,11 +4,14 @@ #include "ui_configure_debug_controller.h" #include "yuzu/configuration/configure_debug_controller.h" +#include "yuzu/configuration/configure_input_player.h" ConfigureDebugController::ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem) + InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles) : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), - debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) { + debug_controller( + new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) { ui->setupUi(this); ui->controllerLayout->addWidget(debug_controller); diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h index 34dcf705f..b4f53fad5 100644 --- a/src/yuzu/configuration/configure_debug_controller.h +++ b/src/yuzu/configuration/configure_debug_controller.h @@ -6,10 +6,13 @@ #include <memory> #include <QDialog> -#include "yuzu/configuration/configure_input_player.h" class QPushButton; +class ConfigureInputPlayer; + +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -22,8 +25,8 @@ class ConfigureDebugController : public QDialog { Q_OBJECT public: - explicit ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem); + explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles); ~ConfigureDebugController() override; void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui index a95ed50ff..7b7e6582c 100644 --- a/src/yuzu/configuration/configure_debug_controller.ui +++ b/src/yuzu/configuration/configure_debug_controller.ui @@ -66,32 +66,12 @@ <signal>accepted()</signal> <receiver>ConfigureDebugController</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureDebugController</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 8186929a6..d6b17a28d 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -5,6 +5,7 @@ #include <QHash> #include <QListWidgetItem> #include <QSignalBlocker> +#include "core/core.h" #include "core/settings.h" #include "ui_configure.h" #include "yuzu/configuration/config.h" @@ -15,7 +16,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, InputCommon::InputSubsystem* input_subsystem) : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { - Settings::configuring_global = true; + Settings::SetConfiguringGlobal(true); ui->setupUi(this); ui->hotkeysTab->Populate(registry); @@ -54,7 +55,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->debugTab->ApplyConfiguration(); ui->webTab->ApplyConfiguration(); ui->serviceTab->ApplyConfiguration(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); Settings::LogSettings(); } @@ -116,31 +117,13 @@ void ConfigureDialog::UpdateVisibleTabs() { return; } - const std::map<QWidget*, QString> widgets = { - {ui->generalTab, tr("General")}, - {ui->systemTab, tr("System")}, - {ui->profileManagerTab, tr("Profiles")}, - {ui->inputTab, tr("Controls")}, - {ui->hotkeysTab, tr("Hotkeys")}, - {ui->cpuTab, tr("CPU")}, - {ui->cpuDebugTab, tr("Debug")}, - {ui->graphicsTab, tr("Graphics")}, - {ui->graphicsAdvancedTab, tr("Advanced")}, - {ui->audioTab, tr("Audio")}, - {ui->debugTab, tr("Debug")}, - {ui->webTab, tr("Web")}, - {ui->uiTab, tr("UI")}, - {ui->filesystemTab, tr("Filesystem")}, - {ui->serviceTab, tr("Services")}, - }; - [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); ui->tabWidget->clear(); - const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); + const auto tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); - for (const auto tab : tabs) { + for (auto* const tab : tabs) { ui->tabWidget->addTab(tab, tab->accessibleName()); } } diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 830096ea0..d4d29d422 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -19,7 +19,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) SetConfiguration(); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); } @@ -41,7 +41,7 @@ void ConfigureGeneral::SetConfiguration() { ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); } else { ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() && @@ -50,7 +50,7 @@ void ConfigureGeneral::SetConfiguration() { } void ConfigureGeneral::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); @@ -93,7 +93,7 @@ void ConfigureGeneral::RetranslateUI() { } void ConfigureGeneral::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 07d818548..9ff32aec4 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -2,24 +2,23 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +// Include this early to include Vulkan headers how we want to +#include "video_core/vulkan_common/vulkan_wrapper.h" + #include <QColorDialog> #include <QComboBox> -#ifdef HAS_VULKAN #include <QVulkanInstance> -#endif #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_library.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" -#ifdef HAS_VULKAN -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#endif - ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { vulkan_device = Settings::values.vulkan_device.GetValue(); @@ -33,7 +32,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateDeviceComboBox(); - if (!Settings::configuring_global) { + if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight( ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); } @@ -49,8 +48,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) UpdateBackgroundColorButton(new_bg_color); }); - ui->bg_label->setVisible(Settings::configuring_global); - ui->bg_combobox->setVisible(!Settings::configuring_global); + ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); + ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); } void ConfigureGraphics::UpdateDeviceSelection(int device) { @@ -70,11 +69,13 @@ void ConfigureGraphics::SetConfiguration() { ui->api->setEnabled(runtime_lock); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); ui->use_disk_shader_cache->setEnabled(runtime_lock); + ui->use_nvdec_emulation->setEnabled(runtime_lock); ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); ui->use_asynchronous_gpu_emulation->setChecked( Settings::values.use_asynchronous_gpu_emulation.GetValue()); + ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); } else { @@ -98,7 +99,7 @@ void ConfigureGraphics::SetConfiguration() { } void ConfigureGraphics::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value if (Settings::values.renderer_backend.UsingGlobal()) { Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); @@ -116,6 +117,9 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.use_asynchronous_gpu_emulation.SetValue( ui->use_asynchronous_gpu_emulation->isChecked()); } + if (Settings::values.use_nvdec_emulation.UsingGlobal()) { + Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked()); + } if (Settings::values.bg_red.UsingGlobal()) { Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); @@ -144,6 +148,8 @@ void ConfigureGraphics::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, ui->use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, + ui->use_nvdec_emulation, use_nvdec_emulation); if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.bg_red.SetGlobal(true); @@ -187,7 +193,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() { bool enabled = false; - if (!Settings::configuring_global && + if (!Settings::IsConfiguringGlobal() && ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { vulkan_device = Settings::values.vulkan_device.GetValue(); } @@ -205,22 +211,32 @@ void ConfigureGraphics::UpdateDeviceComboBox() { break; } // If in per-game config and use global is selected, don't enable. - enabled &= !(!Settings::configuring_global && + enabled &= !(!Settings::IsConfiguringGlobal() && ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX); ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn()); } -void ConfigureGraphics::RetrieveVulkanDevices() { -#ifdef HAS_VULKAN +void ConfigureGraphics::RetrieveVulkanDevices() try { + using namespace Vulkan; + + vk::InstanceDispatch dld; + const Common::DynamicLibrary library = OpenLibrary(); + const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_0); + const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); + vulkan_devices.clear(); - for (auto& name : Vulkan::RendererVulkan::EnumerateDevices()) { + vulkan_devices.reserve(physical_devices.size()); + for (const VkPhysicalDevice device : physical_devices) { + const char* const name = vk::PhysicalDevice(device, dld).GetProperties().deviceName; vulkan_devices.push_back(QString::fromStdString(name)); } -#endif + +} catch (const Vulkan::vk::Exception& exception) { + LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); } @@ -234,12 +250,13 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { } void ConfigureGraphics::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); ui->use_asynchronous_gpu_emulation->setEnabled( Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); + ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); @@ -253,6 +270,8 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredTristate( ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); + ConfigurationShared::SetColoredTristate( + ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, Settings::values.use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index b4961f719..1fefc88eb 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -46,6 +46,7 @@ private: std::unique_ptr<Ui::ConfigureGraphics> ui; QColor bg_color; + ConfigurationShared::CheckState use_nvdec_emulation; ConfigurationShared::CheckState use_disk_shader_cache; ConfigurationShared::CheckState use_asynchronous_gpu_emulation; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 62aa337e7..58486eb1e 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -98,6 +98,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_nvdec_emulation"> + <property name="text"> + <string>Use NVDEC emulation</string> + </property> + </widget> + </item> + <item> <widget class="QWidget" name="aspect_ratio_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_6"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 73f276949..383c7bac8 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -32,7 +32,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( static_cast<int>(Settings::values.gpu_accuracy.GetValue())); ui->anisotropic_filtering_combobox->setCurrentIndex( @@ -52,9 +52,9 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( ui->gpu_accuracy->currentIndex() - - ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Must guard in case of a during-game configuration when set to be game-specific. if (Settings::values.gpu_accuracy.UsingGlobal()) { Settings::values.gpu_accuracy.SetValue(gpu_accuracy); @@ -118,7 +118,7 @@ void ConfigureGraphicsAdvanced::RetranslateUI() { void ConfigureGraphicsAdvanced::SetupPerGameUI() { // Disable if not global (only happens during game) - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 5223eed1d..422022d02 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <memory> +#include <thread> #include <QSignalBlocker> #include <QTimer> @@ -20,8 +21,11 @@ #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_input_advanced.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" namespace { template <typename Dialog, typename... Args> @@ -63,22 +67,32 @@ void OnDockedModeChanged(bool last_state, bool new_state) { } ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), + profiles(std::make_unique<InputProfiles>()) { ui->setupUi(this); } ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, + std::size_t max_players) { player_controllers = { - new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem), + new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, + profiles.get()), }; player_tabs = { @@ -92,6 +106,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + std::array<QLabel*, 8> player_connected_labels = { + ui->label, ui->label_3, ui->label_4, ui->label_5, + ui->label_6, ui->label_7, ui->label_8, ui->label_9, + }; + for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); @@ -106,11 +125,20 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { } } }); - connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, - [this] { UpdateAllInputDevices(); }); + connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, + &ConfigureInput::UpdateAllInputDevices); + connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, + &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); + + // Remove/hide all the elements that exceed max_players, if applicable. + if (i >= max_players) { + ui->tabWidget->removeTab(static_cast<int>(max_players)); + player_connected[i]->hide(); + player_connected_labels[i]->hide(); + } } // Only the first player can choose handheld mode so connect the signal just to player 1 connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged, @@ -120,13 +148,24 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); ui->tabAdvanced->layout()->addWidget(advanced); connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { - CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem); + CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get()); }); connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem); }); connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); + connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog, + [this, input_subsystem] { + CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); + }); + + connect(ui->vibrationButton, &QPushButton::clicked, + [this] { CallConfigureDialog<ConfigureVibration>(*this); }); + + connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { + CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); + }); connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); @@ -143,17 +182,32 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const { } void ConfigureInput::ApplyConfiguration() { - for (auto controller : player_controllers) { + for (auto* controller : player_controllers) { controller->ApplyConfiguration(); + controller->TryDisconnectSelectedController(); + } + + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + using namespace std::chrono_literals; + std::this_thread::sleep_for(150ms); + + for (auto* controller : player_controllers) { + controller->TryConnectSelectedController(); } + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + std::this_thread::sleep_for(150ms); + advanced->ApplyConfiguration(); - const bool pre_docked_mode = Settings::values.use_docked_mode; - Settings::values.use_docked_mode = ui->radioDocked->isChecked(); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); + Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); - Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); + Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); + Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); } void ConfigureInput::changeEvent(QEvent* event) { @@ -170,16 +224,16 @@ void ConfigureInput::RetranslateUI() { void ConfigureInput::LoadConfiguration() { LoadPlayerControllerIndices(); - UpdateDockedState(Settings::values.players[0].controller_type == - Settings::ControllerType::Handheld); + UpdateDockedState(Settings::values.players.GetValue()[8].connected); - ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); + ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); } void ConfigureInput::LoadPlayerControllerIndices() { for (std::size_t i = 0; i < player_connected.size(); ++i) { - const auto connected = Settings::values.players[i].connected || - (i == 0 && Settings::values.players[8].connected); + const auto connected = Settings::values.players.GetValue()[i].connected || + (i == 0 && Settings::values.players.GetValue()[8].connected); player_connected[i]->setChecked(connected); } } @@ -200,17 +254,18 @@ void ConfigureInput::RestoreDefaults() { ui->radioDocked->setChecked(true); ui->radioUndocked->setChecked(false); ui->vibrationGroup->setChecked(true); + ui->motionGroup->setChecked(true); } void ConfigureInput::UpdateDockedState(bool is_handheld) { - // If the controller type is handheld only, disallow changing docked mode + // Disallow changing the console mode if the controller type is handheld. ui->radioDocked->setEnabled(!is_handheld); ui->radioUndocked->setEnabled(!is_handheld); - ui->radioDocked->setChecked(Settings::values.use_docked_mode); - ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); + ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); - // If its handheld only, force docked mode off (since you can't play handheld in a dock) + // Also force into undocked mode if the controller type is handheld. if (is_handheld) { ui->radioUndocked->setChecked(true); } @@ -218,6 +273,16 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) { void ConfigureInput::UpdateAllInputDevices() { for (const auto& player : player_controllers) { - player->UpdateInputDevices(); + player->UpdateInputDeviceCombobox(); + } +} + +void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) { + for (std::size_t i = 0; i < player_controllers.size(); ++i) { + if (i == player_index) { + continue; + } + + player_controllers[i]->UpdateInputProfiles(); } } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index d08a24f96..f4eb0d78b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -8,17 +8,18 @@ #include <memory> #include <QKeyEvent> +#include <QList> #include <QWidget> -#include "yuzu/configuration/configure_input_advanced.h" -#include "yuzu/configuration/configure_input_player.h" - -#include "ui_configure_input.h" - class QCheckBox; class QString; class QTimer; +class ConfigureInputAdvanced; +class ConfigureInputPlayer; + +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -37,7 +38,7 @@ public: ~ConfigureInput() override; /// Initializes the input dialog with the given input subsystem. - void Initialize(InputCommon::InputSubsystem* input_subsystem_); + void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); /// Save all button configurations to settings file. void ApplyConfiguration(); @@ -51,6 +52,7 @@ private: void UpdateDockedState(bool is_handheld); void UpdateAllInputDevices(); + void UpdateAllInputProfiles(std::size_t player_index); /// Load configuration settings. void LoadConfiguration(); @@ -61,6 +63,8 @@ private: std::unique_ptr<Ui::ConfigureInput> ui; + std::unique_ptr<InputProfiles> profiles; + std::array<ConfigureInputPlayer*, 8> player_controllers; std::array<QWidget*, 8> player_tabs; std::array<QCheckBox*, 8> player_connected; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 136955224..2707025e7 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>700</width> + <width>680</width> <height>540</height> </rect> </property> @@ -142,7 +142,7 @@ <number>6</number> </property> <property name="leftMargin"> - <number>3</number> + <number>8</number> </property> <property name="topMargin"> <number>6</number> @@ -195,30 +195,24 @@ <number>3</number> </property> <item> - <widget class="QSpinBox" name="vibrationSpin"> + <widget class="QPushButton" name="vibrationButton"> <property name="minimumSize"> <size> - <width>65</width> - <height>21</height> + <width>68</width> + <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>65</width> + <width>68</width> <height>16777215</height> </size> </property> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>200</number> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> </property> - <property name="value"> - <number>100</number> + <property name="text"> + <string>Configure</string> </property> </widget> </item> @@ -250,18 +244,18 @@ <widget class="QPushButton" name="motionButton"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Configure</string> @@ -272,7 +266,7 @@ </widget> </item> <item alignment="Qt::AlignVCenter"> - <widget class="QWidget" name="widget" native="true"> + <widget class="QWidget" name="connectedControllers" native="true"> <layout class="QGridLayout" name="gridLayout_2"> <property name="leftMargin"> <number>5</number> @@ -468,13 +462,13 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -494,7 +488,7 @@ <enum>Qt::LeftToRight</enum> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Defaults</string> @@ -511,13 +505,13 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -537,7 +531,7 @@ <enum>Qt::LeftToRight</enum> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Clear</string> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index db42b826b..a1a0eb676 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -68,8 +68,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) { connect(color_buttons[button_idx], &QPushButton::clicked, this, [this, player_idx, button_idx] { - OnControllerButtonClick(static_cast<int>(player_idx), - static_cast<int>(button_idx)); + OnControllerButtonClick(player_idx, button_idx); }); } } @@ -86,26 +85,29 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); }); connect(ui->touchscreen_advanced, &QPushButton::clicked, this, [this] { CallTouchscreenConfigDialog(); }); + connect(ui->buttonMotionTouch, &QPushButton::clicked, this, + &ConfigureInputAdvanced::CallMotionTouchConfigDialog); LoadConfiguration(); } ConfigureInputAdvanced::~ConfigureInputAdvanced() = default; -void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) { +void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx, + std::size_t button_idx) { const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]); if (!new_bg_color.isValid()) { return; } controllers_colors[player_idx][button_idx] = new_bg_color; controllers_color_buttons[player_idx][button_idx]->setStyleSheet( - QStringLiteral("background-color: %1; min-width: 55px;") + QStringLiteral("background-color: %1; min-width: 60px;") .arg(controllers_colors[player_idx][button_idx].name())); } void ConfigureInputAdvanced::ApplyConfiguration() { for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { - auto& player = Settings::values.players[player_idx]; + auto& player = Settings::values.players.GetValue()[player_idx]; std::array<u32, 4> colors{}; std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(), colors.begin(), [](QColor color) { return color.rgb(); }); @@ -119,12 +121,16 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); + Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); + Settings::values.mouse_panning = ui->mouse_panning->isChecked(); + Settings::values.mouse_panning_sensitivity = + static_cast<float>(ui->mouse_panning_sensitivity->value()); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { - auto& player = Settings::values.players[player_idx]; + auto& player = Settings::values.players.GetValue()[player_idx]; std::array<u32, 4> colors = { player.body_color_left, player.button_color_left, @@ -137,7 +143,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) { controllers_color_buttons[player_idx][button_idx]->setStyleSheet( - QStringLiteral("background-color: %1; min-width: 55px;") + QStringLiteral("background-color: %1; min-width: 60px;") .arg(controllers_colors[player_idx][button_idx].name())); } } @@ -145,6 +151,9 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); + ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard); + ui->mouse_panning->setChecked(Settings::values.mouse_panning); + ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); UpdateUIEnabled(); diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index d8fcec52d..3083d55c1 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -28,13 +28,14 @@ signals: void CallDebugControllerDialog(); void CallMouseConfigDialog(); void CallTouchscreenConfigDialog(); + void CallMotionTouchConfigDialog(); private: void changeEvent(QEvent* event) override; void RetranslateUI(); void UpdateUIEnabled(); - void OnControllerButtonClick(int player_idx, int button_idx); + void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx); void LoadConfiguration(); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 5958435fc..173130d8d 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -192,18 +192,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -247,18 +247,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -323,18 +323,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -378,18 +378,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -478,18 +478,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -533,18 +533,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -609,18 +609,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -664,18 +664,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -782,18 +782,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -837,18 +837,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -913,18 +913,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -968,18 +968,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1068,18 +1068,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1123,18 +1123,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1199,18 +1199,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1254,18 +1254,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1393,18 +1393,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1448,18 +1448,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1524,18 +1524,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1579,18 +1579,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1679,18 +1679,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1734,18 +1734,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1810,18 +1810,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1865,18 +1865,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1983,18 +1983,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2038,18 +2038,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2114,18 +2114,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2169,18 +2169,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2269,18 +2269,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2324,18 +2324,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2400,18 +2400,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2455,18 +2455,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2546,14 +2546,65 @@ </property> </widget> </item> - <item row="4" column="2"> + <item row="1" column="0"> + <widget class="QCheckBox" name="emulate_analog_keyboard"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Emulate Analog with Keyboard Input</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="mouse_panning"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Enable mouse panning</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QDoubleSpinBox" name="mouse_panning_sensitivity"> + <property name="toolTip"> + <string>Mouse sensitivity</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="decimals"> + <number>2</number> + </property> + <property name="minimum"> + <double>0.100000000000000</double> + </property> + <property name="maximum"> + <double>16.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.010000000000000</double> + </property> + <property name="value"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item row="6" column="2"> <widget class="QPushButton" name="touchscreen_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="3" column="1"> <spacer name="horizontalSpacer_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -2569,21 +2620,21 @@ </property> </spacer> </item> - <item row="1" column="2"> + <item row="3" column="2"> <widget class="QPushButton" name="mouse_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="4" column="0"> + <item row="6" column="0"> <widget class="QCheckBox" name="touchscreen_enabled"> <property name="text"> <string>Touchscreen</string> </property> </widget> </item> - <item row="1" column="0"> + <item row="3" column="0"> <widget class="QCheckBox" name="mouse_enabled"> <property name="minimumSize"> <size> @@ -2596,28 +2647,28 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="8" column="0"> <widget class="QLabel" name="motion_touch"> <property name="text"> <string>Motion / Touch</string> </property> </widget> </item> - <item row="6" column="2"> + <item row="8" column="2"> <widget class="QPushButton" name="buttonMotionTouch"> <property name="text"> <string>Configure</string> </property> </widget> </item> - <item row="5" column="0"> + <item row="7" column="0"> <widget class="QCheckBox" name="debug_enabled"> <property name="text"> <string>Debug Controller</string> </property> </widget> </item> - <item row="5" column="2"> + <item row="7" column="2"> <widget class="QPushButton" name="debug_configure"> <property name="text"> <string>Configure</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 13ecb3dc5..21d0d3449 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -18,11 +18,17 @@ #include "core/hle/service/sm/sm.h" #include "input_common/gcadapter/gc_poller.h" #include "input_common/main.h" +#include "input_common/mouse/mouse_poller.h" +#include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h" -constexpr std::size_t HANDHELD_INDEX = 8; +using namespace Service::HID; const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> ConfigureInputPlayer::analog_sub_buttons{{ @@ -34,6 +40,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> namespace { +constexpr std::size_t HANDHELD_INDEX = 8; + void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { Core::System& system{Core::System::GetInstance()}; @@ -42,48 +50,12 @@ void UpdateController(Settings::ControllerType controller_type, std::size_t npad } Service::SM::ServiceManager& sm = system.ServiceManager(); - auto& npad = - sm.GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); } -/// Maps the controller type combobox index to Controller Type enum -constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { - switch (index) { - case 0: - default: - return Settings::ControllerType::ProController; - case 1: - return Settings::ControllerType::DualJoyconDetached; - case 2: - return Settings::ControllerType::LeftJoycon; - case 3: - return Settings::ControllerType::RightJoycon; - case 4: - return Settings::ControllerType::Handheld; - } -} - -/// Maps the Controller Type enum to controller type combobox index -constexpr int GetIndexFromControllerType(Settings::ControllerType type) { - switch (type) { - case Settings::ControllerType::ProController: - default: - return 0; - case Settings::ControllerType::DualJoyconDetached: - return 1; - case Settings::ControllerType::LeftJoycon: - return 2; - case Settings::ControllerType::RightJoycon: - return 3; - case Settings::ControllerType::Handheld: - return 4; - } -} - QString GetKeyName(int key_code) { switch (key_code) { case Qt::LeftButton: @@ -149,6 +121,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "cemuhookudp") { + if (param.Has("pad_index")) { + const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); + return QObject::tr("Motion %1").arg(motion_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -173,6 +153,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return {}; } + if (param.Get("engine", "") == "mouse") { + if (param.Has("button")) { + const QString button_str = QString::number(int(param.Get("button", 0))); + return QObject::tr("Click %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + return QObject::tr("[unknown]"); } @@ -185,41 +173,31 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - if (param.Get("engine", "") == "sdl") { + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { if (dir == "modifier") { return QObject::tr("[unused]"); } - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Axis %1").arg(axis_x_str); + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Axis %1").arg(axis_y_str); + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - return {}; - } - - if (param.Get("engine", "") == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); - } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("GC Axis %1").arg(axis_x_str); + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("GC Axis %1").arg(axis_y_str); + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } return {}; @@ -231,10 +209,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug) + InputProfiles* profiles_, bool debug) : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), - debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()), - poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) { + debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()), + bottom_row(bottom_row) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -262,19 +241,26 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }, }}; + motion_map = { + ui->buttonMotionLeft, + ui->buttonMotionRight, + }; + analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; + analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod}; analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange}; analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange}; analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup}; analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; - const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param, - int default_val) { + const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id, + Common::ParamPackage* param, int default_val, + InputCommon::Polling::DeviceType type) { connect(button, &QPushButton::clicked, [=, this] { HandleClick( - button, + button, button_id, [=, this](Common::ParamPackage params) { // Workaround for ZL & ZR for analog triggers like on XBOX // controllers. Analog triggers (from controllers like the XBOX @@ -291,22 +277,56 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } *param = std::move(params); }, - InputCommon::Polling::DeviceType::Button); + type); }); }; for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { auto* const button = button_map[button_id]; + if (button == nullptr) { continue; } - ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], - Config::default_buttons[button_id]); + + ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id], + Config::default_buttons[button_id], + InputCommon::Polling::DeviceType::Button); + + button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + }); } - // Handle clicks for the modifier buttons as well. - ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); - ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + auto* const button = motion_map[motion_id]; + if (button == nullptr) { + continue; + } + + ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id], + Config::default_motions[motion_id], + InputCommon::Polling::DeviceType::Motion); + + button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + motions_param[motion_id].Clear(); + motion_map[motion_id]->setText(tr("[not set]")); + }); + context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); + }); + } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { @@ -317,26 +337,97 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } connect(analog_button, &QPushButton::clicked, [=, this] { + if (!map_analog_stick_accepted) { + map_analog_stick_accepted = + QMessageBox::information( + this, tr("Map Analog Stick"), + tr("After pressing OK, first move your joystick horizontally, and then " + "vertically.\nTo invert the axes, first move your joystick " + "vertically, and then horizontally."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok; + if (!map_analog_stick_accepted) { + return; + } + } HandleClick( - analog_map_buttons[analog_id][sub_button_id], + analog_map_buttons[analog_id][sub_button_id], analog_id, [=, this](const Common::ParamPackage& params) { SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); }, InputCommon::Polling::DeviceType::AnalogPreferred); }); + + analog_button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect( + analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Clear(); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = + analogs_param[analog_id].Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_x", invert_str); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = + analogs_param[analog_id].Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_y", invert_str); + } + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + }); + context_menu.exec( + analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location)); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + }); } + // Handle clicks for the modifier buttons as well. + connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_modifier_button[analog_id], analog_id, + [=, this](const Common::ParamPackage& params) { + analogs_param[analog_id].Set("modifier", params.Serialize()); + }, + InputCommon::Polling::DeviceType::Button); + }); + + analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Set("modifier", ""); + analog_map_modifier_button[analog_id]->setText(tr("[not set]")); + }); + context_menu.exec( + analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); + }); + connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), [=, this] { const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); analogs_param[analog_id].Set("range", spinbox_value / 100.0f); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); }); connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] { const auto slider_value = analog_map_deadzone_slider[analog_id]->value(); analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value)); analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); }); connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { @@ -348,21 +439,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } // Player Connected checkbox - connect(ui->groupConnectedController, &QGroupBox::toggled, - [this](bool checked) { emit Connected(checked); }); - - // Set up controller type. Only Player 1 can choose Handheld. - ui->comboControllerType->clear(); - - QStringList controller_types = { - tr("Pro Controller"), - tr("Dual Joycons"), - tr("Left Joycon"), - tr("Right Joycon"), - }; + connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { + emit Connected(checked); + ui->controllerFrame->SetConnectedStatus(checked); + }); if (player_index == 0) { - controller_types.append(tr("Handheld")); connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) { emit HandheldStateChanged(GetControllerTypeFromIndex(index) == @@ -370,31 +452,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } + if (debug || player_index == 9) { + ui->groupConnectedController->setCheckable(false); + } + // The Debug Controller can only choose the Pro Controller. if (debug) { ui->buttonScreenshot->setEnabled(false); ui->buttonHome->setEnabled(false); - ui->groupConnectedController->setCheckable(false); - QStringList debug_controller_types = { - tr("Pro Controller"), - }; - ui->comboControllerType->addItems(debug_controller_types); + ui->comboControllerType->addItem(tr("Pro Controller")); } else { - ui->comboControllerType->addItems(controller_types); + SetConnectableControllers(); } UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); }); - connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, + connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this, &ConfigureInputPlayer::UpdateMappingWithDefaults); + ui->comboDevices->setCurrentIndex(-1); + ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); - UpdateInputDevices(); connect(ui->buttonRefreshDevices, &QPushButton::clicked, [this] { emit RefreshInputDevices(); }); @@ -405,37 +494,82 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i Common::ParamPackage params; if (input_subsystem->GetGCButtons()->IsPolling()) { params = input_subsystem->GetGCButtons()->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } if (input_subsystem->GetGCAnalogs()->IsPolling()) { params = input_subsystem->GetGCAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetUDPMotions()->IsPolling()) { + params = input_subsystem->GetUDPMotions()->GetNextInput(); if (params.Has("engine")) { SetPollingResult(params, false); return; } } + if (input_subsystem->GetMouseButtons()->IsPolling()) { + params = input_subsystem->GetMouseButtons()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseAnalogs()->IsPolling()) { + params = input_subsystem->GetMouseAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseMotions()->IsPolling()) { + params = input_subsystem->GetMouseMotions()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseTouch()->IsPolling()) { + params = input_subsystem->GetMouseTouch()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } }); - LoadConfiguration(); + UpdateInputProfiles(); - // TODO(wwylele): enable this when we actually emulate it - ui->buttonHome->setEnabled(false); + connect(ui->buttonProfilesNew, &QPushButton::clicked, this, + &ConfigureInputPlayer::CreateProfile); + connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, + &ConfigureInputPlayer::DeleteProfile); + connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this, + &ConfigureInputPlayer::LoadProfile); + connect(ui->buttonProfilesSave, &QPushButton::clicked, this, + &ConfigureInputPlayer::SaveProfile); + + LoadConfiguration(); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); } ConfigureInputPlayer::~ConfigureInputPlayer() = default; void ConfigureInputPlayer::ApplyConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs; @@ -448,30 +582,98 @@ void ConfigureInputPlayer::ApplyConfiguration() { return; } - player.controller_type = - static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); - player.connected = ui->groupConnectedController->isChecked(); + auto& motions = player.motions; + + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + // Apply configuration for handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = handheld.connected; + handheld = player; + handheld.connected = handheld_connected; + } +} + +void ConfigureInputPlayer::TryConnectSelectedController() { + auto& player = Settings::values.players.GetValue()[player_index]; - // Player 2-8 - if (player_index != 0) { - UpdateController(player.controller_type, player_index, player.connected); + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + // Connect Handheld depending on Player 1's controller configuration. + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = ui->groupConnectedController->isChecked() && + controller_type == Settings::ControllerType::Handheld; + // Connect only if handheld is going from disconnected to connected + if (!handheld.connected && handheld_connected) { + UpdateController(controller_type, HANDHELD_INDEX, true); + } + handheld.connected = handheld_connected; + } + + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); return; } - // Player 1 and Handheld - auto& handheld = Settings::values.players[HANDHELD_INDEX]; - // If Handheld is selected, copy all the settings from Player 1 to Handheld. - if (player.controller_type == Settings::ControllerType::Handheld) { - handheld = player; - handheld.connected = ui->groupConnectedController->isChecked(); - player.connected = false; // Disconnect Player 1 - } else { - player.connected = ui->groupConnectedController->isChecked(); - handheld.connected = false; // Disconnect Handheld + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + if (!player.connected) { + return; } - UpdateController(player.controller_type, player_index, player.connected); - UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); + UpdateController(controller_type, player_index, true); +} + +void ConfigureInputPlayer::TryDisconnectSelectedController() { + const auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + // Disconnect Handheld depending on Player 1's controller configuration. + if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) { + const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = ui->groupConnectedController->isChecked() && + controller_type == Settings::ControllerType::Handheld; + // Disconnect only if handheld is going from connected to disconnected + if (handheld.connected && !handheld_connected) { + UpdateController(controller_type, HANDHELD_INDEX, false); + } + return; + } + + // Do not do anything if the controller configuration has not changed. + if (player.controller_type == controller_type && player.connected == player_connected) { + return; + } + + // Do not disconnect if the controller is already disconnected + if (!player.connected) { + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { + if (bottom_row == nullptr) { + return; + } + QWidget::showEvent(event); + ui->main->addWidget(bottom_row); } void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -488,7 +690,7 @@ void ConfigureInputPlayer::RetranslateUI() { } void ConfigureInputPlayer::LoadConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; if (debug) { std::transform(Settings::values.debug_pad_buttons.begin(), Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -501,71 +703,105 @@ void ConfigureInputPlayer::LoadConfiguration() { [](const std::string& str) { return Common::ParamPackage(str); }); std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); } UpdateUI(); + UpdateInputDeviceCombobox(); if (debug) { return; } - ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); + ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type)); ui->groupConnectedController->setChecked( player.connected || - (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected)); + (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected)); } -void ConfigureInputPlayer::UpdateInputDevices() { - input_devices = input_subsystem->GetInputDevices(); - ui->comboDevices->clear(); - for (auto device : input_devices) { - ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); - } +void ConfigureInputPlayer::ConnectPlayer(bool connected) { + ui->groupConnectedController->setChecked(connected); } -void ConfigureInputPlayer::RestoreDefaults() { - // Reset Buttons - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +void ConfigureInputPlayer::UpdateInputDeviceCombobox() { + // Skip input device persistence if "Input Devices" is set to "Any". + if (ui->comboDevices->currentIndex() == 0) { + UpdateInputDevices(); + return; } - // Reset Modifier Buttons - lstick_mod = - Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0])); - rstick_mod = - Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1])); + // Find the first button that isn't empty. + const auto button_param = + std::find_if(buttons_param.begin(), buttons_param.end(), + [](const Common::ParamPackage param) { return param.Has("engine"); }); + const bool buttons_empty = button_param == buttons_param.end(); - // Reset Analogs - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + const auto current_engine = button_param->Get("engine", ""); + const auto current_guid = button_param->Get("guid", ""); + const auto current_port = button_param->Get("port", ""); + + const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse"; + + UpdateInputDevices(); + + if (buttons_empty) { + return; + } + + const bool all_one_device = + std::all_of(buttons_param.begin(), buttons_param.end(), + [current_engine, current_guid, current_port, + is_keyboard_mouse](const Common::ParamPackage param) { + if (is_keyboard_mouse) { + return !param.Has("engine") || param.Get("engine", "") == "keyboard" || + param.Get("engine", "") == "mouse"; + } + return !param.Has("engine") || (param.Get("engine", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port); + }); + + if (all_one_device) { + if (is_keyboard_mouse) { + ui->comboDevices->setCurrentIndex(1); + return; } + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [current_engine, current_guid, current_port](const Common::ParamPackage param) { + return param.Get("class", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast<int>(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + } else { + ui->comboDevices->setCurrentIndex(0); } - UpdateUI(); - UpdateInputDevices(); - ui->comboControllerType->setCurrentIndex(0); +} + +void ConfigureInputPlayer::RestoreDefaults() { + UpdateMappingWithDefaults(); } void ConfigureInputPlayer::ClearAll() { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { const auto* const button = button_map[button_id]; - if (button == nullptr || !button->isEnabled()) { + if (button == nullptr) { continue; } buttons_param[button_id].Clear(); } - lstick_mod.Clear(); - rstick_mod.Clear(); - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; - if (analog_button == nullptr || !analog_button->isEnabled()) { + if (analog_button == nullptr) { continue; } @@ -573,6 +809,15 @@ void ConfigureInputPlayer::ClearAll() { } } + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + const auto* const motion_button = motion_map[motion_id]; + if (motion_button == nullptr) { + continue; + } + + motions_param[motion_id].Clear(); + } + UpdateUI(); UpdateInputDevices(); } @@ -582,8 +827,9 @@ void ConfigureInputPlayer::UpdateUI() { button_map[button]->setText(ButtonToText(buttons_param[button])); } - ui->buttonLStickMod->setText(ButtonToText(lstick_mod)); - ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id])); + } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { @@ -597,6 +843,9 @@ void ConfigureInputPlayer::UpdateUI() { AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); } + analog_map_modifier_button[analog_id]->setText( + ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")})); + const auto deadzone_label = analog_map_deadzone_label[analog_id]; const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id]; @@ -607,8 +856,9 @@ void ConfigureInputPlayer::UpdateUI() { int slider_value; auto& param = analogs_param[analog_id]; - const bool is_controller = - param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; + const bool is_controller = param.Get("engine", "") == "sdl" || + param.Get("engine", "") == "gcpad" || + param.Get("engine", "") == "mouse"; if (is_controller) { if (!param.Has("deadzone")) { @@ -636,110 +886,98 @@ void ConfigureInputPlayer::UpdateUI() { modifier_label->setVisible(!is_controller); modifier_slider->setVisible(!is_controller); range_groupbox->setVisible(is_controller); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); } } -void ConfigureInputPlayer::UpdateMappingWithDefaults() { - if (ui->comboDevices->currentIndex() < 2) { - return; - } - const auto& device = input_devices[ui->comboDevices->currentIndex()]; - auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); - auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); - for (int i = 0; i < buttons_param.size(); ++i) { - buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; - } - for (int i = 0; i < analogs_param.size(); ++i) { - analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; - } +void ConfigureInputPlayer::SetConnectableControllers() { + const auto add_controllers = [this](bool enable_all, + Controller_NPad::NpadStyleSet npad_style_set = {}) { + index_controller_type_pairs.clear(); + ui->comboControllerType->clear(); - UpdateUI(); -} + if (enable_all || npad_style_set.fullkey == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::ProController); + ui->comboControllerType->addItem(tr("Pro Controller")); + } -void ConfigureInputPlayer::HandleClick( - QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type) { - button->setText(tr("[waiting]")); - button->setFocus(); + if (enable_all || npad_style_set.joycon_dual == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::DualJoyconDetached); + ui->comboControllerType->addItem(tr("Dual Joycons")); + } - // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a - // controller, then they don't want keyboard/mouse input - want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + if (enable_all || npad_style_set.joycon_left == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::LeftJoycon); + ui->comboControllerType->addItem(tr("Left Joycon")); + } - input_setter = new_input_setter; + if (enable_all || npad_style_set.joycon_right == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::RightJoycon); + ui->comboControllerType->addItem(tr("Right Joycon")); + } - device_pollers = input_subsystem->GetPollers(type); + if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::Handheld); + ui->comboControllerType->addItem(tr("Handheld")); + } - for (auto& poller : device_pollers) { - poller->Start(); - } + if (enable_all || npad_style_set.gamecube == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::GameCube); + ui->comboControllerType->addItem(tr("GameCube Controller")); + } + }; - QWidget::grabMouse(); - QWidget::grabKeyboard(); + Core::System& system{Core::System::GetInstance()}; - if (type == InputCommon::Polling::DeviceType::Button) { - input_subsystem->GetGCButtons()->BeginConfiguration(); - } else { - input_subsystem->GetGCAnalogs()->BeginConfiguration(); + if (!system.IsPoweredOn()) { + add_controllers(true); + return; } - timeout_timer->start(2500); // Cancel after 2.5 seconds - poll_timer->start(50); // Check for new inputs every 50ms -} + Service::SM::ServiceManager& sm = system.ServiceManager(); -void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); - QWidget::releaseMouse(); - QWidget::releaseKeyboard(); + add_controllers(false, npad.GetSupportedStyleSet()); +} - input_subsystem->GetGCButtons()->EndConfiguration(); - input_subsystem->GetGCAnalogs()->EndConfiguration(); +Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [index](const auto& pair) { return pair.first == index; }); - if (!abort) { - (*input_setter)(params); + if (it == index_controller_type_pairs.end()) { + return Settings::ControllerType::ProController; } - UpdateUI(); - input_setter = std::nullopt; + return it->second; } -void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { - if (!input_setter || !event) { - return; - } +int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [type](const auto& pair) { return pair.second == type; }); - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, - false); - } else { - // We don't want any mouse buttons, so don't stop polling - return; + if (it == index_controller_type_pairs.end()) { + return -1; } - SetPollingResult({}, true); + return it->first; } -void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) { - return; - } - - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } +void ConfigureInputPlayer::UpdateInputDevices() { + input_devices = input_subsystem->GetInputDevices(); + ui->comboDevices->clear(); + for (auto device : input_devices) { + ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); } - - SetPollingResult({}, true); } void ConfigureInputPlayer::UpdateControllerIcon() { @@ -762,7 +1000,7 @@ void ConfigureInputPlayer::UpdateControllerIcon() { } }(); - const QString theme = [this] { + const QString theme = [] { if (QIcon::themeName().contains(QStringLiteral("dark"))) { return QStringLiteral("_dark"); } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { @@ -771,8 +1009,8 @@ void ConfigureInputPlayer::UpdateControllerIcon() { return QString{}; } }(); - - ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); + ui->controllerFrame->SetControllerType( + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())); } void ConfigureInputPlayer::UpdateControllerAvailableButtons() { @@ -783,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { // List of all the widgets that will be hidden by any of the following layouts that need // "unhidden" after the controller type changes - const std::array<QWidget*, 9> layout_show = { + const std::array<QWidget*, 11> layout_show = { ui->buttonShoulderButtonsSLSR, ui->horizontalSpacerShoulderButtonsWidget, ui->horizontalSpacerShoulderButtonsWidget2, @@ -793,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->buttonShoulderButtonsRight, ui->buttonMiscButtonsPlusHome, ui->bottomRight, + ui->buttonMiscButtonsMinusGroup, + ui->buttonMiscButtonsScreenshotGroup, }; for (auto* widget : layout_show) { @@ -825,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->bottomLeft, }; break; + case Settings::ControllerType::GameCube: + layout_hidden = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonMiscButtonsMinusGroup, + ui->buttonMiscButtonsScreenshotGroup, + }; + break; } for (auto* widget : layout_hidden) { @@ -832,14 +1080,387 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { } } -void ConfigureInputPlayer::showEvent(QShowEvent* event) { - if (bottom_row == nullptr) { +void ConfigureInputPlayer::UpdateControllerEnabledButtons() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + // List of all the widgets that will be disabled by any of the following layouts that need + // "enabled" after the controller type changes + const std::array<QWidget*, 4> layout_enable = { + ui->buttonHome, + ui->buttonLStickPressedGroup, + ui->groupRStickPressed, + ui->buttonShoulderButtonsButtonLGroup, + }; + + for (auto* widget : layout_enable) { + widget->setEnabled(true); + } + + std::vector<QWidget*> layout_disable; + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::RightJoycon: + // TODO(wwylele): enable this when we actually emulate it + layout_disable = { + ui->buttonHome, + }; + break; + case Settings::ControllerType::GameCube: + layout_disable = { + ui->buttonHome, + ui->buttonLStickPressedGroup, + ui->groupRStickPressed, + ui->buttonShoulderButtonsButtonLGroup, + }; + break; + } + + for (auto* widget : layout_disable) { + widget->setEnabled(false); + } +} + +void ConfigureInputPlayer::UpdateMotionButtons() { + if (debug) { + // Motion isn't used with the debug controller, hide both groupboxes. + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); return; } - QWidget::showEvent(event); - ui->main->addWidget(bottom_row); + + // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::Handheld: + // Show "Motion 1" and hide "Motion 2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::RightJoycon: + // Show "Motion 2" and hide "Motion 1". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->show(); + break; + case Settings::ControllerType::GameCube: + // Hide both "Motion 1/2". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::DualJoyconDetached: + default: + // Show both "Motion 1/2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->show(); + break; + } } -void ConfigureInputPlayer::ConnectPlayer(bool connected) { - ui->groupConnectedController->setChecked(connected); +void ConfigureInputPlayer::UpdateControllerButtonNames() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::RightJoycon: + ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus")); + ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL")); + ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR")); + ui->buttonShoulderButtonsRGroup->setTitle(tr("R")); + ui->LStick->setTitle(tr("Left Stick")); + ui->RStick->setTitle(tr("Right Stick")); + break; + case Settings::ControllerType::GameCube: + ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause")); + ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L")); + ui->buttonShoulderButtonsZRGroup->setTitle(tr("R")); + ui->buttonShoulderButtonsRGroup->setTitle(tr("Z")); + ui->LStick->setTitle(tr("Control Stick")); + ui->RStick->setTitle(tr("C-Stick")); + break; + } +} + +void ConfigureInputPlayer::UpdateMappingWithDefaults() { + if (ui->comboDevices->currentIndex() == 0) { + return; + } + + if (ui->comboDevices->currentIndex() == 1) { + // Reset keyboard bindings + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + + analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); + } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motions_param[motion_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + } + + UpdateUI(); + return; + } + + // Reset controller bindings + const auto& device = input_devices[ui->comboDevices->currentIndex()]; + auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); + auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); + for (std::size_t i = 0; i < buttons_param.size(); ++i) { + buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; + } + for (std::size_t i = 0; i < analogs_param.size(); ++i) { + analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; + } + + UpdateUI(); +} + +void ConfigureInputPlayer::HandleClick( + QPushButton* button, std::size_t button_id, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { + button->setText(tr("Shake!")); + } else { + button->setText(tr("[waiting]")); + } + button->setFocus(); + + // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a + // controller, then they don't want keyboard/mouse input + want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + + input_setter = new_input_setter; + + device_pollers = input_subsystem->GetPollers(type); + + for (auto& poller : device_pollers) { + poller->Start(); + } + + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetGCButtons()->BeginConfiguration(); + } else { + input_subsystem->GetGCAnalogs()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetUDPMotions()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetMouseButtons()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + input_subsystem->GetMouseAnalogs()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetMouseMotions()->BeginConfiguration(); + } else { + input_subsystem->GetMouseTouch()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Button) { + ui->controllerFrame->BeginMappingButton(button_id); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + ui->controllerFrame->BeginMappingAnalog(button_id); + } + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(50); // Check for new inputs every 50ms +} + +void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + input_subsystem->GetGCButtons()->EndConfiguration(); + input_subsystem->GetGCAnalogs()->EndConfiguration(); + + input_subsystem->GetUDPMotions()->EndConfiguration(); + + input_subsystem->GetMouseButtons()->EndConfiguration(); + input_subsystem->GetMouseAnalogs()->EndConfiguration(); + input_subsystem->GetMouseMotions()->EndConfiguration(); + input_subsystem->GetMouseTouch()->EndConfiguration(); + + if (!abort) { + (*input_setter)(params); + } + + UpdateUI(); + UpdateInputDeviceCombobox(); + ui->controllerFrame->EndMapping(); + + input_setter = std::nullopt; +} + +bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const { + if (ui->comboDevices->currentIndex() == 0) { + return true; + } + + // Keyboard/Mouse + if (ui->comboDevices->currentIndex() == 1) { + return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse"; + } + + const auto current_input_device = input_devices[ui->comboDevices->currentIndex()]; + return params.Get("engine", "") == current_input_device.Get("class", "") && + params.Get("guid", "") == current_input_device.Get("guid", "") && + params.Get("port", "") == current_input_device.Get("port", ""); +} + +void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + input_subsystem->GetMouse()->PressButton(0, 0, event->button()); +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_mouse) { + SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + + SetPollingResult({}, true); +} + +void ConfigureInputPlayer::CreateProfile() { + const auto profile_name = + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); + + if (profile_name.isEmpty()) { + return; + } + + if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("The given profile name is not valid!")); + return; + } + + ApplyConfiguration(); + + if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("Failed to create the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } + + emit RefreshInputProfiles(player_index); + + ui->comboProfiles->addItem(profile_name); + ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1); +} + +void ConfigureInputPlayer::DeleteProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + if (!profiles->DeleteProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Delete Input Profile"), + tr("Failed to delete the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } + + emit RefreshInputProfiles(player_index); + + ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex()); + ui->comboProfiles->setCurrentIndex(-1); +} + +void ConfigureInputPlayer::LoadProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Load Input Profile"), + tr("Failed to load the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } + + LoadConfiguration(); +} + +void ConfigureInputPlayer::SaveProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Save Input Profile"), + tr("Failed to save the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } +} + +void ConfigureInputPlayer::UpdateInputProfiles() { + ui->comboProfiles->clear(); + + for (const auto& profile_name : profiles->GetInputProfileNames()) { + ui->comboProfiles->addItem(QString::fromStdString(profile_name)); + } + + ui->comboProfiles->setCurrentIndex(-1); } diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index a25bc3bd9..efe953fbc 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -9,6 +9,7 @@ #include <memory> #include <optional> #include <string> +#include <vector> #include <QWidget> @@ -26,6 +27,8 @@ class QString; class QTimer; class QWidget; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -45,14 +48,32 @@ class ConfigureInputPlayer : public QWidget { public: explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug = false); + InputProfiles* profiles_, bool debug = false); ~ConfigureInputPlayer() override; /// Save all button configurations to settings file. void ApplyConfiguration(); + /** + * Attempts to connect the currently selected controller in the HID backend. + * This function will not do anything if it is not connected in the frontend. + */ + void TryConnectSelectedController(); + + /** + * Attempts to disconnect the currently selected controller in the HID backend. + * This function will not do anything if the configuration has not changed. + */ + void TryDisconnectSelectedController(); + + /// Set the connection state checkbox (used to sync state). + void ConnectPlayer(bool connected); + /// Update the input devices combobox. - void UpdateInputDevices(); + void UpdateInputDeviceCombobox(); + + /// Updates the list of controller profiles. + void UpdateInputProfiles(); /// Restore all buttons to their default values. void RestoreDefaults(); @@ -60,9 +81,6 @@ public: /// Clear all input configuration. void ClearAll(); - /// Set the connection state checkbox (used to sync state). - void ConnectPlayer(bool connected); - signals: /// Emitted when this controller is connected by the user. void Connected(bool connected); @@ -70,6 +88,12 @@ signals: void HandheldStateChanged(bool is_handheld); /// Emitted when the input devices combobox is being refreshed. void RefreshInputDevices(); + /** + * Emitted when the input profiles combobox is being refreshed. + * The player_index represents the current player's index, and the profile combobox + * will not be updated for this index as they are already updated by other mechanisms. + */ + void RefreshInputProfiles(std::size_t player_index); protected: void showEvent(QShowEvent* event) override; @@ -82,13 +106,16 @@ private: void LoadConfiguration(); /// Called when the button was pressed. - void HandleClick(QPushButton* button, + void HandleClick(QPushButton* button, std::size_t button_id, std::function<void(const Common::ParamPackage&)> new_input_setter, InputCommon::Polling::DeviceType type); /// Finish polling and configure input using the input_setter. void SetPollingResult(const Common::ParamPackage& params, bool abort); + /// Checks whether a given input can be accepted. + bool IsInputAcceptable(const Common::ParamPackage& params) const; + /// Handle mouse button press events. void mousePressEvent(QMouseEvent* event) override; @@ -98,8 +125,17 @@ private: /// Update UI to reflect current configuration. void UpdateUI(); - /// Update the controller selection combobox - void UpdateControllerCombobox(); + /// Sets the available controllers. + void SetConnectableControllers(); + + /// Gets the Controller Type for a given controller combobox index. + Settings::ControllerType GetControllerTypeFromIndex(int index) const; + + /// Gets the controller combobox index for a given Controller Type. + int GetIndexFromControllerType(Settings::ControllerType type) const; + + /// Update the available input devices. + void UpdateInputDevices(); /// Update the current controller icon. void UpdateControllerIcon(); @@ -107,9 +143,30 @@ private: /// Hides and disables controller settings based on the current controller type. void UpdateControllerAvailableButtons(); + /// Disables controller settings based on the current controller type. + void UpdateControllerEnabledButtons(); + + /// Shows or hides motion groupboxes based on the current controller type. + void UpdateMotionButtons(); + + /// Alters the button names based on the current controller type. + void UpdateControllerButtonNames(); + /// Gets the default controller mapping for this device and auto configures the input to match. void UpdateMappingWithDefaults(); + /// Creates a controller profile. + void CreateProfile(); + + /// Deletes the selected controller profile. + void DeleteProfile(); + + /// Loads the selected controller profile. + void LoadProfile(); + + /// Saves the current controller configuration into a selected controller profile. + void SaveProfile(); + std::unique_ptr<Ui::ConfigureInputPlayer> ui; std::size_t player_index; @@ -117,9 +174,14 @@ private: InputCommon::InputSubsystem* input_subsystem; + InputProfiles* profiles; + std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> poll_timer; + /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. + std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs; + static constexpr int PLAYER_COUNT = 8; std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; @@ -128,23 +190,25 @@ private: std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param; static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; /// Each button input is represented by a QPushButton. std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; - /// Extra buttons for the modifiers. - Common::ParamPackage lstick_mod; - Common::ParamPackage rstick_mod; /// A group of four QPushButtons represent one analog input. The buttons each represent up, /// down, left, right, respectively. std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> analog_map_buttons; + /// Each motion input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; + std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label; std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider; std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox; + std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button; std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label; std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider; std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox; @@ -154,12 +218,15 @@ private: std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. + bool map_analog_stick_accepted{}; + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. - bool want_keyboard_mouse = false; + bool want_keyboard_mouse{}; /// List of physical devices users can map with. If a SDL backed device is selected, then you - /// can usue this device to get a default mapping. + /// can use this device to get a default mapping. std::vector<Common::ParamPackage> input_devices; /// Bottom row is where console wide settings are held, and its "owned" by the parent diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index 9bc681894..e76aa484f 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -83,6 +83,12 @@ </property> <item> <widget class="QComboBox" name="comboControllerType"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> <item> <property name="text"> <string>Pro Controller</string> @@ -136,6 +142,12 @@ </property> <item> <widget class="QComboBox" name="comboDevices"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> <item> <property name="text"> <string>Any</string> @@ -152,14 +164,14 @@ <widget class="QPushButton" name="buttonRefreshDevices"> <property name="minimumSize"> <size> - <width>24</width> - <height>22</height> + <width>21</width> + <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>24</width> - <height>22</height> + <width>21</width> + <height>21</height> </size> </property> <property name="styleSheet"> @@ -198,18 +210,25 @@ <number>5</number> </property> <item> - <widget class="QComboBox" name="comboProfiles"/> + <widget class="QComboBox" name="comboProfiles"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> + </widget> </item> <item> <widget class="QPushButton" name="buttonProfilesSave"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Save</string> @@ -220,12 +239,12 @@ <widget class="QPushButton" name="buttonProfilesNew"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>New</string> @@ -236,12 +255,12 @@ <widget class="QPushButton" name="buttonProfilesDelete"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Delete</string> @@ -393,18 +412,18 @@ <widget class="QPushButton" name="buttonLStickUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -463,18 +482,18 @@ <widget class="QPushButton" name="buttonLStickLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -512,18 +531,18 @@ <widget class="QPushButton" name="buttonLStickRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -594,18 +613,18 @@ <widget class="QPushButton" name="buttonLStickDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -664,18 +683,18 @@ <widget class="QPushButton" name="buttonLStick"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Pressed</string> @@ -713,18 +732,18 @@ <widget class="QPushButton" name="buttonLStickMod"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Modifier</string> @@ -759,13 +778,13 @@ <widget class="QSpinBox" name="spinboxLStickRange"> <property name="minimumSize"> <size> - <width>55</width> + <width>68</width> <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -966,18 +985,18 @@ <widget class="QPushButton" name="buttonDpadUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -1036,18 +1055,18 @@ <widget class="QPushButton" name="buttonDpadLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -1085,18 +1104,18 @@ <widget class="QPushButton" name="buttonDpadRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -1167,18 +1186,18 @@ <widget class="QPushButton" name="buttonDpadDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -1292,18 +1311,18 @@ <widget class="QPushButton" name="buttonL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>L</string> @@ -1341,18 +1360,18 @@ <widget class="QPushButton" name="buttonZL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>ZL</string> @@ -1445,18 +1464,18 @@ <widget class="QPushButton" name="buttonMinus"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Minus</string> @@ -1494,18 +1513,18 @@ <widget class="QPushButton" name="buttonScreenshot"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Capture</string> @@ -1564,18 +1583,18 @@ <widget class="QPushButton" name="buttonPlus"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Plus</string> @@ -1613,18 +1632,18 @@ <widget class="QPushButton" name="buttonHome"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Home</string> @@ -1717,18 +1736,18 @@ <widget class="QPushButton" name="buttonR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>R</string> @@ -1766,18 +1785,18 @@ <widget class="QPushButton" name="buttonZR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>ZR</string> @@ -1870,18 +1889,18 @@ <widget class="QPushButton" name="buttonSL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>SL</string> @@ -1919,18 +1938,18 @@ <widget class="QPushButton" name="buttonSR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>SR</string> @@ -1945,44 +1964,47 @@ </item> </layout> </item> - <item> - <widget class="QFrame" name="controllerFrame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="styleSheet"> - <string notr="true">image: url(:/controller/pro);</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - </layout> - </widget> - </item> + <item> + <widget class="PlayerControlPreview" name="controllerFrame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">image: url(:/controller/pro);</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + </layout> + </widget> + </item> <item> <layout class="QHBoxLayout" name="miscButtons"> <property name="spacing"> <number>3</number> </property> + <property name="topMargin"> + <number>0</number> + </property> <item> <spacer name="horizontalSpacerMiscButtons1"> <property name="orientation"> @@ -1990,21 +2012,119 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> - <height>0</height> + <width>20</width> + <height>20</height> </size> </property> </spacer> </item> <item> + <widget class="QGroupBox" name="buttonMotionLeftGroup"> + <property name="title"> + <string>Motion 1</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonMotionLeft"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Left</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="buttonMotionRightGroup"> + <property name="title"> + <string>Motion 2</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonMotionRight"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Right</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer name="horizontalSpacerMiscButtons4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> - <height>0</height> + <width>20</width> + <height>20</height> </size> </property> </spacer> @@ -2124,18 +2244,18 @@ <widget class="QPushButton" name="buttonX"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>X</string> @@ -2194,18 +2314,18 @@ <widget class="QPushButton" name="buttonY"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Y</string> @@ -2243,18 +2363,18 @@ <widget class="QPushButton" name="buttonA"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>A</string> @@ -2325,18 +2445,18 @@ <widget class="QPushButton" name="buttonB"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>B</string> @@ -2479,18 +2599,18 @@ <widget class="QPushButton" name="buttonRStickUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -2549,18 +2669,18 @@ <widget class="QPushButton" name="buttonRStickLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -2598,18 +2718,18 @@ <widget class="QPushButton" name="buttonRStickRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -2680,18 +2800,18 @@ <widget class="QPushButton" name="buttonRStickDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -2750,18 +2870,18 @@ <widget class="QPushButton" name="buttonRStick"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Pressed</string> @@ -2799,18 +2919,18 @@ <widget class="QPushButton" name="buttonRStickMod"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Modifier</string> @@ -2845,13 +2965,13 @@ <widget class="QSpinBox" name="spinboxRStickRange"> <property name="minimumSize"> <size> - <width>55</width> + <width>68</width> <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -2967,6 +3087,14 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>PlayerControlPreview</class> + <extends>QFrame</extends> + <header>yuzu/configuration/configure_input_player_widget.h</header> + <container>1</container> + </customwidget> + </customwidgets> <resources> <include location="../../../dist/icons/controller/controller.qrc"/> </resources> diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp new file mode 100644 index 000000000..61ba91cef --- /dev/null +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -0,0 +1,2732 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <QMenu> +#include <QPainter> +#include <QTimer> +#include "yuzu/configuration/configure_input_player_widget.h" + +PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) { + UpdateColors(); + QTimer* timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, QOverload<>::of(&PlayerControlPreview::UpdateInput)); + + // refresh at 60hz + timer->start(16); +} + +PlayerControlPreview::~PlayerControlPreview() = default; + +void PlayerControlPreview::SetPlayerInput(std::size_t index, const ButtonParam& buttons_param, + const AnalogParam& analogs_param) { + player_index = index; + Settings::ButtonsRaw buttonss; + Settings::AnalogsRaw analogs; + std::transform(buttons_param.begin(), buttons_param.end(), buttonss.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + std::transform(buttonss.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + buttonss.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(), + Input::CreateDevice<Input::ButtonDevice>); + std::transform(analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(), + Input::CreateDevice<Input::AnalogDevice>); + UpdateColors(); +} +void PlayerControlPreview::SetPlayerInputRaw(std::size_t index, + const Settings::ButtonsRaw& buttons_, + Settings::AnalogsRaw analogs_) { + player_index = index; + std::transform(buttons_.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + buttons_.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(), + Input::CreateDevice<Input::ButtonDevice>); + std::transform(analogs_.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + analogs_.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(), + Input::CreateDevice<Input::AnalogDevice>); + UpdateColors(); +} + +PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size_t index, + bool player_on) { + if (!player_on) { + return {0, 0, 0, 0}; + } + + switch (index) { + case 0: + return {1, 0, 0, 0}; + case 1: + return {1, 1, 0, 0}; + case 2: + return {1, 1, 1, 0}; + case 3: + return {1, 1, 1, 1}; + case 4: + return {1, 0, 0, 1}; + case 5: + return {1, 0, 1, 0}; + case 6: + return {1, 0, 1, 1}; + case 7: + return {0, 1, 1, 0}; + default: + return {0, 0, 0, 0}; + } +} + +void PlayerControlPreview::SetConnectedStatus(bool checked) { + LedPattern led_pattern = GetColorPattern(player_index, checked); + + led_color[0] = led_pattern.position1 ? colors.led_on : colors.led_off; + led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off; + led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off; + led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off; +} + +void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) { + controller_type = type; + UpdateColors(); +} + +void PlayerControlPreview::BeginMappingButton(std::size_t index) { + button_mapping_index = index; + mapping_active = true; +} + +void PlayerControlPreview::BeginMappingAnalog(std::size_t index) { + button_mapping_index = Settings::NativeButton::LStick + index; + analog_mapping_index = index; + mapping_active = true; +} + +void PlayerControlPreview::EndMapping() { + button_mapping_index = Settings::NativeButton::BUTTON_NS_END; + analog_mapping_index = Settings::NativeAnalog::NumAnalogs; + mapping_active = false; + blink_counter = 0; +} + +void PlayerControlPreview::UpdateColors() { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + colors.primary = QColor(204, 204, 204); + colors.button = QColor(35, 38, 41); + colors.button2 = QColor(26, 27, 30); + colors.slider_arrow = QColor(14, 15, 18); + colors.font2 = QColor(255, 255, 255); + colors.indicator = QColor(170, 238, 255); + colors.deadzone = QColor(204, 136, 136); + colors.slider_button = colors.button; + } + + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + colors.outline = QColor(160, 160, 160); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + colors.outline = QColor(145, 145, 145); + } else { + colors.outline = QColor(0, 0, 0); + colors.primary = QColor(225, 225, 225); + colors.button = QColor(109, 111, 114); + colors.button2 = QColor(109, 111, 114); + colors.button2 = QColor(77, 80, 84); + colors.slider_arrow = QColor(65, 68, 73); + colors.font2 = QColor(0, 0, 0); + colors.indicator = QColor(0, 0, 200); + colors.deadzone = QColor(170, 0, 0); + colors.slider_button = QColor(153, 149, 149); + } + + // Constant colors + colors.highlight = QColor(170, 0, 0); + colors.highlight2 = QColor(119, 0, 0); + colors.slider = QColor(103, 106, 110); + colors.transparent = QColor(0, 0, 0, 0); + colors.font = QColor(255, 255, 255); + colors.led_on = QColor(255, 255, 0); + colors.led_off = QColor(170, 238, 255); + + colors.left = colors.primary; + colors.right = colors.primary; + // Possible alternative to set colors from settings + // colors.left = QColor(Settings::values.players.GetValue()[player_index].body_color_left); + // colors.right = QColor(Settings::values.players.GetValue()[player_index].body_color_right); +} + +void PlayerControlPreview::UpdateInput() { + bool input_changed = false; + const auto& button_state = buttons; + for (std::size_t index = 0; index < button_values.size(); ++index) { + bool value = false; + if (index < Settings::NativeButton::BUTTON_NS_END) { + value = button_state[index]->GetStatus(); + } + bool blink = mapping_active && index == button_mapping_index; + if (analog_mapping_index == Settings::NativeAnalog::NUM_STICKS_HID) { + blink &= blink_counter > 25; + } + if (button_values[index] != value || blink) { + input_changed = true; + } + button_values[index] = value || blink; + } + + const auto& analog_state = sticks; + for (std::size_t index = 0; index < axis_values.size(); ++index) { + const auto [stick_x_f, stick_y_f] = analog_state[index]->GetStatus(); + const auto [stick_x_rf, stick_y_rf] = analog_state[index]->GetRawStatus(); + + if (static_cast<int>(stick_x_rf * 45) != + static_cast<int>(axis_values[index].raw_value.x() * 45) || + static_cast<int>(-stick_y_rf * 45) != + static_cast<int>(axis_values[index].raw_value.y() * 45)) { + input_changed = true; + } + + axis_values[index].properties = analog_state[index]->GetAnalogProperties(); + axis_values[index].value = QPointF(stick_x_f, -stick_y_f); + axis_values[index].raw_value = QPointF(stick_x_rf, -stick_y_rf); + + const bool blink_analog = mapping_active && index == analog_mapping_index; + if (blink_analog) { + input_changed = true; + axis_values[index].value = + QPointF(blink_counter < 25 ? -blink_counter / 25.0f : 0, + blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0); + } + } + + if (input_changed) { + update(); + } + + if (mapping_active) { + blink_counter = (blink_counter + 1) % 50; + } +} + +void PlayerControlPreview::paintEvent(QPaintEvent* event) { + QFrame::paintEvent(event); + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + const QPointF center = rect().center(); + + switch (controller_type) { + case Settings::ControllerType::Handheld: + DrawHandheldController(p, center); + break; + case Settings::ControllerType::DualJoyconDetached: + DrawDualController(p, center); + break; + case Settings::ControllerType::LeftJoycon: + DrawLeftController(p, center); + break; + case Settings::ControllerType::RightJoycon: + DrawRightController(p, center); + break; + case Settings::ControllerType::GameCube: + DrawGCController(p, center); + break; + case Settings::ControllerType::ProController: + default: + DrawProController(p, center); + break; + } +} + +void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) { + { + using namespace Settings::NativeButton; + + // Sideview left joystick + DrawJoystickSideview(p, center + QPoint(142, -69), + -axis_values[Settings::NativeAnalog::LStick].value.y(), 1.15f, + button_values[LStick]); + + // Topview D-pad buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(-163, -21), button_values[DLeft], 11, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(-117, -21), button_values[DRight], 11, 5, Direction::Up); + + // Topview left joystick + DrawJoystickSideview(p, center + QPointF(-140.5f, -28), + -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1.15f, + button_values[LStick]); + + // Topview minus button + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(-111, -22), button_values[Minus], 8, 4, Direction::Up, + 1); + + // Left trigger + DrawLeftTriggers(p, center, button_values[L]); + DrawRoundButton(p, center + QPoint(151, -146), button_values[L], 8, 4, Direction::Down); + DrawLeftZTriggers(p, center, button_values[ZL]); + + // Sideview D-pad buttons + DrawRoundButton(p, center + QPoint(135, 14), button_values[DLeft], 5, 11, Direction::Right); + DrawRoundButton(p, center + QPoint(135, 36), button_values[DDown], 5, 11, Direction::Right); + DrawRoundButton(p, center + QPoint(135, -10), button_values[DUp], 5, 11, Direction::Right); + DrawRoundButton(p, center + QPoint(135, 14), button_values[DRight], 5, 11, + Direction::Right); + DrawRoundButton(p, center + QPoint(135, 71), button_values[Screenshot], 3, 8, + Direction::Right, 1); + + // Sideview minus button + DrawRoundButton(p, center + QPoint(135, -118), button_values[Minus], 4, 2.66f, + Direction::Right, 1); + + // Sideview SL and SR buttons + button_color = colors.slider_button; + DrawRoundButton(p, center + QPoint(59, 52), button_values[SR], 5, 12, Direction::Left); + DrawRoundButton(p, center + QPoint(59, -69), button_values[SL], 5, 12, Direction::Left); + + DrawLeftBody(p, center); + + // Left trigger top view + DrawLeftTriggersTopView(p, center, button_values[L]); + DrawLeftZTriggersTopView(p, center, button_values[ZL]); + } + + { + // Draw joysticks + using namespace Settings::NativeAnalog; + DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f, + button_values[Settings::NativeButton::LStick]); + DrawRawJoystick(p, center + QPointF(-140, 90), axis_values[LStick].raw_value, + axis_values[LStick].properties); + } + + using namespace Settings::NativeButton; + + // D-pad constants + const QPointF dpad_center = center + QPoint(9, 14); + constexpr int dpad_distance = 23; + constexpr int dpad_radius = 11; + constexpr float dpad_arrow_size = 1.2f; + + // D-pad buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius); + + // D-pad arrows + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size); + + // SR and SL buttons + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawRoundButton(p, center + QPoint(155, 52), button_values[SR], 5.2f, 12, Direction::None, 4); + DrawRoundButton(p, center + QPoint(155, -69), button_values[SL], 5.2f, 12, Direction::None, 4); + + // SR and SL text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(155, 52), Symbol::SR, 1.0f); + DrawSymbol(p, center + QPointF(155, -69), Symbol::SL, 1.0f); + + // Minus button + button_color = colors.button; + DrawMinusButton(p, center + QPoint(39, -118), button_values[Minus], 16); + + // Screenshot button + DrawRoundButton(p, center + QPoint(26, 71), button_values[Screenshot], 8, 8); + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawCircle(p, center + QPoint(26, 71), 5); +} + +void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center) { + { + using namespace Settings::NativeButton; + + // Sideview right joystick + DrawJoystickSideview(p, center + QPoint(173 - 315, 11), + axis_values[Settings::NativeAnalog::RStick].value.y() + 10.0f, 1.15f, + button_values[Settings::NativeButton::RStick]); + + // Topview face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(163, -21), button_values[A], 11, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(117, -21), button_values[Y], 11, 5, Direction::Up); + + // Topview right joystick + DrawJoystickSideview(p, center + QPointF(140, -28), + -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1.15f, + button_values[RStick]); + + // Topview plus button + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(111, -22), button_values[Plus], 8, 4, Direction::Up, 1); + DrawRoundButton(p, center + QPoint(111, -22), button_values[Plus], 2.66f, 4, Direction::Up, + 1); + + // Right trigger + p.setPen(colors.outline); + button_color = colors.button; + DrawRightTriggers(p, center, button_values[R]); + DrawRoundButton(p, center + QPoint(-151, -146), button_values[R], 8, 4, Direction::Down); + DrawRightZTriggers(p, center, button_values[ZR]); + + // Sideview face buttons + DrawRoundButton(p, center + QPoint(-135, -73), button_values[A], 5, 11, Direction::Left); + DrawRoundButton(p, center + QPoint(-135, -50), button_values[B], 5, 11, Direction::Left); + DrawRoundButton(p, center + QPoint(-135, -95), button_values[X], 5, 11, Direction::Left); + DrawRoundButton(p, center + QPoint(-135, -73), button_values[Y], 5, 11, Direction::Left); + + // Sideview home and plus button + DrawRoundButton(p, center + QPoint(-135, 66), button_values[Home], 3, 12, Direction::Left); + DrawRoundButton(p, center + QPoint(-135, -118), button_values[Plus], 4, 8, Direction::Left, + 1); + DrawRoundButton(p, center + QPoint(-135, -118), button_values[Plus], 4, 2.66f, + Direction::Left, 1); + + // Sideview SL and SR buttons + button_color = colors.slider_button; + DrawRoundButton(p, center + QPoint(-59, 52), button_values[SL], 5, 11, Direction::Right); + DrawRoundButton(p, center + QPoint(-59, -69), button_values[SR], 5, 11, Direction::Right); + + DrawRightBody(p, center); + + // Right trigger top view + DrawRightTriggersTopView(p, center, button_values[R]); + DrawRightZTriggersTopView(p, center, button_values[ZR]); + } + + { + // Draw joysticks + using namespace Settings::NativeAnalog; + DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f, + button_values[Settings::NativeButton::RStick]); + DrawRawJoystick(p, center + QPointF(140, 90), axis_values[RStick].raw_value, + axis_values[RStick].properties); + } + + using namespace Settings::NativeButton; + + // Face buttons constants + const QPointF face_center = center + QPoint(-9, -73); + constexpr int face_distance = 23; + constexpr int face_radius = 11; + constexpr float text_size = 1.1f; + + // Face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius); + DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius); + DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius); + DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius); + + // Face buttons text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size); + DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size); + DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size); + DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size); + + // SR and SL buttons + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawRoundButton(p, center + QPoint(-155, 52), button_values[SL], 5, 12, Direction::None, 4.0f); + DrawRoundButton(p, center + QPoint(-155, -69), button_values[SR], 5, 12, Direction::None, 4.0f); + + // SR and SL text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + p.rotate(-180); + DrawSymbol(p, QPointF(-center.x(), -center.y()) + QPointF(155, 69), Symbol::SR, 1.0f); + DrawSymbol(p, QPointF(-center.x(), -center.y()) + QPointF(155, -52), Symbol::SL, 1.0f); + p.rotate(180); + + // Plus Button + DrawPlusButton(p, center + QPoint(-40, -118), button_values[Plus], 16); + + // Home Button + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawCircleButton(p, center + QPoint(-26, 66), button_values[Home], 12); + button_color = colors.button; + DrawCircleButton(p, center + QPoint(-26, 66), button_values[Home], 9); + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPoint(-26, 66), Symbol::House, 5); +} + +void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) { + { + using namespace Settings::NativeButton; + + // Left/Right trigger + DrawDualTriggers(p, center, button_values[L], button_values[R]); + + // Topview face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(200, -71), button_values[A], 10, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(160, -71), button_values[Y], 10, 5, Direction::Up); + + // Topview right joystick + DrawJoystickSideview(p, center + QPointF(180, -78), + -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1, + button_values[RStick]); + + // Topview plus button + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(154, -72), button_values[Plus], 7, 4, Direction::Up, 1); + DrawRoundButton(p, center + QPoint(154, -72), button_values[Plus], 2.33f, 4, Direction::Up, + 1); + + // Topview D-pad buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(-200, -71), button_values[DLeft], 10, 5, Direction::Up); + DrawRoundButton(p, center + QPoint(-160, -71), button_values[DRight], 10, 5, Direction::Up); + + // Topview left joystick + DrawJoystickSideview(p, center + QPointF(-180.5f, -78), + -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1, + button_values[LStick]); + + // Topview minus button + p.setPen(colors.outline); + button_color = colors.button; + DrawRoundButton(p, center + QPoint(-154, -72), button_values[Minus], 7, 4, Direction::Up, + 1); + + DrawDualBody(p, center); + + // Right trigger top view + DrawDualTriggersTopView(p, center, button_values[L], button_values[R]); + DrawDualZTriggersTopView(p, center, button_values[ZL], button_values[ZR]); + } + + { + // Draw joysticks + using namespace Settings::NativeAnalog; + const auto& l_stick = axis_values[LStick]; + const auto l_button = button_values[Settings::NativeButton::LStick]; + const auto& r_stick = axis_values[RStick]; + const auto r_button = button_values[Settings::NativeButton::RStick]; + + DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button); + DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button); + DrawRawJoystick(p, center + QPointF(-180, 90), l_stick.raw_value, l_stick.properties); + DrawRawJoystick(p, center + QPointF(180, 90), r_stick.raw_value, r_stick.properties); + } + + using namespace Settings::NativeButton; + + // Face buttons constants + const QPointF face_center = center + QPoint(65, -65); + constexpr int face_distance = 20; + constexpr int face_radius = 10; + constexpr float text_size = 1.0f; + + // Face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius); + DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius); + DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius); + DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius); + + // Face buttons text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size); + DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size); + DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size); + DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size); + + // D-pad constants + const QPointF dpad_center = center + QPoint(-65, 12); + constexpr int dpad_distance = 20; + constexpr int dpad_radius = 10; + constexpr float dpad_arrow_size = 1.1f; + + // D-pad buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius); + + // D-pad arrows + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size); + + // Minus and Plus button + button_color = colors.button; + DrawMinusButton(p, center + QPoint(-39, -106), button_values[Minus], 14); + DrawPlusButton(p, center + QPoint(39, -106), button_values[Plus], 14); + + // Screenshot button + p.setPen(colors.outline); + DrawRoundButton(p, center + QPoint(-52, 63), button_values[Screenshot], 8, 8); + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawCircle(p, center + QPoint(-52, 63), 5); + + // Home Button + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawCircleButton(p, center + QPoint(50, 60), button_values[Home], 11); + button_color = colors.button; + DrawCircleButton(p, center + QPoint(50, 60), button_values[Home], 8.5f); + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPoint(50, 60), Symbol::House, 4.2f); +} + +void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF center) { + DrawHandheldTriggers(p, center, button_values[Settings::NativeButton::L], + button_values[Settings::NativeButton::R]); + DrawHandheldBody(p, center); + { + // Draw joysticks + using namespace Settings::NativeAnalog; + const auto& l_stick = axis_values[LStick]; + const auto l_button = button_values[Settings::NativeButton::LStick]; + const auto& r_stick = axis_values[RStick]; + const auto r_button = button_values[Settings::NativeButton::RStick]; + + DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button); + DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button); + DrawRawJoystick(p, center + QPointF(-50, 0), l_stick.raw_value, l_stick.properties); + DrawRawJoystick(p, center + QPointF(50, 0), r_stick.raw_value, r_stick.properties); + } + + using namespace Settings::NativeButton; + + // Face buttons constants + const QPointF face_center = center + QPoint(171, -41); + constexpr float face_distance = 12.8f; + constexpr float face_radius = 6.4f; + constexpr float text_size = 0.6f; + + // Face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius); + DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius); + DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius); + DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius); + + // Face buttons text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size); + DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size); + DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size); + DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size); + + // D-pad constants + const QPointF dpad_center = center + QPoint(-171, 8); + constexpr float dpad_distance = 12.8f; + constexpr float dpad_radius = 6.4f; + constexpr float dpad_arrow_size = 0.68f; + + // D-pad buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius); + DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius); + + // D-pad arrows + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size); + DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size); + + // ZL and ZR buttons + p.setPen(colors.outline); + DrawTriggerButton(p, center + QPoint(-210, -130), Direction::Left, button_values[ZL]); + DrawTriggerButton(p, center + QPoint(210, -130), Direction::Right, button_values[ZR]); + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPoint(-210, -130), Symbol::ZL, 1.5f); + DrawSymbol(p, center + QPoint(210, -130), Symbol::ZR, 1.5f); + + // Minus and Plus button + p.setPen(colors.outline); + button_color = colors.button; + DrawMinusButton(p, center + QPoint(-155, -67), button_values[Minus], 8); + DrawPlusButton(p, center + QPoint(155, -67), button_values[Plus], 8); + + // Screenshot button + p.setPen(colors.outline); + DrawRoundButton(p, center + QPoint(-162, 39), button_values[Screenshot], 5, 5); + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawCircle(p, center + QPoint(-162, 39), 3); + + // Home Button + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawCircleButton(p, center + QPoint(161, 37), button_values[Home], 7); + button_color = colors.button; + DrawCircleButton(p, center + QPoint(161, 37), button_values[Home], 5); + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPoint(161, 37), Symbol::House, 2.75f); +} + +void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) { + DrawProTriggers(p, center, button_values[Settings::NativeButton::L], + button_values[Settings::NativeButton::R]); + DrawProBody(p, center); + { + // Draw joysticks + using namespace Settings::NativeAnalog; + DrawProJoystick(p, center + QPointF(-111, -55), axis_values[LStick].value, 11, + button_values[Settings::NativeButton::LStick]); + DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11, + button_values[Settings::NativeButton::RStick]); + DrawRawJoystick(p, center + QPointF(-50, 105), axis_values[LStick].raw_value, + axis_values[LStick].properties); + DrawRawJoystick(p, center + QPointF(50, 105), axis_values[RStick].raw_value, + axis_values[RStick].properties); + } + + using namespace Settings::NativeButton; + + // Face buttons constants + const QPointF face_center = center + QPoint(105, -56); + constexpr int face_distance = 31; + constexpr int face_radius = 15; + constexpr float text_size = 1.5f; + + // Face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius); + DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius); + DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius); + DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius); + + // Face buttons text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size); + DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size); + DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size); + DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size); + + // D-pad buttons + const QPointF dpad_postion = center + QPoint(-61, 0); + DrawArrowButton(p, dpad_postion, Direction::Up, button_values[DUp]); + DrawArrowButton(p, dpad_postion, Direction::Left, button_values[DLeft]); + DrawArrowButton(p, dpad_postion, Direction::Right, button_values[DRight]); + DrawArrowButton(p, dpad_postion, Direction::Down, button_values[DDown]); + DrawArrowButtonOutline(p, dpad_postion); + + // ZL and ZR buttons + p.setPen(colors.outline); + DrawTriggerButton(p, center + QPoint(-210, -130), Direction::Left, button_values[ZL]); + DrawTriggerButton(p, center + QPoint(210, -130), Direction::Right, button_values[ZR]); + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPoint(-210, -130), Symbol::ZL, 1.5f); + DrawSymbol(p, center + QPoint(210, -130), Symbol::ZR, 1.5f); + + // Minus and Plus buttons + p.setPen(colors.outline); + DrawCircleButton(p, center + QPoint(-50, -86), button_values[Minus], 9); + DrawCircleButton(p, center + QPoint(50, -86), button_values[Plus], 9); + + // Minus and Plus symbols + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawRectangle(p, center + QPoint(-50, -86), 9, 1.5f); + DrawRectangle(p, center + QPoint(50, -86), 9, 1.5f); + DrawRectangle(p, center + QPoint(50, -86), 1.5f, 9); + + // Screenshot button + p.setPen(colors.outline); + DrawRoundButton(p, center + QPoint(-29, -56), button_values[Screenshot], 7, 7); + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawCircle(p, center + QPoint(-29, -56), 4.5f); + + // Home Button + p.setPen(colors.outline); + button_color = colors.slider_button; + DrawCircleButton(p, center + QPoint(29, -56), button_values[Home], 10.0f); + button_color = colors.button; + DrawCircleButton(p, center + QPoint(29, -56), button_values[Home], 7.1f); + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPoint(29, -56), Symbol::House, 3.9f); +} + +void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) { + DrawGCTriggers(p, center, button_values[Settings::NativeButton::ZL], + button_values[Settings::NativeButton::ZR]); + DrawGCButtonZ(p, center, button_values[Settings::NativeButton::R]); + DrawGCBody(p, center); + { + // Draw joysticks + using namespace Settings::NativeAnalog; + DrawGCJoystick(p, center + QPointF(-111, -44) + (axis_values[LStick].value * 10), false); + button_color = colors.button2; + DrawCircleButton(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), false, + 15); + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C, + 1.0f); + DrawRawJoystick(p, center + QPointF(-198, -125), axis_values[LStick].raw_value, + axis_values[LStick].properties); + DrawRawJoystick(p, center + QPointF(198, -125), axis_values[RStick].raw_value, + axis_values[RStick].properties); + } + + using namespace Settings::NativeButton; + + // Face buttons constants + constexpr float text_size = 1.1f; + + // Face buttons + p.setPen(colors.outline); + button_color = colors.button; + DrawCircleButton(p, center + QPoint(111, -44), button_values[A], 21); + DrawCircleButton(p, center + QPoint(70, -23), button_values[B], 13); + DrawGCButtonX(p, center, button_values[Settings::NativeButton::X]); + DrawGCButtonY(p, center, button_values[Settings::NativeButton::Y]); + + // Face buttons text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPoint(111, -44), Symbol::A, 1.5f); + DrawSymbol(p, center + QPoint(70, -23), Symbol::B, text_size); + DrawSymbol(p, center + QPoint(151, -53), Symbol::X, text_size); + DrawSymbol(p, center + QPoint(100, -83), Symbol::Y, text_size); + + // D-pad buttons + const QPointF dpad_postion = center + QPoint(-61, 37); + const float dpad_size = 0.8f; + DrawArrowButton(p, dpad_postion, Direction::Up, button_values[DUp], dpad_size); + DrawArrowButton(p, dpad_postion, Direction::Left, button_values[DLeft], dpad_size); + DrawArrowButton(p, dpad_postion, Direction::Right, button_values[DRight], dpad_size); + DrawArrowButton(p, dpad_postion, Direction::Down, button_values[DDown], dpad_size); + DrawArrowButtonOutline(p, dpad_postion, dpad_size); + + // Minus and Plus buttons + p.setPen(colors.outline); + DrawCircleButton(p, center + QPoint(0, -44), button_values[Plus], 8); +} + +constexpr std::array<float, 13 * 2> symbol_a = { + -1.085f, -5.2f, 1.085f, -5.2f, 5.085f, 5.0f, 2.785f, 5.0f, 1.785f, + 2.65f, -1.785f, 2.65f, -2.785f, 5.0f, -5.085f, 5.0f, -1.4f, 1.0f, + 0.0f, -2.8f, 1.4f, 1.0f, -1.4f, 1.0f, -5.085f, 5.0f, +}; +constexpr std::array<float, 134 * 2> symbol_b = { + -4.0f, 0.0f, -4.0f, 0.0f, -4.0f, -0.1f, -3.8f, -5.1f, 1.8f, -5.0f, 2.3f, -4.9f, 2.6f, + -4.8f, 2.8f, -4.7f, 2.9f, -4.6f, 3.1f, -4.5f, 3.2f, -4.4f, 3.4f, -4.3f, 3.4f, -4.2f, + 3.5f, -4.1f, 3.7f, -4.0f, 3.7f, -3.9f, 3.8f, -3.8f, 3.8f, -3.7f, 3.9f, -3.6f, 3.9f, + -3.5f, 4.0f, -3.4f, 4.0f, -3.3f, 4.1f, -3.1f, 4.1f, -3.0f, 4.0f, -2.0f, 4.0f, -1.9f, + 3.9f, -1.7f, 3.9f, -1.6f, 3.8f, -1.5f, 3.8f, -1.4f, 3.7f, -1.3f, 3.7f, -1.2f, 3.6f, + -1.1f, 3.6f, -1.0f, 3.5f, -0.9f, 3.3f, -0.8f, 3.3f, -0.7f, 3.2f, -0.6f, 3.0f, -0.5f, + 2.9f, -0.4f, 2.7f, -0.3f, 2.9f, -0.2f, 3.2f, -0.1f, 3.3f, 0.0f, 3.5f, 0.1f, 3.6f, + 0.2f, 3.8f, 0.3f, 3.9f, 0.4f, 4.0f, 0.6f, 4.1f, 0.7f, 4.3f, 0.8f, 4.3f, 0.9f, + 4.4f, 1.0f, 4.4f, 1.1f, 4.5f, 1.3f, 4.5f, 1.4f, 4.6f, 1.6f, 4.6f, 1.7f, 4.5f, + 2.8f, 4.5f, 2.9f, 4.4f, 3.1f, 4.4f, 3.2f, 4.3f, 3.4f, 4.3f, 3.5f, 4.2f, 3.6f, + 4.2f, 3.7f, 4.1f, 3.8f, 4.1f, 3.9f, 4.0f, 4.0f, 3.9f, 4.2f, 3.8f, 4.3f, 3.6f, + 4.4f, 3.6f, 4.5f, 3.4f, 4.6f, 3.3f, 4.7f, 3.1f, 4.8f, 2.8f, 4.9f, 2.6f, 5.0f, + 2.1f, 5.1f, -4.0f, 5.0f, -4.0f, 4.9f, + + -4.0f, 0.0f, 1.1f, 3.4f, 1.1f, 3.4f, 1.5f, 3.3f, 1.8f, 3.2f, 2.0f, 3.1f, 2.1f, + 3.0f, 2.3f, 2.9f, 2.3f, 2.8f, 2.4f, 2.7f, 2.4f, 2.6f, 2.5f, 2.3f, 2.5f, 2.2f, + 2.4f, 1.7f, 2.4f, 1.6f, 2.3f, 1.4f, 2.3f, 1.3f, 2.2f, 1.2f, 2.2f, 1.1f, 2.1f, + 1.0f, 1.9f, 0.9f, 1.6f, 0.8f, 1.4f, 0.7f, -1.9f, 0.6f, -1.9f, 0.7f, -1.8f, 3.4f, + 1.1f, 3.4f, -4.0f, 0.0f, + + 0.3f, -1.1f, 0.3f, -1.1f, 1.3f, -1.2f, 1.5f, -1.3f, 1.8f, -1.4f, 1.8f, -1.5f, 1.9f, + -1.6f, 2.0f, -1.8f, 2.0f, -1.9f, 2.1f, -2.0f, 2.1f, -2.1f, 2.0f, -2.7f, 2.0f, -2.8f, + 1.9f, -2.9f, 1.9f, -3.0f, 1.8f, -3.1f, 1.6f, -3.2f, 1.6f, -3.3f, 1.3f, -3.4f, -1.9f, + -3.3f, -1.9f, -3.2f, -1.8f, -1.0f, 0.2f, -1.1f, 0.3f, -1.1f, -4.0f, 0.0f, +}; + +constexpr std::array<float, 9 * 2> symbol_y = { + -4.79f, -4.9f, -2.44f, -4.9f, 0.0f, -0.9f, 2.44f, -4.9f, 4.79f, + -4.9f, 1.05f, 1.0f, 1.05f, 5.31f, -1.05f, 5.31f, -1.05f, 1.0f, + +}; + +constexpr std::array<float, 12 * 2> symbol_x = { + -4.4f, -5.0f, -2.0f, -5.0f, 0.0f, -1.7f, 2.0f, -5.0f, 4.4f, -5.0f, 1.2f, 0.0f, + 4.4f, 5.0f, 2.0f, 5.0f, 0.0f, 1.7f, -2.0f, 5.0f, -4.4f, 5.0f, -1.2f, 0.0f, + +}; + +constexpr std::array<float, 7 * 2> symbol_l = { + 2.4f, -3.23f, 2.4f, 2.1f, 5.43f, 2.1f, 5.43f, 3.22f, 0.98f, 3.22f, 0.98f, -3.23f, 2.4f, -3.23f, +}; + +constexpr std::array<float, 98 * 2> symbol_r = { + 1.0f, 0.0f, 1.0f, -0.1f, 1.1f, -3.3f, 4.3f, -3.2f, 5.1f, -3.1f, 5.4f, -3.0f, 5.6f, -2.9f, + 5.7f, -2.8f, 5.9f, -2.7f, 5.9f, -2.6f, 6.0f, -2.5f, 6.1f, -2.3f, 6.2f, -2.2f, 6.2f, -2.1f, + 6.3f, -2.0f, 6.3f, -1.9f, 6.2f, -0.8f, 6.2f, -0.7f, 6.1f, -0.6f, 6.1f, -0.5f, 6.0f, -0.4f, + 6.0f, -0.3f, 5.9f, -0.2f, 5.7f, -0.1f, 5.7f, 0.0f, 5.6f, 0.1f, 5.4f, 0.2f, 5.1f, 0.3f, + 4.7f, 0.4f, 4.7f, 0.5f, 4.9f, 0.6f, 5.0f, 0.7f, 5.2f, 0.8f, 5.2f, 0.9f, 5.3f, 1.0f, + 5.5f, 1.1f, 5.5f, 1.2f, 5.6f, 1.3f, 5.7f, 1.5f, 5.8f, 1.6f, 5.9f, 1.8f, 6.0f, 1.9f, + 6.1f, 2.1f, 6.2f, 2.2f, 6.2f, 2.3f, 6.3f, 2.4f, 6.4f, 2.6f, 6.5f, 2.7f, 6.6f, 2.9f, + 6.7f, 3.0f, 6.7f, 3.1f, 6.8f, 3.2f, 6.8f, 3.3f, 5.3f, 3.2f, 5.2f, 3.1f, 5.2f, 3.0f, + 5.1f, 2.9f, 5.0f, 2.7f, 4.9f, 2.6f, 4.8f, 2.4f, 4.7f, 2.3f, 4.6f, 2.1f, 4.5f, 2.0f, + 4.4f, 1.8f, 4.3f, 1.7f, 4.1f, 1.4f, 4.0f, 1.3f, 3.9f, 1.1f, 3.8f, 1.0f, 3.6f, 0.9f, + 3.6f, 0.8f, 3.5f, 0.7f, 3.3f, 0.6f, 2.9f, 0.5f, 2.3f, 0.6f, 2.3f, 0.7f, 2.2f, 3.3f, + 1.0f, 3.2f, 1.0f, 3.1f, 1.0f, 0.0f, + + 4.2f, -0.5f, 4.4f, -0.6f, 4.7f, -0.7f, 4.8f, -0.8f, 4.9f, -1.0f, 5.0f, -1.1f, 5.0f, -1.2f, + 4.9f, -1.7f, 4.9f, -1.8f, 4.8f, -1.9f, 4.8f, -2.0f, 4.6f, -2.1f, 4.3f, -2.2f, 2.3f, -2.1f, + 2.3f, -2.0f, 2.4f, -0.5f, 4.2f, -0.5f, 1.0f, 0.0f, +}; + +constexpr std::array<float, 18 * 2> symbol_zl = { + -2.6f, -2.13f, -5.6f, -2.13f, -5.6f, -3.23f, -0.8f, -3.23f, -0.8f, -2.13f, -4.4f, 2.12f, + -0.7f, 2.12f, -0.7f, 3.22f, -6.0f, 3.22f, -6.0f, 2.12f, 2.4f, -3.23f, 2.4f, 2.1f, + 5.43f, 2.1f, 5.43f, 3.22f, 0.98f, 3.22f, 0.98f, -3.23f, 2.4f, -3.23f, -6.0f, 2.12f, +}; + +constexpr std::array<float, 57 * 2> symbol_sl = { + -3.0f, -3.65f, -2.76f, -4.26f, -2.33f, -4.76f, -1.76f, -5.09f, -1.13f, -5.26f, -0.94f, + -4.77f, -0.87f, -4.11f, -1.46f, -3.88f, -1.91f, -3.41f, -2.05f, -2.78f, -1.98f, -2.13f, + -1.59f, -1.61f, -0.96f, -1.53f, -0.56f, -2.04f, -0.38f, -2.67f, -0.22f, -3.31f, 0.0f, + -3.93f, 0.34f, -4.49f, 0.86f, -4.89f, 1.49f, -5.05f, 2.14f, -4.95f, 2.69f, -4.6f, + 3.07f, -4.07f, 3.25f, -3.44f, 3.31f, -2.78f, 3.25f, -2.12f, 3.07f, -1.49f, 2.7f, + -0.95f, 2.16f, -0.58f, 1.52f, -0.43f, 1.41f, -0.99f, 1.38f, -1.65f, 1.97f, -1.91f, + 2.25f, -2.49f, 2.25f, -3.15f, 1.99f, -3.74f, 1.38f, -3.78f, 1.06f, -3.22f, 0.88f, + -2.58f, 0.71f, -1.94f, 0.49f, -1.32f, 0.13f, -0.77f, -0.4f, -0.4f, -1.04f, -0.25f, + -1.69f, -0.32f, -2.28f, -0.61f, -2.73f, -1.09f, -2.98f, -1.69f, -3.09f, -2.34f, + + 3.23f, 2.4f, -2.1f, 2.4f, -2.1f, 5.43f, -3.22f, 5.43f, -3.22f, 0.98f, 3.23f, + 0.98f, 3.23f, 2.4f, -3.09f, -2.34f, +}; +constexpr std::array<float, 109 * 2> symbol_zr = { + -2.6f, -2.13f, -5.6f, -2.13f, -5.6f, -3.23f, -0.8f, -3.23f, -0.8f, -2.13f, -4.4f, 2.12f, -0.7f, + 2.12f, -0.7f, 3.22f, -6.0f, 3.22f, -6.0f, 2.12f, + + 1.0f, 0.0f, 1.0f, -0.1f, 1.1f, -3.3f, 4.3f, -3.2f, 5.1f, -3.1f, 5.4f, -3.0f, 5.6f, + -2.9f, 5.7f, -2.8f, 5.9f, -2.7f, 5.9f, -2.6f, 6.0f, -2.5f, 6.1f, -2.3f, 6.2f, -2.2f, + 6.2f, -2.1f, 6.3f, -2.0f, 6.3f, -1.9f, 6.2f, -0.8f, 6.2f, -0.7f, 6.1f, -0.6f, 6.1f, + -0.5f, 6.0f, -0.4f, 6.0f, -0.3f, 5.9f, -0.2f, 5.7f, -0.1f, 5.7f, 0.0f, 5.6f, 0.1f, + 5.4f, 0.2f, 5.1f, 0.3f, 4.7f, 0.4f, 4.7f, 0.5f, 4.9f, 0.6f, 5.0f, 0.7f, 5.2f, + 0.8f, 5.2f, 0.9f, 5.3f, 1.0f, 5.5f, 1.1f, 5.5f, 1.2f, 5.6f, 1.3f, 5.7f, 1.5f, + 5.8f, 1.6f, 5.9f, 1.8f, 6.0f, 1.9f, 6.1f, 2.1f, 6.2f, 2.2f, 6.2f, 2.3f, 6.3f, + 2.4f, 6.4f, 2.6f, 6.5f, 2.7f, 6.6f, 2.9f, 6.7f, 3.0f, 6.7f, 3.1f, 6.8f, 3.2f, + 6.8f, 3.3f, 5.3f, 3.2f, 5.2f, 3.1f, 5.2f, 3.0f, 5.1f, 2.9f, 5.0f, 2.7f, 4.9f, + 2.6f, 4.8f, 2.4f, 4.7f, 2.3f, 4.6f, 2.1f, 4.5f, 2.0f, 4.4f, 1.8f, 4.3f, 1.7f, + 4.1f, 1.4f, 4.0f, 1.3f, 3.9f, 1.1f, 3.8f, 1.0f, 3.6f, 0.9f, 3.6f, 0.8f, 3.5f, + 0.7f, 3.3f, 0.6f, 2.9f, 0.5f, 2.3f, 0.6f, 2.3f, 0.7f, 2.2f, 3.3f, 1.0f, 3.2f, + 1.0f, 3.1f, 1.0f, 0.0f, + + 4.2f, -0.5f, 4.4f, -0.6f, 4.7f, -0.7f, 4.8f, -0.8f, 4.9f, -1.0f, 5.0f, -1.1f, 5.0f, + -1.2f, 4.9f, -1.7f, 4.9f, -1.8f, 4.8f, -1.9f, 4.8f, -2.0f, 4.6f, -2.1f, 4.3f, -2.2f, + 2.3f, -2.1f, 2.3f, -2.0f, 2.4f, -0.5f, 4.2f, -0.5f, 1.0f, 0.0f, -6.0f, 2.12f, +}; + +constexpr std::array<float, 148 * 2> symbol_sr = { + -3.0f, -3.65f, -2.76f, -4.26f, -2.33f, -4.76f, -1.76f, -5.09f, -1.13f, -5.26f, -0.94f, -4.77f, + -0.87f, -4.11f, -1.46f, -3.88f, -1.91f, -3.41f, -2.05f, -2.78f, -1.98f, -2.13f, -1.59f, -1.61f, + -0.96f, -1.53f, -0.56f, -2.04f, -0.38f, -2.67f, -0.22f, -3.31f, 0.0f, -3.93f, 0.34f, -4.49f, + 0.86f, -4.89f, 1.49f, -5.05f, 2.14f, -4.95f, 2.69f, -4.6f, 3.07f, -4.07f, 3.25f, -3.44f, + 3.31f, -2.78f, 3.25f, -2.12f, 3.07f, -1.49f, 2.7f, -0.95f, 2.16f, -0.58f, 1.52f, -0.43f, + 1.41f, -0.99f, 1.38f, -1.65f, 1.97f, -1.91f, 2.25f, -2.49f, 2.25f, -3.15f, 1.99f, -3.74f, + 1.38f, -3.78f, 1.06f, -3.22f, 0.88f, -2.58f, 0.71f, -1.94f, 0.49f, -1.32f, 0.13f, -0.77f, + -0.4f, -0.4f, -1.04f, -0.25f, -1.69f, -0.32f, -2.28f, -0.61f, -2.73f, -1.09f, -2.98f, -1.69f, + -3.09f, -2.34f, + + -1.0f, 0.0f, 0.1f, 1.0f, 3.3f, 1.1f, 3.2f, 4.3f, 3.1f, 5.1f, 3.0f, 5.4f, + 2.9f, 5.6f, 2.8f, 5.7f, 2.7f, 5.9f, 2.6f, 5.9f, 2.5f, 6.0f, 2.3f, 6.1f, + 2.2f, 6.2f, 2.1f, 6.2f, 2.0f, 6.3f, 1.9f, 6.3f, 0.8f, 6.2f, 0.7f, 6.2f, + 0.6f, 6.1f, 0.5f, 6.1f, 0.4f, 6.0f, 0.3f, 6.0f, 0.2f, 5.9f, 0.1f, 5.7f, + 0.0f, 5.7f, -0.1f, 5.6f, -0.2f, 5.4f, -0.3f, 5.1f, -0.4f, 4.7f, -0.5f, 4.7f, + -0.6f, 4.9f, -0.7f, 5.0f, -0.8f, 5.2f, -0.9f, 5.2f, -1.0f, 5.3f, -1.1f, 5.5f, + -1.2f, 5.5f, -1.3f, 5.6f, -1.5f, 5.7f, -1.6f, 5.8f, -1.8f, 5.9f, -1.9f, 6.0f, + -2.1f, 6.1f, -2.2f, 6.2f, -2.3f, 6.2f, -2.4f, 6.3f, -2.6f, 6.4f, -2.7f, 6.5f, + -2.9f, 6.6f, -3.0f, 6.7f, -3.1f, 6.7f, -3.2f, 6.8f, -3.3f, 6.8f, -3.2f, 5.3f, + -3.1f, 5.2f, -3.0f, 5.2f, -2.9f, 5.1f, -2.7f, 5.0f, -2.6f, 4.9f, -2.4f, 4.8f, + -2.3f, 4.7f, -2.1f, 4.6f, -2.0f, 4.5f, -1.8f, 4.4f, -1.7f, 4.3f, -1.4f, 4.1f, + -1.3f, 4.0f, -1.1f, 3.9f, -1.0f, 3.8f, -0.9f, 3.6f, -0.8f, 3.6f, -0.7f, 3.5f, + -0.6f, 3.3f, -0.5f, 2.9f, -0.6f, 2.3f, -0.7f, 2.3f, -3.3f, 2.2f, -3.2f, 1.0f, + -3.1f, 1.0f, 0.0f, 1.0f, + + 0.5f, 4.2f, 0.6f, 4.4f, 0.7f, 4.7f, 0.8f, 4.8f, 1.0f, 4.9f, 1.1f, 5.0f, + 1.2f, 5.0f, 1.7f, 4.9f, 1.8f, 4.9f, 1.9f, 4.8f, 2.0f, 4.8f, 2.1f, 4.6f, + 2.2f, 4.3f, 2.1f, 2.3f, 2.0f, 2.3f, 0.5f, 2.4f, 0.5f, 4.2f, -0.0f, 1.0f, + -3.09f, -2.34f, + +}; + +constexpr std::array<float, 30 * 2> symbol_c = { + 2.86f, 7.57f, 0.99f, 7.94f, -0.91f, 7.87f, -2.73f, 7.31f, -4.23f, 6.14f, -5.2f, 4.51f, + -5.65f, 2.66f, -5.68f, 0.75f, -5.31f, -1.12f, -4.43f, -2.81f, -3.01f, -4.08f, -1.24f, -4.78f, + 0.66f, -4.94f, 2.54f, -4.67f, 4.33f, -4.0f, 4.63f, -2.27f, 3.37f, -2.7f, 1.6f, -3.4f, + -0.3f, -3.5f, -2.09f, -2.87f, -3.34f, -1.45f, -3.91f, 0.37f, -3.95f, 2.27f, -3.49f, 4.12f, + -2.37f, 5.64f, -0.65f, 6.44f, 1.25f, 6.47f, 3.06f, 5.89f, 4.63f, 4.92f, 4.63f, 6.83f, +}; + +constexpr std::array<float, 12 * 2> house = { + -1.3f, 0.0f, -0.93f, 0.0f, -0.93f, 1.15f, 0.93f, 1.15f, 0.93f, 0.0f, 1.3f, 0.0f, + 0.0f, -1.2f, -1.3f, 0.0f, -0.43f, 0.0f, -0.43f, .73f, 0.43f, .73f, 0.43f, 0.0f, +}; + +constexpr std::array<float, 11 * 2> up_arrow_button = { + 9.1f, -9.1f, 9.1f, -30.0f, 8.1f, -30.1f, 7.7f, -30.1f, -8.6f, -30.0f, -9.0f, + -29.8f, -9.3f, -29.5f, -9.5f, -29.1f, -9.1f, -28.7f, -9.1f, -9.1f, 0.0f, 0.6f, +}; + +constexpr std::array<float, 3 * 2> up_arrow_symbol = { + 0.0f, -3.0f, -3.0f, 2.0f, 3.0f, 2.0f, +}; + +constexpr std::array<float, 64 * 2> trigger_button = { + 5.5f, -12.6f, 5.8f, -12.6f, 6.7f, -12.5f, 8.1f, -12.3f, 8.6f, -12.2f, 9.2f, -12.0f, + 9.5f, -11.9f, 9.9f, -11.8f, 10.6f, -11.5f, 11.0f, -11.3f, 11.2f, -11.2f, 11.4f, -11.1f, + 11.8f, -10.9f, 12.0f, -10.8f, 12.2f, -10.7f, 12.4f, -10.5f, 12.6f, -10.4f, 12.8f, -10.3f, + 13.6f, -9.7f, 13.8f, -9.6f, 13.9f, -9.4f, 14.1f, -9.3f, 14.8f, -8.6f, 15.0f, -8.5f, + 15.1f, -8.3f, 15.6f, -7.8f, 15.7f, -7.6f, 16.1f, -7.0f, 16.3f, -6.8f, 16.4f, -6.6f, + 16.5f, -6.4f, 16.8f, -6.0f, 16.9f, -5.8f, 17.0f, -5.6f, 17.1f, -5.4f, 17.2f, -5.2f, + 17.3f, -5.0f, 17.4f, -4.8f, 17.5f, -4.6f, 17.6f, -4.4f, 17.7f, -4.1f, 17.8f, -3.9f, + 17.9f, -3.5f, 18.0f, -3.3f, 18.1f, -3.0f, 18.2f, -2.6f, 18.2f, -2.3f, 18.3f, -2.1f, + 18.3f, -1.9f, 18.4f, -1.4f, 18.5f, -1.2f, 18.6f, -0.3f, 18.6f, 0.0f, 18.3f, 13.9f, + -17.0f, 13.8f, -17.0f, 13.6f, -16.4f, -11.4f, -16.3f, -11.6f, -16.1f, -11.8f, -15.7f, -12.0f, + -15.5f, -12.1f, -15.1f, -12.3f, -14.6f, -12.4f, -13.4f, -12.5f, +}; + +constexpr std::array<float, 36 * 2> pro_left_trigger = { + -65.2f, -132.6f, -68.2f, -134.1f, -71.3f, -135.5f, -74.4f, -136.7f, -77.6f, + -137.6f, -80.9f, -138.1f, -84.3f, -138.3f, -87.6f, -138.3f, -91.0f, -138.1f, + -94.3f, -137.8f, -97.6f, -137.3f, -100.9f, -136.7f, -107.5f, -135.3f, -110.7f, + -134.5f, -120.4f, -131.8f, -123.6f, -130.8f, -126.8f, -129.7f, -129.9f, -128.5f, + -132.9f, -127.1f, -135.9f, -125.6f, -138.8f, -123.9f, -141.6f, -122.0f, -144.1f, + -119.8f, -146.3f, -117.3f, -148.4f, -114.7f, -150.4f, -112.0f, -152.3f, -109.2f, + -155.3f, -104.0f, -152.0f, -104.3f, -148.7f, -104.5f, -145.3f, -104.8f, -35.5f, + -117.2f, -38.5f, -118.7f, -41.4f, -120.3f, -44.4f, -121.8f, -50.4f, -124.9f, +}; + +constexpr std::array<float, 14 * 2> pro_body_top = { + 0.0f, -115.4f, -4.4f, -116.1f, -69.7f, -131.3f, -66.4f, -131.9f, -63.1f, -132.3f, + -56.4f, -133.0f, -53.1f, -133.3f, -49.8f, -133.5f, -43.1f, -133.8f, -39.8f, -134.0f, + -36.5f, -134.1f, -16.4f, -134.4f, -13.1f, -134.4f, 0.0f, -134.1f, +}; + +constexpr std::array<float, 145 * 2> pro_left_handle = { + -178.7f, -47.5f, -179.0f, -46.1f, -179.3f, -44.6f, -182.0f, -29.8f, -182.3f, -28.4f, + -182.6f, -26.9f, -182.8f, -25.4f, -183.1f, -23.9f, -183.3f, -22.4f, -183.6f, -21.0f, + -183.8f, -19.5f, -184.1f, -18.0f, -184.3f, -16.5f, -184.6f, -15.1f, -184.8f, -13.6f, + -185.1f, -12.1f, -185.3f, -10.6f, -185.6f, -9.1f, -185.8f, -7.7f, -186.1f, -6.2f, + -186.3f, -4.7f, -186.6f, -3.2f, -186.8f, -1.7f, -187.1f, -0.3f, -187.3f, 1.2f, + -187.6f, 2.7f, -187.8f, 4.2f, -188.3f, 7.1f, -188.5f, 8.6f, -188.8f, 10.1f, + -189.0f, 11.6f, -189.3f, 13.1f, -189.5f, 14.5f, -190.0f, 17.5f, -190.2f, 19.0f, + -190.5f, 20.5f, -190.7f, 21.9f, -191.2f, 24.9f, -191.4f, 26.4f, -191.7f, 27.9f, + -191.9f, 29.3f, -192.4f, 32.3f, -192.6f, 33.8f, -193.1f, 36.8f, -193.3f, 38.2f, + -193.8f, 41.2f, -194.0f, 42.7f, -194.7f, 47.1f, -194.9f, 48.6f, -199.0f, 82.9f, + -199.1f, 84.4f, -199.1f, 85.9f, -199.2f, 87.4f, -199.2f, 88.9f, -199.1f, 94.9f, + -198.9f, 96.4f, -198.8f, 97.8f, -198.5f, 99.3f, -198.3f, 100.8f, -198.0f, 102.3f, + -197.7f, 103.7f, -197.4f, 105.2f, -197.0f, 106.7f, -196.6f, 108.1f, -195.7f, 111.0f, + -195.2f, 112.4f, -194.1f, 115.2f, -193.5f, 116.5f, -192.8f, 117.9f, -192.1f, 119.2f, + -190.6f, 121.8f, -189.8f, 123.1f, -188.9f, 124.3f, -187.0f, 126.6f, -186.0f, 127.7f, + -183.9f, 129.8f, -182.7f, 130.8f, -180.3f, 132.6f, -179.1f, 133.4f, -177.8f, 134.1f, + -176.4f, 134.8f, -175.1f, 135.5f, -173.7f, 136.0f, -169.4f, 137.3f, -167.9f, 137.7f, + -166.5f, 138.0f, -165.0f, 138.3f, -163.5f, 138.4f, -162.0f, 138.4f, -160.5f, 138.3f, + -159.0f, 138.0f, -157.6f, 137.7f, -156.1f, 137.3f, -154.7f, 136.9f, -153.2f, 136.5f, + -151.8f, 136.0f, -150.4f, 135.4f, -149.1f, 134.8f, -147.7f, 134.1f, -146.5f, 133.3f, + -145.2f, 132.5f, -144.0f, 131.6f, -142.8f, 130.6f, -141.7f, 129.6f, -139.6f, 127.5f, + -138.6f, 126.4f, -137.7f, 125.2f, -135.1f, 121.5f, -134.3f, 120.3f, -133.5f, 119.0f, + -131.9f, 116.5f, -131.1f, 115.2f, -128.8f, 111.3f, -128.0f, 110.1f, -127.2f, 108.8f, + -126.5f, 107.5f, -125.7f, 106.2f, -125.0f, 104.9f, -124.2f, 103.6f, -123.5f, 102.3f, + -122.0f, 99.6f, -121.3f, 98.3f, -115.8f, 87.7f, -115.1f, 86.4f, -114.4f, 85.0f, + -113.7f, 83.7f, -112.3f, 81.0f, -111.6f, 79.7f, -110.1f, 77.1f, -109.4f, 75.8f, + -108.0f, 73.1f, -107.2f, 71.8f, -106.4f, 70.6f, -105.7f, 69.3f, -104.8f, 68.0f, + -104.0f, 66.8f, -103.1f, 65.6f, -101.1f, 63.3f, -100.0f, 62.3f, -98.8f, 61.4f, + -97.6f, 60.6f, -97.9f, 59.5f, -98.8f, 58.3f, -101.5f, 54.6f, -102.4f, 53.4f, +}; + +constexpr std::array<float, 245 * 2> pro_body = { + -0.7f, -129.1f, -54.3f, -129.1f, -55.0f, -129.1f, -57.8f, -129.0f, -58.5f, -129.0f, + -60.7f, -128.9f, -61.4f, -128.9f, -62.8f, -128.8f, -63.5f, -128.8f, -65.7f, -128.7f, + -66.4f, -128.7f, -67.8f, -128.6f, -68.5f, -128.6f, -69.2f, -128.5f, -70.0f, -128.5f, + -70.7f, -128.4f, -71.4f, -128.4f, -72.1f, -128.3f, -72.8f, -128.3f, -73.5f, -128.2f, + -74.2f, -128.2f, -74.9f, -128.1f, -75.7f, -128.1f, -76.4f, -128.0f, -77.1f, -128.0f, + -77.8f, -127.9f, -78.5f, -127.9f, -79.2f, -127.8f, -80.6f, -127.7f, -81.4f, -127.6f, + -82.1f, -127.5f, -82.8f, -127.5f, -83.5f, -127.4f, -84.9f, -127.3f, -85.6f, -127.2f, + -87.0f, -127.1f, -87.7f, -127.0f, -88.5f, -126.9f, -89.2f, -126.8f, -89.9f, -126.8f, + -90.6f, -126.7f, -94.1f, -126.3f, -94.8f, -126.2f, -113.2f, -123.3f, -113.9f, -123.2f, + -114.6f, -123.0f, -115.3f, -122.9f, -116.7f, -122.6f, -117.4f, -122.5f, -118.1f, -122.3f, + -118.8f, -122.2f, -119.5f, -122.0f, -120.9f, -121.7f, -121.6f, -121.5f, -122.3f, -121.4f, + -122.9f, -121.2f, -123.6f, -121.0f, -126.4f, -120.3f, -127.1f, -120.1f, -127.8f, -119.8f, + -128.4f, -119.6f, -129.1f, -119.4f, -131.2f, -118.7f, -132.5f, -118.3f, -133.2f, -118.0f, + -133.8f, -117.7f, -134.5f, -117.4f, -135.1f, -117.2f, -135.8f, -116.9f, -136.4f, -116.5f, + -137.0f, -116.2f, -137.7f, -115.8f, -138.3f, -115.4f, -138.9f, -115.1f, -139.5f, -114.7f, + -160.0f, -100.5f, -160.5f, -100.0f, -162.5f, -97.9f, -162.9f, -97.4f, -163.4f, -96.8f, + -163.8f, -96.2f, -165.3f, -93.8f, -165.7f, -93.2f, -166.0f, -92.6f, -166.4f, -91.9f, + -166.7f, -91.3f, -167.3f, -90.0f, -167.6f, -89.4f, -167.8f, -88.7f, -168.1f, -88.0f, + -168.4f, -87.4f, -168.6f, -86.7f, -168.9f, -86.0f, -169.1f, -85.4f, -169.3f, -84.7f, + -169.6f, -84.0f, -169.8f, -83.3f, -170.2f, -82.0f, -170.4f, -81.3f, -172.8f, -72.3f, + -173.0f, -71.6f, -173.5f, -69.5f, -173.7f, -68.8f, -173.9f, -68.2f, -174.0f, -67.5f, + -174.2f, -66.8f, -174.5f, -65.4f, -174.7f, -64.7f, -174.8f, -64.0f, -175.0f, -63.3f, + -175.3f, -61.9f, -175.5f, -61.2f, -175.8f, -59.8f, -176.0f, -59.1f, -176.1f, -58.4f, + -176.3f, -57.7f, -176.6f, -56.3f, -176.8f, -55.6f, -176.9f, -54.9f, -177.1f, -54.2f, + -177.3f, -53.6f, -177.4f, -52.9f, -177.6f, -52.2f, -177.9f, -50.8f, -178.1f, -50.1f, + -178.2f, -49.4f, -178.2f, -48.7f, -177.8f, -48.1f, -177.1f, -46.9f, -176.7f, -46.3f, + -176.4f, -45.6f, -176.0f, -45.0f, -175.3f, -43.8f, -174.9f, -43.2f, -174.2f, -42.0f, + -173.4f, -40.7f, -173.1f, -40.1f, -172.7f, -39.5f, -172.0f, -38.3f, -171.6f, -37.7f, + -170.5f, -35.9f, -170.1f, -35.3f, -169.7f, -34.6f, -169.3f, -34.0f, -168.6f, -32.8f, + -168.2f, -32.2f, -166.3f, -29.2f, -165.9f, -28.6f, -163.2f, -24.4f, -162.8f, -23.8f, + -141.8f, 6.8f, -141.4f, 7.4f, -139.4f, 10.3f, -139.0f, 10.9f, -138.5f, 11.5f, + -138.1f, 12.1f, -137.3f, 13.2f, -136.9f, 13.8f, -136.0f, 15.0f, -135.6f, 15.6f, + -135.2f, 16.1f, -134.8f, 16.7f, -133.9f, 17.9f, -133.5f, 18.4f, -133.1f, 19.0f, + -131.8f, 20.7f, -131.4f, 21.3f, -130.1f, 23.0f, -129.7f, 23.6f, -128.4f, 25.3f, + -128.0f, 25.9f, -126.7f, 27.6f, -126.3f, 28.2f, -125.4f, 29.3f, -125.0f, 29.9f, + -124.1f, 31.0f, -123.7f, 31.6f, -122.8f, 32.7f, -122.4f, 33.3f, -121.5f, 34.4f, + -121.1f, 35.0f, -120.6f, 35.6f, -120.2f, 36.1f, -119.7f, 36.7f, -119.3f, 37.2f, + -118.9f, 37.8f, -118.4f, 38.4f, -118.0f, 38.9f, -117.5f, 39.5f, -117.1f, 40.0f, + -116.6f, 40.6f, -116.2f, 41.1f, -115.7f, 41.7f, -115.2f, 42.2f, -114.8f, 42.8f, + -114.3f, 43.3f, -113.9f, 43.9f, -113.4f, 44.4f, -112.4f, 45.5f, -112.0f, 46.0f, + -111.5f, 46.5f, -110.5f, 47.6f, -110.0f, 48.1f, -109.6f, 48.6f, -109.1f, 49.2f, + -108.6f, 49.7f, -107.7f, 50.8f, -107.2f, 51.3f, -105.7f, 52.9f, -105.3f, 53.4f, + -104.8f, 53.9f, -104.3f, 54.5f, -103.8f, 55.0f, -100.7f, 58.0f, -100.2f, 58.4f, + -99.7f, 58.9f, -99.1f, 59.3f, -97.2f, 60.3f, -96.5f, 60.1f, -95.9f, 59.7f, + -95.3f, 59.4f, -94.6f, 59.1f, -93.9f, 58.9f, -92.6f, 58.5f, -91.9f, 58.4f, + -91.2f, 58.2f, -90.5f, 58.1f, -89.7f, 58.0f, -89.0f, 57.9f, -86.2f, 57.6f, + -85.5f, 57.5f, -84.1f, 57.4f, -83.4f, 57.3f, -82.6f, 57.3f, -81.9f, 57.2f, + -81.2f, 57.2f, -80.5f, 57.1f, -79.8f, 57.1f, -78.4f, 57.0f, -77.7f, 57.0f, + -75.5f, 56.9f, -74.8f, 56.9f, -71.9f, 56.8f, -71.2f, 56.8f, 0.0f, 56.8f, +}; + +constexpr std::array<float, 199 * 2> gc_body = { + 0.0f, -138.03f, -4.91f, -138.01f, -8.02f, -137.94f, -11.14f, -137.82f, -14.25f, + -137.67f, -17.37f, -137.48f, -20.48f, -137.25f, -23.59f, -137.0f, -26.69f, -136.72f, + -29.8f, -136.41f, -32.9f, -136.07f, -35.99f, -135.71f, -39.09f, -135.32f, -42.18f, + -134.91f, -45.27f, -134.48f, -48.35f, -134.03f, -51.43f, -133.55f, -54.51f, -133.05f, + -57.59f, -132.52f, -60.66f, -131.98f, -63.72f, -131.41f, -66.78f, -130.81f, -69.84f, + -130.2f, -72.89f, -129.56f, -75.94f, -128.89f, -78.98f, -128.21f, -82.02f, -127.49f, + -85.05f, -126.75f, -88.07f, -125.99f, -91.09f, -125.19f, -94.1f, -124.37f, -97.1f, + -123.52f, -100.09f, -122.64f, -103.07f, -121.72f, -106.04f, -120.77f, -109.0f, -119.79f, + -111.95f, -118.77f, -114.88f, -117.71f, -117.8f, -116.61f, -120.7f, -115.46f, -123.58f, + -114.27f, -126.44f, -113.03f, -129.27f, -111.73f, -132.08f, -110.38f, -134.86f, -108.96f, + -137.6f, -107.47f, -140.3f, -105.91f, -142.95f, -104.27f, -145.55f, -102.54f, -148.07f, + -100.71f, -150.51f, -98.77f, -152.86f, -96.71f, -155.09f, -94.54f, -157.23f, -92.27f, + -159.26f, -89.9f, -161.2f, -87.46f, -163.04f, -84.94f, -164.78f, -82.35f, -166.42f, + -79.7f, -167.97f, -77.0f, -169.43f, -74.24f, -170.8f, -71.44f, -172.09f, -68.6f, + -173.29f, -65.72f, -174.41f, -62.81f, -175.45f, -59.87f, -176.42f, -56.91f, -177.31f, + -53.92f, -178.14f, -50.91f, -178.9f, -47.89f, -179.6f, -44.85f, -180.24f, -41.8f, + -180.82f, -38.73f, -181.34f, -35.66f, -181.8f, -32.57f, -182.21f, -29.48f, -182.57f, + -26.38f, -182.88f, -23.28f, -183.15f, -20.17f, -183.36f, -17.06f, -183.54f, -13.95f, + -183.71f, -10.84f, -184.0f, -7.73f, -184.23f, -4.62f, -184.44f, -1.51f, -184.62f, + 1.6f, -184.79f, 4.72f, -184.95f, 7.83f, -185.11f, 10.95f, -185.25f, 14.06f, + -185.38f, 17.18f, -185.51f, 20.29f, -185.63f, 23.41f, -185.74f, 26.53f, -185.85f, + 29.64f, -185.95f, 32.76f, -186.04f, 35.88f, -186.12f, 39.0f, -186.19f, 42.11f, + -186.26f, 45.23f, -186.32f, 48.35f, -186.37f, 51.47f, -186.41f, 54.59f, -186.44f, + 57.7f, -186.46f, 60.82f, -186.46f, 63.94f, -186.44f, 70.18f, -186.41f, 73.3f, + -186.36f, 76.42f, -186.3f, 79.53f, -186.22f, 82.65f, -186.12f, 85.77f, -185.99f, + 88.88f, -185.84f, 92.0f, -185.66f, 95.11f, -185.44f, 98.22f, -185.17f, 101.33f, + -184.85f, 104.43f, -184.46f, 107.53f, -183.97f, 110.61f, -183.37f, 113.67f, -182.65f, + 116.7f, -181.77f, 119.69f, -180.71f, 122.62f, -179.43f, 125.47f, -177.89f, 128.18f, + -176.05f, 130.69f, -173.88f, 132.92f, -171.36f, 134.75f, -168.55f, 136.1f, -165.55f, + 136.93f, -162.45f, 137.29f, -156.23f, 137.03f, -153.18f, 136.41f, -150.46f, 134.9f, + -148.14f, 132.83f, -146.14f, 130.43f, -144.39f, 127.85f, -142.83f, 125.16f, -141.41f, + 122.38f, -140.11f, 119.54f, -138.9f, 116.67f, -137.77f, 113.76f, -136.7f, 110.84f, + -135.68f, 107.89f, -134.71f, 104.93f, -133.77f, 101.95f, -132.86f, 98.97f, -131.97f, + 95.98f, -131.09f, 92.99f, -130.23f, 89.99f, -129.36f, 86.99f, -128.49f, 84.0f, + -127.63f, 81.0f, -126.76f, 78.01f, -125.9f, 75.01f, -124.17f, 69.02f, -123.31f, + 66.02f, -121.59f, 60.03f, -120.72f, 57.03f, -119.86f, 54.03f, -118.13f, 48.04f, + -117.27f, 45.04f, -115.55f, 39.05f, -114.68f, 36.05f, -113.82f, 33.05f, -112.96f, + 30.06f, -110.4f, 28.29f, -107.81f, 26.55f, -105.23f, 24.8f, -97.48f, 19.55f, + -94.9f, 17.81f, -92.32f, 16.06f, -87.15f, 12.56f, -84.57f, 10.81f, -81.99f, + 9.07f, -79.4f, 7.32f, -76.82f, 5.57f, -69.07f, 0.33f, -66.49f, -1.42f, + -58.74f, -6.66f, -56.16f, -8.41f, -48.4f, -13.64f, -45.72f, -15.22f, -42.93f, + -16.62f, -40.07f, -17.86f, -37.15f, -18.96f, -34.19f, -19.94f, -31.19f, -20.79f, + -28.16f, -21.55f, -25.12f, -22.21f, -22.05f, -22.79f, -18.97f, -23.28f, -15.88f, + -23.7f, -12.78f, -24.05f, -9.68f, -24.33f, -6.57f, -24.55f, -3.45f, -24.69f, + 0.0f, -24.69f, +}; + +constexpr std::array<float, 99 * 2> gc_left_body = { + -74.59f, -97.22f, -70.17f, -94.19f, -65.95f, -90.89f, -62.06f, -87.21f, -58.58f, + -83.14f, -55.58f, -78.7f, -53.08f, -73.97f, -51.05f, -69.01f, -49.46f, -63.89f, + -48.24f, -58.67f, -47.36f, -53.39f, -46.59f, -48.09f, -45.7f, -42.8f, -44.69f, + -37.54f, -43.54f, -32.31f, -42.25f, -27.11f, -40.8f, -21.95f, -39.19f, -16.84f, + -37.38f, -11.8f, -35.34f, -6.84f, -33.04f, -2.0f, -30.39f, 2.65f, -27.26f, + 7.0f, -23.84f, 11.11f, -21.19f, 15.76f, -19.18f, 20.73f, -17.73f, 25.88f, + -16.82f, 31.16f, -16.46f, 36.5f, -16.7f, 41.85f, -17.63f, 47.13f, -19.31f, + 52.21f, -21.8f, 56.95f, -24.91f, 61.3f, -28.41f, 65.36f, -32.28f, 69.06f, + -36.51f, 72.35f, -41.09f, 75.13f, -45.97f, 77.32f, -51.1f, 78.86f, -56.39f, + 79.7f, -61.74f, 79.84f, -67.07f, 79.3f, -72.3f, 78.15f, -77.39f, 76.48f, + -82.29f, 74.31f, -86.76f, 71.37f, -90.7f, 67.75f, -94.16f, 63.66f, -97.27f, + 59.3f, -100.21f, 54.81f, -103.09f, 50.3f, -106.03f, 45.82f, -109.11f, 41.44f, + -112.37f, 37.19f, -115.85f, 33.11f, -119.54f, 29.22f, -123.45f, 25.56f, -127.55f, + 22.11f, -131.77f, 18.81f, -136.04f, 15.57f, -140.34f, 12.37f, -144.62f, 9.15f, + -148.86f, 5.88f, -153.03f, 2.51f, -157.05f, -1.03f, -160.83f, -4.83f, -164.12f, + -9.05f, -166.71f, -13.73f, -168.91f, -18.62f, -170.77f, -23.64f, -172.3f, -28.78f, + -173.49f, -34.0f, -174.3f, -39.3f, -174.72f, -44.64f, -174.72f, -49.99f, -174.28f, + -55.33f, -173.37f, -60.61f, -172.0f, -65.79f, -170.17f, -70.82f, -167.79f, -75.62f, + -164.84f, -80.09f, -161.43f, -84.22f, -157.67f, -88.03f, -153.63f, -91.55f, -149.37f, + -94.81f, -144.94f, -97.82f, -140.37f, -100.61f, -135.65f, -103.16f, -130.73f, -105.26f, + -125.62f, -106.86f, -120.37f, -107.95f, -115.05f, -108.56f, -109.7f, -108.69f, -104.35f, + -108.36f, -99.05f, -107.6f, -93.82f, -106.41f, -88.72f, -104.79f, -83.78f, -102.7f, +}; + +constexpr std::array<float, 47 * 2> left_gc_trigger = { + -99.69f, -125.04f, -101.81f, -126.51f, -104.02f, -127.85f, -106.3f, -129.06f, -108.65f, + -130.12f, -111.08f, -130.99f, -113.58f, -131.62f, -116.14f, -131.97f, -121.26f, -131.55f, + -123.74f, -130.84f, -126.17f, -129.95f, -128.53f, -128.9f, -130.82f, -127.71f, -133.03f, + -126.38f, -135.15f, -124.92f, -137.18f, -123.32f, -139.11f, -121.6f, -140.91f, -119.75f, + -142.55f, -117.77f, -144.0f, -115.63f, -145.18f, -113.34f, -146.17f, -110.95f, -147.05f, + -108.53f, -147.87f, -106.08f, -148.64f, -103.61f, -149.37f, -101.14f, -149.16f, -100.12f, + -147.12f, -101.71f, -144.99f, -103.16f, -142.8f, -104.53f, -140.57f, -105.83f, -138.31f, + -107.08f, -136.02f, -108.27f, -133.71f, -109.42f, -131.38f, -110.53f, -129.04f, -111.61f, + -126.68f, -112.66f, -124.31f, -113.68f, -121.92f, -114.67f, -119.53f, -115.64f, -117.13f, + -116.58f, -114.72f, -117.51f, -112.3f, -118.41f, -109.87f, -119.29f, -107.44f, -120.16f, + -105.0f, -121.0f, -100.11f, -122.65f, +}; + +constexpr std::array<float, 50 * 2> gc_button_x = { + 142.1f, -50.67f, 142.44f, -48.65f, 142.69f, -46.62f, 142.8f, -44.57f, 143.0f, -42.54f, + 143.56f, -40.57f, 144.42f, -38.71f, 145.59f, -37.04f, 147.08f, -35.64f, 148.86f, -34.65f, + 150.84f, -34.11f, 152.88f, -34.03f, 154.89f, -34.38f, 156.79f, -35.14f, 158.49f, -36.28f, + 159.92f, -37.74f, 161.04f, -39.45f, 161.85f, -41.33f, 162.4f, -43.3f, 162.72f, -45.32f, + 162.85f, -47.37f, 162.82f, -49.41f, 162.67f, -51.46f, 162.39f, -53.48f, 162.0f, -55.5f, + 161.51f, -57.48f, 160.9f, -59.44f, 160.17f, -61.35f, 159.25f, -63.18f, 158.19f, -64.93f, + 157.01f, -66.61f, 155.72f, -68.2f, 154.31f, -69.68f, 152.78f, -71.04f, 151.09f, -72.2f, + 149.23f, -73.04f, 147.22f, -73.36f, 145.19f, -73.11f, 143.26f, -72.42f, 141.51f, -71.37f, + 140.0f, -69.99f, 138.82f, -68.32f, 138.13f, -66.4f, 138.09f, -64.36f, 138.39f, -62.34f, + 139.05f, -60.41f, 139.91f, -58.55f, 140.62f, -56.63f, 141.21f, -54.67f, 141.67f, -52.67f, +}; + +constexpr std::array<float, 50 * 2> gc_button_y = { + 104.02f, -75.23f, 106.01f, -75.74f, 108.01f, -76.15f, 110.04f, -76.42f, 112.05f, -76.78f, + 113.97f, -77.49f, 115.76f, -78.49f, 117.33f, -79.79f, 118.6f, -81.39f, 119.46f, -83.25f, + 119.84f, -85.26f, 119.76f, -87.3f, 119.24f, -89.28f, 118.33f, -91.11f, 117.06f, -92.71f, + 115.49f, -94.02f, 113.7f, -95.01f, 111.77f, -95.67f, 109.76f, -96.05f, 107.71f, -96.21f, + 105.67f, -96.18f, 103.63f, -95.99f, 101.61f, -95.67f, 99.61f, -95.24f, 97.63f, -94.69f, + 95.69f, -94.04f, 93.79f, -93.28f, 91.94f, -92.4f, 90.19f, -91.34f, 88.53f, -90.14f, + 86.95f, -88.84f, 85.47f, -87.42f, 84.1f, -85.9f, 82.87f, -84.26f, 81.85f, -82.49f, + 81.15f, -80.57f, 81.0f, -78.54f, 81.41f, -76.54f, 82.24f, -74.67f, 83.43f, -73.01f, + 84.92f, -71.61f, 86.68f, -70.57f, 88.65f, -70.03f, 90.69f, -70.15f, 92.68f, -70.61f, + 94.56f, -71.42f, 96.34f, -72.43f, 98.2f, -73.29f, 100.11f, -74.03f, 102.06f, -74.65f, +}; + +constexpr std::array<float, 47 * 2> gc_button_z = { + 95.74f, -126.41f, 98.34f, -126.38f, 100.94f, -126.24f, 103.53f, -126.01f, 106.11f, -125.7f, + 108.69f, -125.32f, 111.25f, -124.87f, 113.8f, -124.34f, 116.33f, -123.73f, 118.84f, -123.05f, + 121.33f, -122.3f, 123.79f, -121.47f, 126.23f, -120.56f, 128.64f, -119.58f, 131.02f, -118.51f, + 133.35f, -117.37f, 135.65f, -116.14f, 137.9f, -114.84f, 140.1f, -113.46f, 142.25f, -111.99f, + 144.35f, -110.45f, 146.38f, -108.82f, 148.35f, -107.13f, 150.25f, -105.35f, 151.89f, -103.38f, + 151.43f, -100.86f, 149.15f, -100.15f, 146.73f, -101.06f, 144.36f, -102.12f, 141.98f, -103.18f, + 139.6f, -104.23f, 137.22f, -105.29f, 134.85f, -106.35f, 132.47f, -107.41f, 127.72f, -109.53f, + 125.34f, -110.58f, 122.96f, -111.64f, 120.59f, -112.7f, 118.21f, -113.76f, 113.46f, -115.88f, + 111.08f, -116.93f, 108.7f, -117.99f, 106.33f, -119.05f, 103.95f, -120.11f, 99.2f, -122.23f, + 96.82f, -123.29f, 94.44f, -124.34f, +}; + +constexpr std::array<float, 84 * 2> left_joycon_body = { + -145.0f, -78.9f, -145.0f, -77.9f, -145.0f, 85.6f, -145.0f, 85.6f, -168.3f, 85.5f, + -169.3f, 85.4f, -171.3f, 85.1f, -172.3f, 84.9f, -173.4f, 84.7f, -174.3f, 84.5f, + -175.3f, 84.2f, -176.3f, 83.8f, -177.3f, 83.5f, -178.2f, 83.1f, -179.2f, 82.7f, + -180.1f, 82.2f, -181.0f, 81.8f, -181.9f, 81.3f, -182.8f, 80.7f, -183.7f, 80.2f, + -184.5f, 79.6f, -186.2f, 78.3f, -186.9f, 77.7f, -187.7f, 77.0f, -189.2f, 75.6f, + -189.9f, 74.8f, -190.6f, 74.1f, -191.3f, 73.3f, -191.9f, 72.5f, -192.5f, 71.6f, + -193.1f, 70.8f, -193.7f, 69.9f, -194.3f, 69.1f, -194.8f, 68.2f, -196.2f, 65.5f, + -196.6f, 64.5f, -197.0f, 63.6f, -197.4f, 62.6f, -198.1f, 60.7f, -198.4f, 59.7f, + -198.6f, 58.7f, -199.2f, 55.6f, -199.3f, 54.6f, -199.5f, 51.5f, -199.5f, 50.5f, + -199.5f, -49.4f, -199.4f, -50.5f, -199.3f, -51.5f, -199.1f, -52.5f, -198.2f, -56.5f, + -197.9f, -57.5f, -197.2f, -59.4f, -196.8f, -60.4f, -196.4f, -61.3f, -195.9f, -62.2f, + -194.3f, -64.9f, -193.7f, -65.7f, -193.1f, -66.6f, -192.5f, -67.4f, -191.8f, -68.2f, + -191.2f, -68.9f, -190.4f, -69.7f, -188.2f, -71.8f, -187.4f, -72.5f, -186.6f, -73.1f, + -185.8f, -73.8f, -185.0f, -74.4f, -184.1f, -74.9f, -183.2f, -75.5f, -182.4f, -76.0f, + -181.5f, -76.5f, -179.6f, -77.5f, -178.7f, -77.9f, -177.8f, -78.4f, -176.8f, -78.8f, + -175.9f, -79.1f, -174.9f, -79.5f, -173.9f, -79.8f, -170.9f, -80.6f, -169.9f, -80.8f, + -167.9f, -81.1f, -166.9f, -81.2f, -165.8f, -81.2f, -145.0f, -80.9f, +}; + +constexpr std::array<float, 84 * 2> left_joycon_trigger = { + -166.8f, -83.3f, -167.9f, -83.2f, -168.9f, -83.1f, -170.0f, -83.0f, -171.0f, -82.8f, + -172.1f, -82.6f, -173.1f, -82.4f, -174.2f, -82.1f, -175.2f, -81.9f, -176.2f, -81.5f, + -177.2f, -81.2f, -178.2f, -80.8f, -180.1f, -80.0f, -181.1f, -79.5f, -182.0f, -79.0f, + -183.0f, -78.5f, -183.9f, -78.0f, -184.8f, -77.4f, -185.7f, -76.9f, -186.6f, -76.3f, + -187.4f, -75.6f, -188.3f, -75.0f, -189.1f, -74.3f, -192.2f, -71.5f, -192.9f, -70.7f, + -193.7f, -69.9f, -194.3f, -69.1f, -195.0f, -68.3f, -195.6f, -67.4f, -196.8f, -65.7f, + -197.3f, -64.7f, -197.8f, -63.8f, -198.2f, -62.8f, -198.9f, -60.8f, -198.6f, -59.8f, + -197.6f, -59.7f, -196.6f, -60.0f, -195.6f, -60.5f, -194.7f, -60.9f, -193.7f, -61.4f, + -192.8f, -61.9f, -191.8f, -62.4f, -190.9f, -62.8f, -189.9f, -63.3f, -189.0f, -63.8f, + -187.1f, -64.8f, -186.2f, -65.2f, -185.2f, -65.7f, -184.3f, -66.2f, -183.3f, -66.7f, + -182.4f, -67.1f, -181.4f, -67.6f, -180.5f, -68.1f, -179.5f, -68.6f, -178.6f, -69.0f, + -177.6f, -69.5f, -176.7f, -70.0f, -175.7f, -70.5f, -174.8f, -70.9f, -173.8f, -71.4f, + -172.9f, -71.9f, -171.9f, -72.4f, -171.0f, -72.8f, -170.0f, -73.3f, -169.1f, -73.8f, + -168.1f, -74.3f, -167.2f, -74.7f, -166.2f, -75.2f, -165.3f, -75.7f, -164.3f, -76.2f, + -163.4f, -76.6f, -162.4f, -77.1f, -161.5f, -77.6f, -160.5f, -78.1f, -159.6f, -78.5f, + -158.7f, -79.0f, -157.7f, -79.5f, -156.8f, -80.0f, -155.8f, -80.4f, -154.9f, -80.9f, + -154.2f, -81.6f, -154.3f, -82.6f, -155.2f, -83.3f, -156.2f, -83.3f, +}; + +constexpr std::array<float, 70 * 2> handheld_body = { + -137.3f, -81.9f, -137.6f, -81.8f, -137.8f, -81.6f, -138.0f, -81.3f, -138.1f, -81.1f, + -138.1f, -80.8f, -138.2f, -78.7f, -138.2f, -78.4f, -138.3f, -78.1f, -138.7f, -77.3f, + -138.9f, -77.0f, -139.0f, -76.8f, -139.2f, -76.5f, -139.5f, -76.3f, -139.7f, -76.1f, + -139.9f, -76.0f, -140.2f, -75.8f, -140.5f, -75.7f, -140.7f, -75.6f, -141.0f, -75.5f, + -141.9f, -75.3f, -142.2f, -75.3f, -142.5f, -75.2f, -143.0f, -74.9f, -143.2f, -74.7f, + -143.3f, -74.4f, -143.0f, -74.1f, -143.0f, 85.3f, -143.0f, 85.6f, -142.7f, 85.8f, + -142.4f, 85.9f, -142.2f, 85.9f, 143.0f, 85.6f, 143.1f, 85.4f, 143.3f, 85.1f, + 143.0f, 84.8f, 143.0f, -74.9f, 142.8f, -75.1f, 142.5f, -75.2f, 141.9f, -75.3f, + 141.6f, -75.3f, 141.3f, -75.4f, 141.1f, -75.4f, 140.8f, -75.5f, 140.5f, -75.7f, + 140.2f, -75.8f, 140.0f, -76.0f, 139.7f, -76.1f, 139.5f, -76.3f, 139.1f, -76.8f, + 138.9f, -77.0f, 138.6f, -77.5f, 138.4f, -77.8f, 138.3f, -78.1f, 138.3f, -78.3f, + 138.2f, -78.6f, 138.2f, -78.9f, 138.1f, -79.2f, 138.1f, -79.5f, 138.0f, -81.3f, + 137.8f, -81.6f, 137.6f, -81.8f, 137.3f, -81.9f, 137.1f, -81.9f, 120.0f, -70.0f, + -120.0f, -70.0f, -120.0f, 70.0f, 120.0f, 70.0f, 120.0f, -70.0f, 137.1f, -81.9f, +}; + +constexpr std::array<float, 40 * 2> handheld_bezel = { + -131.4f, -75.9f, -132.2f, -75.7f, -132.9f, -75.3f, -134.2f, -74.3f, -134.7f, -73.6f, + -135.1f, -72.8f, -135.4f, -72.0f, -135.5f, -71.2f, -135.5f, -70.4f, -135.2f, 76.7f, + -134.8f, 77.5f, -134.3f, 78.1f, -133.7f, 78.8f, -133.1f, 79.2f, -132.3f, 79.6f, + -131.5f, 79.9f, -130.7f, 80.0f, -129.8f, 80.0f, 132.2f, 79.7f, 133.0f, 79.3f, + 133.7f, 78.8f, 134.3f, 78.3f, 134.8f, 77.6f, 135.1f, 76.8f, 135.5f, 75.2f, + 135.5f, 74.3f, 135.2f, -72.7f, 134.8f, -73.5f, 134.4f, -74.2f, 133.8f, -74.8f, + 133.1f, -75.3f, 132.3f, -75.6f, 130.7f, -76.0f, 129.8f, -76.0f, -112.9f, -62.2f, + 112.9f, -62.2f, 112.9f, 62.2f, -112.9f, 62.2f, -112.9f, -62.2f, 129.8f, -76.0f, +}; + +constexpr std::array<float, 58 * 2> handheld_buttons = { + -82.48f, -82.95f, -82.53f, -82.95f, -106.69f, -82.96f, -106.73f, -82.98f, -106.78f, -83.01f, + -106.81f, -83.05f, -106.83f, -83.1f, -106.83f, -83.15f, -106.82f, -83.93f, -106.81f, -83.99f, + -106.8f, -84.04f, -106.78f, -84.08f, -106.76f, -84.13f, -106.73f, -84.18f, -106.7f, -84.22f, + -106.6f, -84.34f, -106.56f, -84.37f, -106.51f, -84.4f, -106.47f, -84.42f, -106.42f, -84.45f, + -106.37f, -84.47f, -106.32f, -84.48f, -106.17f, -84.5f, -98.9f, -84.48f, -98.86f, -84.45f, + -98.83f, -84.41f, -98.81f, -84.36f, -98.8f, -84.31f, -98.8f, -84.26f, -98.79f, -84.05f, + -90.26f, -84.1f, -90.26f, -84.15f, -90.25f, -84.36f, -90.23f, -84.41f, -90.2f, -84.45f, + -90.16f, -84.48f, -90.11f, -84.5f, -82.79f, -84.49f, -82.74f, -84.48f, -82.69f, -84.46f, + -82.64f, -84.45f, -82.59f, -84.42f, -82.55f, -84.4f, -82.5f, -84.37f, -82.46f, -84.33f, + -82.42f, -84.3f, -82.39f, -84.26f, -82.3f, -84.13f, -82.28f, -84.08f, -82.25f, -83.98f, + -82.24f, -83.93f, -82.23f, -83.83f, -82.23f, -83.78f, -82.24f, -83.1f, -82.26f, -83.05f, + -82.29f, -83.01f, -82.33f, -82.97f, -82.38f, -82.95f, +}; + +constexpr std::array<float, 47 * 2> left_joycon_slider = { + -23.7f, -118.2f, -23.7f, -117.3f, -23.7f, 96.6f, -22.8f, 96.6f, -21.5f, 97.2f, -21.5f, + 98.1f, -21.2f, 106.7f, -20.8f, 107.5f, -20.1f, 108.2f, -19.2f, 108.2f, -16.4f, 108.1f, + -15.8f, 107.5f, -15.8f, 106.5f, -15.8f, 62.8f, -16.3f, 61.9f, -15.8f, 61.0f, -17.3f, + 60.3f, -19.1f, 58.9f, -19.1f, 58.1f, -19.1f, 57.2f, -19.1f, 34.5f, -17.9f, 33.9f, + -17.2f, 33.2f, -16.6f, 32.4f, -16.2f, 31.6f, -15.8f, 30.7f, -15.8f, 29.7f, -15.8f, + 28.8f, -15.8f, -46.4f, -16.3f, -47.3f, -15.8f, -48.1f, -17.4f, -48.8f, -19.1f, -49.4f, + -19.1f, -50.1f, -19.1f, -51.0f, -19.1f, -51.9f, -19.1f, -73.7f, -19.1f, -74.5f, -17.5f, + -75.2f, -16.4f, -76.7f, -16.0f, -77.6f, -15.8f, -78.5f, -15.8f, -79.4f, -15.8f, -80.4f, + -15.8f, -118.2f, -15.8f, -118.2f, -18.3f, -118.2f, +}; + +constexpr std::array<float, 66 * 2> left_joycon_sideview = { + -158.8f, -133.5f, -159.8f, -133.5f, -173.5f, -133.3f, -174.5f, -133.0f, -175.4f, -132.6f, + -176.2f, -132.1f, -177.0f, -131.5f, -177.7f, -130.9f, -178.3f, -130.1f, -179.4f, -128.5f, + -179.8f, -127.6f, -180.4f, -125.7f, -180.6f, -124.7f, -180.7f, -123.8f, -180.7f, -122.8f, + -180.0f, 128.8f, -179.6f, 129.7f, -179.1f, 130.5f, -177.9f, 132.1f, -177.2f, 132.7f, + -176.4f, 133.3f, -175.6f, 133.8f, -174.7f, 134.3f, -173.8f, 134.6f, -172.8f, 134.8f, + -170.9f, 135.0f, -169.9f, 135.0f, -156.1f, 134.8f, -155.2f, 134.6f, -154.2f, 134.3f, + -153.3f, 134.0f, -152.4f, 133.6f, -151.6f, 133.1f, -150.7f, 132.6f, -149.9f, 132.0f, + -149.2f, 131.4f, -148.5f, 130.7f, -147.1f, 129.2f, -146.5f, 128.5f, -146.0f, 127.7f, + -145.5f, 126.8f, -145.0f, 126.0f, -144.6f, 125.1f, -144.2f, 124.1f, -143.9f, 123.2f, + -143.7f, 122.2f, -143.6f, 121.3f, -143.5f, 120.3f, -143.5f, 119.3f, -144.4f, -123.4f, + -144.8f, -124.3f, -145.3f, -125.1f, -145.8f, -126.0f, -146.3f, -126.8f, -147.0f, -127.5f, + -147.6f, -128.3f, -148.3f, -129.0f, -149.0f, -129.6f, -149.8f, -130.3f, -150.6f, -130.8f, + -151.4f, -131.4f, -152.2f, -131.9f, -153.1f, -132.3f, -155.9f, -133.3f, -156.8f, -133.5f, + -157.8f, -133.5f, +}; + +constexpr std::array<float, 40 * 2> left_joycon_body_trigger = { + -146.1f, -124.3f, -146.0f, -122.0f, -145.8f, -119.7f, -145.7f, -117.4f, -145.4f, -112.8f, + -145.3f, -110.5f, -145.0f, -105.9f, -144.9f, -103.6f, -144.6f, -99.1f, -144.5f, -96.8f, + -144.5f, -89.9f, -144.5f, -87.6f, -144.5f, -83.0f, -144.5f, -80.7f, -144.5f, -80.3f, + -142.4f, -82.4f, -141.4f, -84.5f, -140.2f, -86.4f, -138.8f, -88.3f, -137.4f, -90.1f, + -134.5f, -93.6f, -133.0f, -95.3f, -130.0f, -98.8f, -128.5f, -100.6f, -127.1f, -102.4f, + -125.8f, -104.3f, -124.7f, -106.3f, -123.9f, -108.4f, -125.1f, -110.2f, -127.4f, -110.3f, + -129.7f, -110.3f, -134.2f, -110.5f, -136.4f, -111.4f, -138.1f, -112.8f, -139.4f, -114.7f, + -140.5f, -116.8f, -141.4f, -118.9f, -143.3f, -123.1f, -144.6f, -124.9f, -146.2f, -126.0f, +}; + +constexpr std::array<float, 49 * 2> left_joycon_topview = { + -184.8f, -20.8f, -185.6f, -21.1f, -186.4f, -21.5f, -187.1f, -22.1f, -187.8f, -22.6f, + -188.4f, -23.2f, -189.6f, -24.5f, -190.2f, -25.2f, -190.7f, -25.9f, -191.1f, -26.7f, + -191.4f, -27.5f, -191.6f, -28.4f, -191.7f, -29.2f, -191.7f, -30.1f, -191.5f, -47.7f, + -191.2f, -48.5f, -191.0f, -49.4f, -190.7f, -50.2f, -190.3f, -51.0f, -190.0f, -51.8f, + -189.6f, -52.6f, -189.1f, -53.4f, -188.6f, -54.1f, -187.5f, -55.4f, -186.9f, -56.1f, + -186.2f, -56.7f, -185.5f, -57.2f, -184.0f, -58.1f, -183.3f, -58.5f, -182.5f, -58.9f, + -181.6f, -59.2f, -180.8f, -59.5f, -179.9f, -59.7f, -179.1f, -59.9f, -178.2f, -60.0f, + -174.7f, -60.1f, -168.5f, -60.2f, -162.4f, -60.3f, -156.2f, -60.4f, -149.2f, -60.5f, + -143.0f, -60.6f, -136.9f, -60.7f, -130.7f, -60.8f, -123.7f, -60.9f, -117.5f, -61.0f, + -110.5f, -61.1f, -94.4f, -60.4f, -94.4f, -59.5f, -94.4f, -20.6f, +}; + +constexpr std::array<float, 41 * 2> left_joycon_slider_topview = { + -95.1f, -51.5f, -95.0f, -51.5f, -91.2f, -51.6f, -91.2f, -51.7f, -91.1f, -52.4f, -91.1f, -52.6f, + -91.0f, -54.1f, -86.3f, -54.0f, -86.0f, -53.9f, -85.9f, -53.8f, -85.6f, -53.4f, -85.5f, -53.2f, + -85.5f, -53.1f, -85.4f, -52.9f, -85.4f, -52.8f, -85.3f, -52.4f, -85.3f, -52.3f, -85.4f, -27.2f, + -85.4f, -27.1f, -85.5f, -27.0f, -85.5f, -26.9f, -85.6f, -26.7f, -85.6f, -26.6f, -85.7f, -26.5f, + -85.9f, -26.4f, -86.0f, -26.3f, -86.4f, -26.0f, -86.5f, -25.9f, -86.7f, -25.8f, -87.1f, -25.7f, + -90.4f, -25.8f, -90.7f, -25.9f, -90.8f, -26.0f, -90.9f, -26.3f, -91.0f, -26.4f, -91.0f, -26.5f, + -91.1f, -26.7f, -91.1f, -26.9f, -91.2f, -28.9f, -95.2f, -29.1f, -95.2f, -29.2f, +}; + +constexpr std::array<float, 42 * 2> left_joycon_sideview_zl = { + -148.9f, -128.2f, -148.7f, -126.6f, -148.4f, -124.9f, -148.2f, -123.3f, -147.9f, -121.7f, + -147.7f, -120.1f, -147.4f, -118.5f, -147.2f, -116.9f, -146.9f, -115.3f, -146.4f, -112.1f, + -146.1f, -110.5f, -145.9f, -108.9f, -145.6f, -107.3f, -144.2f, -107.3f, -142.6f, -107.5f, + -141.0f, -107.8f, -137.8f, -108.3f, -136.2f, -108.6f, -131.4f, -109.4f, -129.8f, -109.7f, + -125.6f, -111.4f, -124.5f, -112.7f, -123.9f, -114.1f, -123.8f, -115.8f, -123.8f, -117.4f, + -123.9f, -120.6f, -124.5f, -122.1f, -125.8f, -123.1f, -127.4f, -123.4f, -129.0f, -123.6f, + -130.6f, -124.0f, -132.1f, -124.4f, -133.7f, -124.8f, -135.3f, -125.3f, -136.8f, -125.9f, + -138.3f, -126.4f, -139.9f, -126.9f, -141.4f, -127.5f, -142.9f, -128.0f, -144.5f, -128.5f, + -146.0f, -129.0f, -147.6f, -129.4f, +}; + +constexpr std::array<float, 72 * 2> left_joystick_sideview = { + -14.7f, -3.8f, -15.2f, -5.6f, -15.2f, -7.6f, -15.5f, -17.6f, -17.4f, -18.3f, -19.4f, -18.2f, + -21.3f, -17.6f, -22.8f, -16.4f, -23.4f, -14.5f, -23.4f, -12.5f, -24.1f, -8.6f, -24.8f, -6.7f, + -25.3f, -4.8f, -25.7f, -2.8f, -25.9f, -0.8f, -26.0f, 1.2f, -26.0f, 3.2f, -25.8f, 5.2f, + -25.5f, 7.2f, -25.0f, 9.2f, -24.4f, 11.1f, -23.7f, 13.0f, -23.4f, 14.9f, -23.4f, 16.9f, + -23.3f, 18.9f, -22.0f, 20.5f, -20.2f, 21.3f, -18.3f, 21.6f, -16.3f, 21.4f, -15.3f, 19.9f, + -15.3f, 17.8f, -15.2f, 7.8f, -13.5f, 6.4f, -12.4f, 7.2f, -11.4f, 8.9f, -10.2f, 10.5f, + -8.7f, 11.8f, -7.1f, 13.0f, -5.3f, 14.0f, -3.5f, 14.7f, -1.5f, 15.0f, 0.5f, 15.0f, + 2.5f, 14.7f, 4.4f, 14.2f, 6.3f, 13.4f, 8.0f, 12.4f, 9.6f, 11.1f, 10.9f, 9.6f, + 12.0f, 7.9f, 12.7f, 6.0f, 13.2f, 4.1f, 13.3f, 2.1f, 13.2f, 0.1f, 12.9f, -1.9f, + 12.2f, -3.8f, 11.3f, -5.6f, 10.2f, -7.2f, 8.8f, -8.6f, 7.1f, -9.8f, 5.4f, -10.8f, + 3.5f, -11.5f, 1.5f, -11.9f, -0.5f, -12.0f, -2.5f, -11.8f, -4.4f, -11.3f, -6.2f, -10.4f, + -8.0f, -9.4f, -9.6f, -8.2f, -10.9f, -6.7f, -11.9f, -4.9f, -12.8f, -3.2f, -13.5f, -3.8f, +}; + +constexpr std::array<float, 63 * 2> left_joystick_L_topview = { + -186.7f, -43.7f, -186.4f, -43.7f, -110.6f, -43.4f, -110.6f, -43.1f, -110.7f, -34.3f, + -110.7f, -34.0f, -110.8f, -33.7f, -111.1f, -32.9f, -111.2f, -32.6f, -111.4f, -32.3f, + -111.5f, -32.1f, -111.7f, -31.8f, -111.8f, -31.5f, -112.0f, -31.3f, -112.2f, -31.0f, + -112.4f, -30.8f, -112.8f, -30.3f, -113.0f, -30.1f, -114.1f, -29.1f, -114.3f, -28.9f, + -114.6f, -28.7f, -114.8f, -28.6f, -115.1f, -28.4f, -115.3f, -28.3f, -115.6f, -28.1f, + -115.9f, -28.0f, -116.4f, -27.8f, -116.7f, -27.7f, -117.3f, -27.6f, -117.6f, -27.5f, + -182.9f, -27.6f, -183.5f, -27.7f, -183.8f, -27.8f, -184.4f, -27.9f, -184.6f, -28.1f, + -184.9f, -28.2f, -185.4f, -28.5f, -185.7f, -28.7f, -185.9f, -28.8f, -186.2f, -29.0f, + -186.4f, -29.2f, -187.0f, -29.9f, -187.2f, -30.1f, -187.6f, -30.6f, -187.8f, -30.8f, + -187.9f, -31.1f, -188.1f, -31.3f, -188.2f, -31.6f, -188.4f, -31.9f, -188.5f, -32.1f, + -188.6f, -32.4f, -188.8f, -33.3f, -188.9f, -33.6f, -188.9f, -33.9f, -188.8f, -39.9f, + -188.8f, -40.2f, -188.7f, -41.1f, -188.7f, -41.4f, -188.6f, -41.7f, -188.0f, -43.1f, + -187.9f, -43.4f, -187.6f, -43.6f, -187.3f, -43.7f, +}; + +constexpr std::array<float, 44 * 2> left_joystick_ZL_topview = { + -179.4f, -53.3f, -177.4f, -53.3f, -111.2f, -53.3f, -111.3f, -53.3f, -111.5f, -58.6f, + -111.8f, -60.5f, -112.2f, -62.4f, -113.1f, -66.1f, -113.8f, -68.0f, -114.5f, -69.8f, + -115.3f, -71.5f, -116.3f, -73.2f, -117.3f, -74.8f, -118.5f, -76.4f, -119.8f, -77.8f, + -121.2f, -79.1f, -122.8f, -80.2f, -124.4f, -81.2f, -126.2f, -82.0f, -128.1f, -82.6f, + -130.0f, -82.9f, -131.9f, -83.0f, -141.5f, -82.9f, -149.3f, -82.8f, -153.1f, -82.6f, + -155.0f, -82.1f, -156.8f, -81.6f, -158.7f, -80.9f, -160.4f, -80.2f, -162.2f, -79.3f, + -163.8f, -78.3f, -165.4f, -77.2f, -166.9f, -76.0f, -168.4f, -74.7f, -169.7f, -73.3f, + -172.1f, -70.3f, -173.2f, -68.7f, -174.2f, -67.1f, -175.2f, -65.4f, -176.1f, -63.7f, + -178.7f, -58.5f, -179.6f, -56.8f, -180.4f, -55.1f, -181.3f, -53.3f, +}; + +void PlayerControlPreview::DrawProBody(QPainter& p, const QPointF center) { + std::array<QPointF, pro_left_handle.size() / 2> qleft_handle; + std::array<QPointF, pro_left_handle.size() / 2> qright_handle; + std::array<QPointF, pro_body.size()> qbody; + constexpr int radius1 = 32; + + for (std::size_t point = 0; point < pro_left_handle.size() / 2; ++point) { + const float left_x = pro_left_handle[point * 2 + 0]; + const float left_y = pro_left_handle[point * 2 + 1]; + + qleft_handle[point] = center + QPointF(left_x, left_y); + qright_handle[point] = center + QPointF(-left_x, left_y); + } + for (std::size_t point = 0; point < pro_body.size() / 2; ++point) { + const float body_x = pro_body[point * 2 + 0]; + const float body_y = pro_body[point * 2 + 1]; + + qbody[point] = center + QPointF(body_x, body_y); + qbody[pro_body.size() - 1 - point] = center + QPointF(-body_x, body_y); + } + + // Draw left handle body + p.setPen(colors.outline); + p.setBrush(colors.left); + DrawPolygon(p, qleft_handle); + + // Draw right handle body + p.setBrush(colors.right); + DrawPolygon(p, qright_handle); + + // Draw body + p.setBrush(colors.primary); + DrawPolygon(p, qbody); + + // Draw joycon circles + p.setBrush(colors.transparent); + p.drawEllipse(center + QPoint(-111, -55), radius1, radius1); + p.drawEllipse(center + QPoint(51, 0), radius1, radius1); +} + +void PlayerControlPreview::DrawGCBody(QPainter& p, const QPointF center) { + std::array<QPointF, gc_left_body.size() / 2> qleft_handle; + std::array<QPointF, gc_left_body.size() / 2> qright_handle; + std::array<QPointF, gc_body.size()> qbody; + std::array<QPointF, 8> left_hex; + std::array<QPointF, 8> right_hex; + constexpr float angle = 2 * 3.1415f / 8; + + for (std::size_t point = 0; point < gc_left_body.size() / 2; ++point) { + const float body_x = gc_left_body[point * 2 + 0]; + const float body_y = gc_left_body[point * 2 + 1]; + + qleft_handle[point] = center + QPointF(body_x, body_y); + qright_handle[point] = center + QPointF(-body_x, body_y); + } + for (std::size_t point = 0; point < gc_body.size() / 2; ++point) { + const float body_x = gc_body[point * 2 + 0]; + const float body_y = gc_body[point * 2 + 1]; + + qbody[point] = center + QPointF(body_x, body_y); + qbody[gc_body.size() - 1 - point] = center + QPointF(-body_x, body_y); + } + for (std::size_t point = 0; point < 8; ++point) { + const float point_cos = std::cos(point * angle); + const float point_sin = std::sin(point * angle); + + left_hex[point] = center + QPointF(34 * point_cos - 111, 34 * point_sin - 44); + right_hex[point] = center + QPointF(26 * point_cos + 61, 26 * point_sin + 37); + } + + // Draw body + p.setPen(colors.outline); + p.setBrush(colors.primary); + DrawPolygon(p, qbody); + + // Draw left handle body + p.setBrush(colors.left); + DrawPolygon(p, qleft_handle); + + // Draw right handle body + p.setBrush(colors.right); + DrawPolygon(p, qright_handle); + + DrawText(p, center + QPoint(0, -58), 4.7f, tr("START/PAUSE")); + + // Draw right joystick body + p.setBrush(colors.button); + DrawCircle(p, center + QPointF(61, 37), 23.5f); + + // Draw joystick details + p.setBrush(colors.transparent); + DrawPolygon(p, left_hex); + DrawPolygon(p, right_hex); +} + +void PlayerControlPreview::DrawHandheldBody(QPainter& p, const QPointF center) { + const std::size_t body_outline_end = handheld_body.size() / 2 - 6; + const std::size_t bezel_outline_end = handheld_bezel.size() / 2 - 6; + const std::size_t bezel_inline_size = 4; + const std::size_t bezel_inline_start = 35; + std::array<QPointF, left_joycon_body.size() / 2> left_joycon; + std::array<QPointF, left_joycon_body.size() / 2> right_joycon; + std::array<QPointF, handheld_body.size() / 2> qhandheld_body; + std::array<QPointF, body_outline_end> qhandheld_body_outline; + std::array<QPointF, handheld_bezel.size() / 2> qhandheld_bezel; + std::array<QPointF, bezel_inline_size> qhandheld_bezel_inline; + std::array<QPointF, bezel_outline_end> qhandheld_bezel_outline; + std::array<QPointF, handheld_buttons.size() / 2> qhandheld_buttons; + + for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) { + left_joycon[point] = + center + QPointF(left_joycon_body[point * 2], left_joycon_body[point * 2 + 1]); + right_joycon[point] = + center + QPointF(-left_joycon_body[point * 2], left_joycon_body[point * 2 + 1]); + } + for (std::size_t point = 0; point < body_outline_end; ++point) { + qhandheld_body_outline[point] = + center + QPointF(handheld_body[point * 2], handheld_body[point * 2 + 1]); + } + for (std::size_t point = 0; point < handheld_body.size() / 2; ++point) { + qhandheld_body[point] = + center + QPointF(handheld_body[point * 2], handheld_body[point * 2 + 1]); + } + for (std::size_t point = 0; point < handheld_bezel.size() / 2; ++point) { + qhandheld_bezel[point] = + center + QPointF(handheld_bezel[point * 2], handheld_bezel[point * 2 + 1]); + } + for (std::size_t point = 0; point < bezel_outline_end; ++point) { + qhandheld_bezel_outline[point] = + center + QPointF(handheld_bezel[point * 2], handheld_bezel[point * 2 + 1]); + } + for (std::size_t point = 0; point < bezel_inline_size; ++point) { + qhandheld_bezel_inline[point] = + center + QPointF(handheld_bezel[(point + bezel_inline_start) * 2], + handheld_bezel[(point + bezel_inline_start) * 2 + 1]); + } + for (std::size_t point = 0; point < handheld_buttons.size() / 2; ++point) { + qhandheld_buttons[point] = + center + QPointF(handheld_buttons[point * 2], handheld_buttons[point * 2 + 1]); + } + + // Draw left joycon + p.setPen(colors.outline); + p.setBrush(colors.left); + DrawPolygon(p, left_joycon); + + // Draw right joycon + p.setPen(colors.outline); + p.setBrush(colors.right); + DrawPolygon(p, right_joycon); + + // Draw Handheld buttons + p.setPen(colors.outline); + p.setBrush(colors.button); + DrawPolygon(p, qhandheld_buttons); + + // Draw handheld body + p.setPen(colors.transparent); + p.setBrush(colors.primary); + DrawPolygon(p, qhandheld_body); + p.setPen(colors.outline); + p.setBrush(colors.transparent); + DrawPolygon(p, qhandheld_body_outline); + + // Draw Handheld bezel + p.setPen(colors.transparent); + p.setBrush(colors.button); + DrawPolygon(p, qhandheld_bezel); + p.setPen(colors.outline); + p.setBrush(colors.transparent); + DrawPolygon(p, qhandheld_bezel_outline); + DrawPolygon(p, qhandheld_bezel_inline); +} + +void PlayerControlPreview::DrawDualBody(QPainter& p, const QPointF center) { + std::array<QPointF, left_joycon_body.size() / 2> left_joycon; + std::array<QPointF, left_joycon_body.size() / 2> right_joycon; + std::array<QPointF, left_joycon_slider.size() / 2> qleft_joycon_slider; + std::array<QPointF, left_joycon_slider.size() / 2> qright_joycon_slider; + std::array<QPointF, left_joycon_slider_topview.size() / 2> qleft_joycon_slider_topview; + std::array<QPointF, left_joycon_slider_topview.size() / 2> qright_joycon_slider_topview; + std::array<QPointF, left_joycon_topview.size() / 2> qleft_joycon_topview; + std::array<QPointF, left_joycon_topview.size() / 2> qright_joycon_topview; + constexpr float size = 1.61f; + constexpr float size2 = 0.9f; + constexpr float offset = 209.3f; + + for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) { + const float body_x = left_joycon_body[point * 2 + 0]; + const float body_y = left_joycon_body[point * 2 + 1]; + + left_joycon[point] = center + QPointF(body_x * size + offset, body_y * size - 1); + right_joycon[point] = center + QPointF(-body_x * size - offset, body_y * size - 1); + } + for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) { + const float slider_x = left_joycon_slider[point * 2 + 0]; + const float slider_y = left_joycon_slider[point * 2 + 1]; + + qleft_joycon_slider[point] = center + QPointF(slider_x, slider_y); + qright_joycon_slider[point] = center + QPointF(-slider_x, slider_y); + } + for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) { + const float top_view_x = left_joycon_topview[point * 2 + 0]; + const float top_view_y = left_joycon_topview[point * 2 + 1]; + + qleft_joycon_topview[point] = + center + QPointF(top_view_x * size2 - 52, top_view_y * size2 - 52); + qright_joycon_topview[point] = + center + QPointF(-top_view_x * size2 + 52, top_view_y * size2 - 52); + } + for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) { + const float top_view_x = left_joycon_slider_topview[point * 2 + 0]; + const float top_view_y = left_joycon_slider_topview[point * 2 + 1]; + + qleft_joycon_slider_topview[point] = + center + QPointF(top_view_x * size2 - 52, top_view_y * size2 - 52); + qright_joycon_slider_topview[point] = + center + QPointF(-top_view_x * size2 + 52, top_view_y * size2 - 52); + } + + // right joycon body + p.setPen(colors.outline); + p.setBrush(colors.right); + DrawPolygon(p, right_joycon); + + // Left joycon body + p.setPen(colors.outline); + p.setBrush(colors.left); + DrawPolygon(p, left_joycon); + + // Slider release button top view + p.setBrush(colors.button); + DrawRoundRectangle(p, center + QPoint(-149, -108), 12, 11, 2); + DrawRoundRectangle(p, center + QPoint(149, -108), 12, 11, 2); + + // Joycon slider top view + p.setBrush(colors.slider); + DrawPolygon(p, qleft_joycon_slider_topview); + p.drawLine(center + QPointF(-133.8f, -99.0f), center + QPointF(-133.8f, -78.5f)); + DrawPolygon(p, qright_joycon_slider_topview); + p.drawLine(center + QPointF(133.8f, -99.0f), center + QPointF(133.8f, -78.5f)); + + // Joycon body top view + p.setBrush(colors.left); + DrawPolygon(p, qleft_joycon_topview); + p.setBrush(colors.right); + DrawPolygon(p, qright_joycon_topview); + + // Right SR and SL sideview buttons + p.setPen(colors.outline); + p.setBrush(colors.slider_button); + DrawRoundRectangle(p, center + QPoint(19, 47), 7, 22, 1); + DrawRoundRectangle(p, center + QPoint(19, -62), 7, 22, 1); + + // Left SR and SL sideview buttons + DrawRoundRectangle(p, center + QPoint(-19, 47), 7, 22, 1); + DrawRoundRectangle(p, center + QPoint(-19, -62), 7, 22, 1); + + // Right Sideview body + p.setBrush(colors.slider); + DrawPolygon(p, qright_joycon_slider); + + // Left Sideview body + p.setBrush(colors.slider); + DrawPolygon(p, qleft_joycon_slider); +} + +void PlayerControlPreview::DrawLeftBody(QPainter& p, const QPointF center) { + std::array<QPointF, left_joycon_body.size() / 2> left_joycon; + std::array<QPointF, left_joycon_sideview.size() / 2> qleft_joycon_sideview; + std::array<QPointF, left_joycon_body_trigger.size() / 2> qleft_joycon_trigger; + std::array<QPointF, left_joycon_slider.size() / 2> qleft_joycon_slider; + std::array<QPointF, left_joycon_slider_topview.size() / 2> qleft_joycon_slider_topview; + std::array<QPointF, left_joycon_topview.size() / 2> qleft_joycon_topview; + constexpr float size = 1.78f; + constexpr float size2 = 1.1115f; + constexpr float offset = 312.39f; + constexpr float offset2 = 335; + + for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) { + left_joycon[point] = center + QPointF(left_joycon_body[point * 2] * size + offset, + left_joycon_body[point * 2 + 1] * size - 1); + } + + for (std::size_t point = 0; point < left_joycon_sideview.size() / 2; ++point) { + qleft_joycon_sideview[point] = + center + QPointF(left_joycon_sideview[point * 2] * size2 + offset2, + left_joycon_sideview[point * 2 + 1] * size2 + 2); + } + for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) { + qleft_joycon_slider[point] = center + QPointF(left_joycon_slider[point * 2] * size2 + 81, + left_joycon_slider[point * 2 + 1] * size2); + } + for (std::size_t point = 0; point < left_joycon_body_trigger.size() / 2; ++point) { + qleft_joycon_trigger[point] = + center + QPointF(left_joycon_body_trigger[point * 2] * size2 + offset2, + left_joycon_body_trigger[point * 2 + 1] * size2 + 2); + } + for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) { + qleft_joycon_topview[point] = + center + QPointF(left_joycon_topview[point * 2], left_joycon_topview[point * 2 + 1]); + } + for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) { + qleft_joycon_slider_topview[point] = + center + QPointF(left_joycon_slider_topview[point * 2], + left_joycon_slider_topview[point * 2 + 1]); + } + + // Joycon body + p.setPen(colors.outline); + p.setBrush(colors.left); + DrawPolygon(p, left_joycon); + DrawPolygon(p, qleft_joycon_trigger); + + // Slider release button top view + p.setBrush(colors.button); + DrawRoundRectangle(p, center + QPoint(-107, -62), 14, 12, 2); + + // Joycon slider top view + p.setBrush(colors.slider); + DrawPolygon(p, qleft_joycon_slider_topview); + p.drawLine(center + QPointF(-91.1f, -51.7f), center + QPointF(-91.1f, -26.5f)); + + // Joycon body top view + p.setBrush(colors.left); + DrawPolygon(p, qleft_joycon_topview); + + // Slider release button + p.setBrush(colors.button); + DrawRoundRectangle(p, center + QPoint(175, -110), 12, 14, 2); + + // Sideview body + p.setBrush(colors.left); + DrawPolygon(p, qleft_joycon_sideview); + p.setBrush(colors.slider); + DrawPolygon(p, qleft_joycon_slider); + + const QPointF sideview_center = QPointF(155, 0) + center; + + // Sideview slider body + p.setBrush(colors.slider); + DrawRoundRectangle(p, sideview_center + QPointF(0, -5), 28, 253, 3); + p.setBrush(colors.button2); + DrawRoundRectangle(p, sideview_center + QPointF(0, 97), 22.44f, 44.66f, 3); + + // Slider decorations + p.setPen(colors.outline); + p.setBrush(colors.slider_arrow); + DrawArrow(p, sideview_center + QPoint(0, 83), Direction::Down, 2.2f); + DrawArrow(p, sideview_center + QPoint(0, 96), Direction::Down, 2.2f); + DrawArrow(p, sideview_center + QPoint(0, 109), Direction::Down, 2.2f); + DrawCircle(p, sideview_center + QPointF(0, 19), 4.44f); + + // LED indicators + const float led_size = 5.0f; + const QPointF led_position = sideview_center + QPointF(0, -36); + int led_count = 0; + for (const auto color : led_color) { + p.setBrush(color); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + } +} + +void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) { + std::array<QPointF, left_joycon_body.size() / 2> right_joycon; + std::array<QPointF, left_joycon_sideview.size() / 2> qright_joycon_sideview; + std::array<QPointF, left_joycon_body_trigger.size() / 2> qright_joycon_trigger; + std::array<QPointF, left_joycon_slider.size() / 2> qright_joycon_slider; + std::array<QPointF, left_joycon_slider_topview.size() / 2> qright_joycon_slider_topview; + std::array<QPointF, left_joycon_topview.size() / 2> qright_joycon_topview; + constexpr float size = 1.78f; + constexpr float size2 = 1.1115f; + constexpr float offset = 312.39f; + constexpr float offset2 = 335; + + for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) { + right_joycon[point] = center + QPointF(-left_joycon_body[point * 2] * size - offset, + left_joycon_body[point * 2 + 1] * size - 1); + } + + for (std::size_t point = 0; point < left_joycon_sideview.size() / 2; ++point) { + qright_joycon_sideview[point] = + center + QPointF(-left_joycon_sideview[point * 2] * size2 - offset2, + left_joycon_sideview[point * 2 + 1] * size2 + 2); + } + for (std::size_t point = 0; point < left_joycon_body_trigger.size() / 2; ++point) { + qright_joycon_trigger[point] = + center + QPointF(-left_joycon_body_trigger[point * 2] * size2 - offset2, + left_joycon_body_trigger[point * 2 + 1] * size2 + 2); + } + for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) { + qright_joycon_slider[point] = center + QPointF(-left_joycon_slider[point * 2] * size2 - 81, + left_joycon_slider[point * 2 + 1] * size2); + } + for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) { + qright_joycon_topview[point] = + center + QPointF(-left_joycon_topview[point * 2], left_joycon_topview[point * 2 + 1]); + } + for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) { + qright_joycon_slider_topview[point] = + center + QPointF(-left_joycon_slider_topview[point * 2], + left_joycon_slider_topview[point * 2 + 1]); + } + + // Joycon body + p.setPen(colors.outline); + p.setBrush(colors.left); + DrawPolygon(p, right_joycon); + DrawPolygon(p, qright_joycon_trigger); + + // Slider release button top view + p.setBrush(colors.button); + DrawRoundRectangle(p, center + QPoint(107, -62), 14, 12, 2); + + // Joycon slider top view + p.setBrush(colors.slider); + DrawPolygon(p, qright_joycon_slider_topview); + p.drawLine(center + QPointF(91.1f, -51.7f), center + QPointF(91.1f, -26.5f)); + + // Joycon body top view + p.setBrush(colors.left); + DrawPolygon(p, qright_joycon_topview); + + // Slider release button + p.setBrush(colors.button); + DrawRoundRectangle(p, center + QPoint(-175, -110), 12, 14, 2); + + // Sideview body + p.setBrush(colors.left); + DrawPolygon(p, qright_joycon_sideview); + p.setBrush(colors.slider); + DrawPolygon(p, qright_joycon_slider); + + const QPointF sideview_center = QPointF(-155, 0) + center; + + // Sideview slider body + p.setBrush(colors.slider); + DrawRoundRectangle(p, sideview_center + QPointF(0, -5), 28, 253, 3); + p.setBrush(colors.button2); + DrawRoundRectangle(p, sideview_center + QPointF(0, 97), 22.44f, 44.66f, 3); + + // Slider decorations + p.setPen(colors.outline); + p.setBrush(colors.slider_arrow); + DrawArrow(p, sideview_center + QPoint(0, 83), Direction::Down, 2.2f); + DrawArrow(p, sideview_center + QPoint(0, 96), Direction::Down, 2.2f); + DrawArrow(p, sideview_center + QPoint(0, 109), Direction::Down, 2.2f); + DrawCircle(p, sideview_center + QPointF(0, 19), 4.44f); + + // LED indicators + const float led_size = 5.0f; + const QPointF led_position = sideview_center + QPointF(0, -36); + int led_count = 0; + for (const auto color : led_color) { + p.setBrush(color); + DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size); + } +} + +void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bool left_pressed, + bool right_pressed) { + std::array<QPointF, pro_left_trigger.size() / 2> qleft_trigger; + std::array<QPointF, pro_left_trigger.size() / 2> qright_trigger; + std::array<QPointF, pro_body_top.size()> qbody_top; + + for (std::size_t point = 0; point < pro_left_trigger.size() / 2; ++point) { + const float trigger_x = pro_left_trigger[point * 2 + 0]; + const float trigger_y = pro_left_trigger[point * 2 + 1]; + + qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 2 : 0)); + qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 2 : 0)); + } + + for (std::size_t point = 0; point < pro_body_top.size() / 2; ++point) { + const float top_x = pro_body_top[point * 2 + 0]; + const float top_y = pro_body_top[point * 2 + 1]; + + qbody_top[pro_body_top.size() - 1 - point] = center + QPointF(-top_x, top_y); + qbody_top[point] = center + QPointF(top_x, top_y); + } + + // Pro body detail + p.setPen(colors.outline); + p.setBrush(colors.primary); + DrawPolygon(p, qbody_top); + + // Left trigger + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Right trigger + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); +} + +void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, bool left_pressed, + bool right_pressed) { + std::array<QPointF, left_gc_trigger.size() / 2> qleft_trigger; + std::array<QPointF, left_gc_trigger.size() / 2> qright_trigger; + + for (std::size_t point = 0; point < left_gc_trigger.size() / 2; ++point) { + const float trigger_x = left_gc_trigger[point * 2 + 0]; + const float trigger_y = left_gc_trigger[point * 2 + 1]; + + qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 10 : 0)); + qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 10 : 0)); + } + + // Left trigger + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Right trigger + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + + // Draw L text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPointF(-132, -119 + (left_pressed ? 10 : 0)), Symbol::L, 1.7f); + + // Draw R text + p.setPen(colors.transparent); + p.setBrush(colors.font); + DrawSymbol(p, center + QPointF(121.5f, -119 + (right_pressed ? 10 : 0)), Symbol::R, 1.7f); +} + +void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF center, + bool left_pressed, bool right_pressed) { + std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger; + std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger; + + for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { + const float left_trigger_x = left_joycon_trigger[point * 2 + 0]; + const float left_trigger_y = left_joycon_trigger[point * 2 + 1]; + + qleft_trigger[point] = + center + QPointF(left_trigger_x, left_trigger_y + (left_pressed ? 0.5f : 0)); + qright_trigger[point] = + center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed ? 0.5f : 0)); + } + + // Left trigger + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Right trigger + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); +} + +void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, bool left_pressed, + bool right_pressed) { + std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger; + std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger; + constexpr float size = 1.62f; + constexpr float offset = 210.6f; + for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { + const float left_trigger_x = left_joycon_trigger[point * 2 + 0]; + const float left_trigger_y = left_joycon_trigger[point * 2 + 1]; + + qleft_trigger[point] = center + QPointF(left_trigger_x * size + offset, + left_trigger_y * size + (left_pressed ? 0.5f : 0)); + qright_trigger[point] = + center + QPointF(-left_trigger_x * size - offset, + left_trigger_y * size + (right_pressed ? 0.5f : 0)); + } + + // Left trigger + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Right trigger + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); +} + +void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF center, + bool left_pressed, bool right_pressed) { + std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger; + std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger; + constexpr float size = 0.9f; + + for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { + const float top_view_x = left_joystick_L_topview[point * 2 + 0]; + const float top_view_y = left_joystick_L_topview[point * 2 + 1]; + + qleft_trigger[point] = center + QPointF(top_view_x * size - 50, top_view_y * size - 52); + } + for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { + const float top_view_x = left_joystick_L_topview[point * 2 + 0]; + const float top_view_y = left_joystick_L_topview[point * 2 + 1]; + + qright_trigger[point] = center + QPointF(-top_view_x * size + 50, top_view_y * size - 52); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + + // Draw L text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(-183, -84), Symbol::L, 1.0f); + + // Draw R text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(177, -84), Symbol::R, 1.0f); +} + +void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF center, + bool left_pressed, bool right_pressed) { + std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger; + std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger; + constexpr float size = 0.9f; + + for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { + qleft_trigger[point] = + center + QPointF(left_joystick_ZL_topview[point * 2] * size - 52, + left_joystick_ZL_topview[point * 2 + 1] * size - 52); + } + for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { + qright_trigger[point] = + center + QPointF(-left_joystick_ZL_topview[point * 2] * size + 52, + left_joystick_ZL_topview[point * 2 + 1] * size - 52); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + + // Draw ZL text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(-180, -113), Symbol::ZL, 1.0f); + + // Draw ZR text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(180, -113), Symbol::ZR, 1.0f); +} + +void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, bool left_pressed) { + std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger; + constexpr float size = 1.78f; + constexpr float offset = 311.5f; + + for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { + qleft_trigger[point] = center + QPointF(left_joycon_trigger[point * 2] * size + offset, + left_joycon_trigger[point * 2 + 1] * size - + (left_pressed ? 0.5f : 1.0f)); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); +} + +void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, bool left_pressed) { + std::array<QPointF, left_joycon_sideview_zl.size() / 2> qleft_trigger; + constexpr float size = 1.1115f; + constexpr float offset2 = 335; + + for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) { + qleft_trigger[point] = center + QPointF(left_joycon_sideview_zl[point * 2] * size + offset2, + left_joycon_sideview_zl[point * 2 + 1] * size + + (left_pressed ? 1.5f : 1.0f)); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + p.drawArc(center.x() + 158, center.y() + (left_pressed ? -203.5f : -204.0f), 77, 77, 225 * 16, + 44 * 16); +} + +void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF center, + bool left_pressed) { + std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger; + + for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { + qleft_trigger[point] = center + QPointF(left_joystick_L_topview[point * 2], + left_joystick_L_topview[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Draw L text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(-143, -36), Symbol::L, 1.0f); +} + +void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF center, + bool left_pressed) { + std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger; + + for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { + qleft_trigger[point] = center + QPointF(left_joystick_ZL_topview[point * 2], + left_joystick_ZL_topview[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(left_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qleft_trigger); + + // Draw ZL text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(-140, -68), Symbol::ZL, 1.0f); +} + +void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center, + bool right_pressed) { + std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger; + constexpr float size = 1.78f; + constexpr float offset = 311.5f; + + for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) { + qright_trigger[point] = center + QPointF(-left_joycon_trigger[point * 2] * size - offset, + left_joycon_trigger[point * 2 + 1] * size - + (right_pressed ? 0.5f : 1.0f)); + } + + p.setPen(colors.outline); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); +} + +void PlayerControlPreview::DrawRightZTriggers(QPainter& p, const QPointF center, + bool right_pressed) { + std::array<QPointF, left_joycon_sideview_zl.size() / 2> qright_trigger; + constexpr float size = 1.1115f; + constexpr float offset2 = 335; + + for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) { + qright_trigger[point] = + center + + QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2, + left_joycon_sideview_zl[point * 2 + 1] * size + (right_pressed ? 0.5f : 0) + 1); + } + + p.setPen(colors.outline); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + p.drawArc(center.x() - 236, center.y() + (right_pressed ? -203.5f : -204.0f), 77, 77, 271 * 16, + 44 * 16); +} + +void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF center, + bool right_pressed) { + std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger; + + for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) { + qright_trigger[point] = center + QPointF(-left_joystick_L_topview[point * 2], + left_joystick_L_topview[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + + // Draw R text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(137, -36), Symbol::R, 1.0f); +} + +void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF center, + bool right_pressed) { + std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger; + + for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) { + qright_trigger[point] = center + QPointF(-left_joystick_ZL_topview[point * 2], + left_joystick_ZL_topview[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(right_pressed ? colors.highlight : colors.button); + DrawPolygon(p, qright_trigger); + + // Draw ZR text + p.setPen(colors.transparent); + p.setBrush(colors.font2); + DrawSymbol(p, center + QPointF(140, -68), Symbol::ZR, 1.0f); +} + +void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float size, + bool pressed) { + const float radius1 = 13.0f * size; + const float radius2 = 9.0f * size; + + // Outer circle + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawCircle(p, center, radius1); + + // Cross + p.drawLine(center - QPoint(radius1, 0), center + QPoint(radius1, 0)); + p.drawLine(center - QPoint(0, radius1), center + QPoint(0, radius1)); + + // Inner circle + p.setBrush(pressed ? colors.highlight2 : colors.button2); + DrawCircle(p, center, radius2); +} + +void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF center, float angle, + float size, bool pressed) { + QVector<QPointF> joystick; + joystick.reserve(static_cast<int>(left_joystick_sideview.size() / 2)); + + for (std::size_t point = 0; point < left_joystick_sideview.size() / 2; ++point) { + joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed ? 1 : 0), + left_joystick_sideview[point * 2 + 1] * size - 1)); + } + + // Rotate joystick + QTransform t; + t.translate(center.x(), center.y()); + t.rotate(18 * angle); + QPolygonF p2 = t.map(QPolygonF(joystick)); + + // Draw joystick + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + p.drawPolygon(p2); + p.drawLine(p2.at(1), p2.at(30)); + p.drawLine(p2.at(32), p2.at(71)); +} + +void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, const QPointF offset, + float offset_scalar, bool pressed) { + const float radius1 = 24.0f; + const float radius2 = 17.0f; + + const QPointF offset_center = center + offset * offset_scalar; + + const auto amplitude = static_cast<float>( + 1.0 - std::sqrt((offset.x() * offset.x()) + (offset.y() * offset.y())) * 0.1f); + + const float rotation = + ((offset.x() == 0) ? atan(1) * 2 : atan(offset.y() / offset.x())) * (180 / (atan(1) * 4)); + + p.save(); + p.translate(offset_center); + p.rotate(rotation); + + // Outer circle + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + p.drawEllipse(QPointF(0, 0), radius1 * amplitude, radius1); + + // Inner circle + p.setBrush(pressed ? colors.highlight2 : colors.button2); + + const float inner_offset = + (radius1 - radius2) * 0.4f * ((offset.x() == 0 && offset.y() < 0) ? -1.0f : 1.0f); + const float offset_factor = (1.0f - amplitude) / 0.1f; + + p.drawEllipse(QPointF((offset.x() < 0) ? -inner_offset : inner_offset, 0) * offset_factor, + radius2 * amplitude, radius2); + + p.restore(); +} + +void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, bool pressed) { + // Outer circle + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawCircle(p, center, 26.0f); + + // Inner circle + p.setBrush(pressed ? colors.highlight2 : colors.button2); + DrawCircle(p, center, 19.0f); + p.setBrush(colors.transparent); + DrawCircle(p, center, 13.5f); + DrawCircle(p, center, 7.5f); +} + +void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, const QPointF value, + const Input::AnalogProperties& properties) { + constexpr float size = 45.0f; + const float range = size * properties.range; + const float deadzone = size * properties.deadzone; + + // Max range zone circle + p.setPen(colors.outline); + p.setBrush(colors.transparent); + QPen pen = p.pen(); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + DrawCircle(p, center, range); + + // Deadzone circle + pen.setColor(colors.deadzone); + p.setPen(pen); + DrawCircle(p, center, deadzone); + + // Dot pointer + p.setPen(colors.indicator); + p.setBrush(colors.indicator); + DrawCircle(p, center + (value * range), 2); +} + +void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, + float height, Direction direction, float radius) { + p.setBrush(button_color); + if (pressed) { + switch (direction) { + case Direction::Left: + center.setX(center.x() - 1); + break; + case Direction::Right: + center.setX(center.x() + 1); + break; + case Direction::Down: + center.setY(center.y() + 1); + break; + case Direction::Up: + center.setY(center.y() - 1); + break; + case Direction::None: + break; + } + p.setBrush(colors.highlight); + } + QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f}; + p.drawRoundedRect(rect, radius, radius); +} +void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, bool pressed, + int button_size) { + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawRectangle(p, center, button_size, button_size / 3.0f); +} +void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, bool pressed, + int button_size) { + // Draw outer line + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawRectangle(p, center, button_size, button_size / 3.0f); + DrawRectangle(p, center, button_size / 3.0f, button_size); + + // Scale down size + button_size *= 0.88f; + + // Draw inner color + p.setPen(colors.transparent); + DrawRectangle(p, center, button_size, button_size / 3.0f); + DrawRectangle(p, center, button_size / 3.0f, button_size); +} + +void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool pressed) { + std::array<QPointF, gc_button_x.size() / 2> button_x; + + for (std::size_t point = 0; point < gc_button_x.size() / 2; ++point) { + button_x[point] = center + QPointF(gc_button_x[point * 2], gc_button_x[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawPolygon(p, button_x); +} + +void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool pressed) { + std::array<QPointF, gc_button_y.size() / 2> button_x; + + for (std::size_t point = 0; point < gc_button_y.size() / 2; ++point) { + button_x[point] = center + QPointF(gc_button_y[point * 2], gc_button_y[point * 2 + 1]); + } + + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawPolygon(p, button_x); +} + +void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, bool pressed) { + std::array<QPointF, gc_button_z.size() / 2> button_x; + + for (std::size_t point = 0; point < gc_button_z.size() / 2; ++point) { + button_x[point] = center + QPointF(gc_button_z[point * 2], + gc_button_z[point * 2 + 1] + (pressed ? 1 : 0)); + } + + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button2); + DrawPolygon(p, button_x); +} + +void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, bool pressed, + float button_size) { + p.setBrush(button_color); + if (pressed) { + p.setBrush(colors.highlight); + } + p.drawEllipse(center, button_size, button_size); +} + +void PlayerControlPreview::DrawArrowButtonOutline(QPainter& p, const QPointF center, float size) { + const std::size_t arrow_points = up_arrow_button.size() / 2; + std::array<QPointF, (arrow_points - 1) * 4> arrow_button_outline; + + for (std::size_t point = 0; point < arrow_points - 1; ++point) { + const float up_arrow_x = up_arrow_button[point * 2 + 0]; + const float up_arrow_y = up_arrow_button[point * 2 + 1]; + + arrow_button_outline[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size); + arrow_button_outline[(arrow_points - 1) * 2 - point - 1] = + center + QPointF(up_arrow_y * size, up_arrow_x * size); + arrow_button_outline[(arrow_points - 1) * 2 + point] = + center + QPointF(-up_arrow_x * size, -up_arrow_y * size); + arrow_button_outline[(arrow_points - 1) * 4 - point - 1] = + center + QPointF(-up_arrow_y * size, -up_arrow_x * size); + } + // Draw arrow button outline + p.setPen(colors.outline); + p.setBrush(colors.transparent); + DrawPolygon(p, arrow_button_outline); +} + +void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, + const Direction direction, bool pressed, float size) { + std::array<QPointF, up_arrow_button.size() / 2> arrow_button; + QPoint offset; + + for (std::size_t point = 0; point < up_arrow_button.size() / 2; ++point) { + const float up_arrow_x = up_arrow_button[point * 2 + 0]; + const float up_arrow_y = up_arrow_button[point * 2 + 1]; + + switch (direction) { + case Direction::Up: + arrow_button[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size); + break; + case Direction::Left: + arrow_button[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size); + break; + case Direction::Right: + arrow_button[point] = center + QPointF(-up_arrow_y * size, up_arrow_x * size); + break; + case Direction::Down: + arrow_button[point] = center + QPointF(up_arrow_x * size, -up_arrow_y * size); + break; + case Direction::None: + break; + } + } + + // Draw arrow button + p.setPen(pressed ? colors.highlight : colors.button); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawPolygon(p, arrow_button); + + switch (direction) { + case Direction::Up: + offset = QPoint(0, -20 * size); + break; + case Direction::Left: + offset = QPoint(-20 * size, 0); + break; + case Direction::Right: + offset = QPoint(20 * size, 0); + break; + case Direction::Down: + offset = QPoint(0, 20 * size); + break; + case Direction::None: + offset = QPoint(0, 0); + break; + } + + // Draw arrow icon + p.setPen(colors.font2); + p.setBrush(colors.font2); + DrawArrow(p, center + offset, direction, size); +} + +void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center, + const Direction direction, bool pressed) { + std::array<QPointF, trigger_button.size() / 2> qtrigger_button; + + for (std::size_t point = 0; point < trigger_button.size() / 2; ++point) { + const float trigger_button_x = trigger_button[point * 2 + 0]; + const float trigger_button_y = trigger_button[point * 2 + 1]; + + switch (direction) { + case Direction::Left: + qtrigger_button[point] = center + QPointF(-trigger_button_x, trigger_button_y); + break; + case Direction::Right: + qtrigger_button[point] = center + QPointF(trigger_button_x, trigger_button_y); + break; + case Direction::Up: + case Direction::Down: + case Direction::None: + break; + } + } + + // Draw arrow button + p.setPen(colors.outline); + p.setBrush(pressed ? colors.highlight : colors.button); + DrawPolygon(p, qtrigger_button); +} + +void PlayerControlPreview::DrawSymbol(QPainter& p, const QPointF center, Symbol symbol, + float icon_size) { + std::array<QPointF, house.size() / 2> house_icon; + std::array<QPointF, symbol_a.size() / 2> a_icon; + std::array<QPointF, symbol_b.size() / 2> b_icon; + std::array<QPointF, symbol_x.size() / 2> x_icon; + std::array<QPointF, symbol_y.size() / 2> y_icon; + std::array<QPointF, symbol_l.size() / 2> l_icon; + std::array<QPointF, symbol_r.size() / 2> r_icon; + std::array<QPointF, symbol_c.size() / 2> c_icon; + std::array<QPointF, symbol_zl.size() / 2> zl_icon; + std::array<QPointF, symbol_sl.size() / 2> sl_icon; + std::array<QPointF, symbol_zr.size() / 2> zr_icon; + std::array<QPointF, symbol_sr.size() / 2> sr_icon; + switch (symbol) { + case Symbol::House: + for (std::size_t point = 0; point < house.size() / 2; ++point) { + house_icon[point] = center + QPointF(house[point * 2] * icon_size, + (house[point * 2 + 1] - 0.025f) * icon_size); + } + p.drawPolygon(house_icon.data(), static_cast<int>(house_icon.size())); + break; + case Symbol::A: + for (std::size_t point = 0; point < symbol_a.size() / 2; ++point) { + a_icon[point] = center + QPointF(symbol_a[point * 2] * icon_size, + symbol_a[point * 2 + 1] * icon_size); + } + p.drawPolygon(a_icon.data(), static_cast<int>(a_icon.size())); + break; + case Symbol::B: + for (std::size_t point = 0; point < symbol_b.size() / 2; ++point) { + b_icon[point] = center + QPointF(symbol_b[point * 2] * icon_size, + symbol_b[point * 2 + 1] * icon_size); + } + p.drawPolygon(b_icon.data(), static_cast<int>(b_icon.size())); + break; + case Symbol::X: + for (std::size_t point = 0; point < symbol_x.size() / 2; ++point) { + x_icon[point] = center + QPointF(symbol_x[point * 2] * icon_size, + symbol_x[point * 2 + 1] * icon_size); + } + p.drawPolygon(x_icon.data(), static_cast<int>(x_icon.size())); + break; + case Symbol::Y: + for (std::size_t point = 0; point < symbol_y.size() / 2; ++point) { + y_icon[point] = center + QPointF(symbol_y[point * 2] * icon_size, + (symbol_y[point * 2 + 1] - 1.0f) * icon_size); + } + p.drawPolygon(y_icon.data(), static_cast<int>(y_icon.size())); + break; + case Symbol::L: + for (std::size_t point = 0; point < symbol_l.size() / 2; ++point) { + l_icon[point] = center + QPointF(symbol_l[point * 2] * icon_size, + (symbol_l[point * 2 + 1] - 1.0f) * icon_size); + } + p.drawPolygon(l_icon.data(), static_cast<int>(l_icon.size())); + break; + case Symbol::R: + for (std::size_t point = 0; point < symbol_r.size() / 2; ++point) { + r_icon[point] = center + QPointF(symbol_r[point * 2] * icon_size, + (symbol_r[point * 2 + 1] - 1.0f) * icon_size); + } + p.drawPolygon(r_icon.data(), static_cast<int>(r_icon.size())); + break; + case Symbol::C: + for (std::size_t point = 0; point < symbol_c.size() / 2; ++point) { + c_icon[point] = center + QPointF(symbol_c[point * 2] * icon_size, + (symbol_c[point * 2 + 1] - 1.0f) * icon_size); + } + p.drawPolygon(c_icon.data(), static_cast<int>(c_icon.size())); + break; + case Symbol::ZL: + for (std::size_t point = 0; point < symbol_zl.size() / 2; ++point) { + zl_icon[point] = center + QPointF(symbol_zl[point * 2] * icon_size, + symbol_zl[point * 2 + 1] * icon_size); + } + p.drawPolygon(zl_icon.data(), static_cast<int>(zl_icon.size())); + break; + case Symbol::SL: + for (std::size_t point = 0; point < symbol_sl.size() / 2; ++point) { + sl_icon[point] = center + QPointF(symbol_sl[point * 2] * icon_size, + symbol_sl[point * 2 + 1] * icon_size); + } + p.drawPolygon(sl_icon.data(), static_cast<int>(sl_icon.size())); + break; + case Symbol::ZR: + for (std::size_t point = 0; point < symbol_zr.size() / 2; ++point) { + zr_icon[point] = center + QPointF(symbol_zr[point * 2] * icon_size, + symbol_zr[point * 2 + 1] * icon_size); + } + p.drawPolygon(zr_icon.data(), static_cast<int>(zr_icon.size())); + break; + case Symbol::SR: + for (std::size_t point = 0; point < symbol_sr.size() / 2; ++point) { + sr_icon[point] = center + QPointF(symbol_sr[point * 2] * icon_size, + symbol_sr[point * 2 + 1] * icon_size); + } + p.drawPolygon(sr_icon.data(), static_cast<int>(sr_icon.size())); + break; + } +} + +void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Direction direction, + float size) { + + std::array<QPointF, up_arrow_symbol.size() / 2> arrow_symbol; + + for (std::size_t point = 0; point < up_arrow_symbol.size() / 2; ++point) { + const float up_arrow_x = up_arrow_symbol[point * 2 + 0]; + const float up_arrow_y = up_arrow_symbol[point * 2 + 1]; + + switch (direction) { + case Direction::Up: + arrow_symbol[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size); + break; + case Direction::Left: + arrow_symbol[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size); + break; + case Direction::Right: + arrow_symbol[point] = center + QPointF(-up_arrow_y * size, up_arrow_x * size); + break; + case Direction::Down: + arrow_symbol[point] = center + QPointF(up_arrow_x * size, -up_arrow_y * size); + break; + case Direction::None: + break; + } + } + + DrawPolygon(p, arrow_symbol); +} + +template <size_t N> +void PlayerControlPreview::DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon) { + p.drawPolygon(polygon.data(), static_cast<int>(polygon.size())); +} + +void PlayerControlPreview::DrawCircle(QPainter& p, const QPointF center, float size) { + p.drawEllipse(center, size, size); +} + +void PlayerControlPreview::DrawRectangle(QPainter& p, const QPointF center, float width, + float height) { + const QRectF rect = QRectF(center.x() - (width / 2), center.y() - (height / 2), width, height); + p.drawRect(rect); +} +void PlayerControlPreview::DrawRoundRectangle(QPainter& p, const QPointF center, float width, + float height, float round) { + const QRectF rect = QRectF(center.x() - (width / 2), center.y() - (height / 2), width, height); + p.drawRoundedRect(rect, round, round); +} + +void PlayerControlPreview::DrawText(QPainter& p, const QPointF center, float text_size, + const QString& text) { + SetTextFont(p, text_size); + const QFontMetrics fm(p.font()); + const QPointF offset = {fm.horizontalAdvance(text) / 2.0f, -text_size / 2.0f}; + p.drawText(center - offset, text); +} + +void PlayerControlPreview::SetTextFont(QPainter& p, float text_size, const QString& font_family) { + QFont font = p.font(); + font.setPointSizeF(text_size); + font.setFamily(font_family); + p.setFont(font); +} diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h new file mode 100644 index 000000000..91c3343f1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -0,0 +1,192 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <QFrame> +#include <QPointer> +#include "core/frontend/input.h" +#include "core/settings.h" + +class QLabel; + +using AnalogParam = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; +using ButtonParam = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; + +// Widget for representing controller animations +class PlayerControlPreview : public QFrame { + Q_OBJECT + +public: + explicit PlayerControlPreview(QWidget* parent); + ~PlayerControlPreview() override; + + void SetPlayerInput(std::size_t index, const ButtonParam& buttons_param, + const AnalogParam& analogs_param); + void SetPlayerInputRaw(std::size_t index, const Settings::ButtonsRaw& buttons_, + Settings::AnalogsRaw analogs_); + void SetConnectedStatus(bool checked); + void SetControllerType(Settings::ControllerType type); + void BeginMappingButton(std::size_t button_id); + void BeginMappingAnalog(std::size_t button_id); + void EndMapping(); + void UpdateInput(); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + enum class Direction : std::size_t { + None, + Up, + Right, + Down, + Left, + }; + + enum class Symbol { + House, + A, + B, + X, + Y, + L, + R, + C, + SL, + ZL, + ZR, + SR, + }; + + struct AxisValue { + QPointF value{}; + QPointF raw_value{}; + Input::AnalogProperties properties{}; + int size{}; + QPoint offset{}; + bool active{}; + }; + + struct LedPattern { + bool position1; + bool position2; + bool position3; + bool position4; + }; + + struct ColorMapping { + QColor outline{}; + QColor primary{}; + QColor left{}; + QColor right{}; + QColor button{}; + QColor button2{}; + QColor font{}; + QColor font2{}; + QColor highlight{}; + QColor highlight2{}; + QColor transparent{}; + QColor indicator{}; + QColor led_on{}; + QColor led_off{}; + QColor slider{}; + QColor slider_button{}; + QColor slider_arrow{}; + QColor deadzone{}; + }; + + static LedPattern GetColorPattern(std::size_t index, bool player_on); + void UpdateColors(); + + // Draw controller functions + void DrawHandheldController(QPainter& p, QPointF center); + void DrawDualController(QPainter& p, QPointF center); + void DrawLeftController(QPainter& p, QPointF center); + void DrawRightController(QPainter& p, QPointF center); + void DrawProController(QPainter& p, QPointF center); + void DrawGCController(QPainter& p, QPointF center); + + // Draw body functions + void DrawHandheldBody(QPainter& p, QPointF center); + void DrawDualBody(QPainter& p, QPointF center); + void DrawLeftBody(QPainter& p, QPointF center); + void DrawRightBody(QPainter& p, QPointF center); + void DrawProBody(QPainter& p, QPointF center); + void DrawGCBody(QPainter& p, QPointF center); + + // Draw triggers functions + void DrawProTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); + void DrawGCTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); + void DrawHandheldTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); + void DrawDualTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed); + void DrawDualTriggersTopView(QPainter& p, QPointF center, bool left_pressed, + bool right_pressed); + void DrawDualZTriggersTopView(QPainter& p, QPointF center, bool left_pressed, + bool right_pressed); + void DrawLeftTriggers(QPainter& p, QPointF center, bool left_pressed); + void DrawLeftZTriggers(QPainter& p, QPointF center, bool left_pressed); + void DrawLeftTriggersTopView(QPainter& p, QPointF center, bool left_pressed); + void DrawLeftZTriggersTopView(QPainter& p, QPointF center, bool left_pressed); + void DrawRightTriggers(QPainter& p, QPointF center, bool right_pressed); + void DrawRightZTriggers(QPainter& p, QPointF center, bool right_pressed); + void DrawRightTriggersTopView(QPainter& p, QPointF center, bool right_pressed); + void DrawRightZTriggersTopView(QPainter& p, QPointF center, bool right_pressed); + + // Draw joystick functions + void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed); + void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed); + void DrawRawJoystick(QPainter& p, QPointF center, QPointF value, + const Input::AnalogProperties& properties); + void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed); + void DrawGCJoystick(QPainter& p, QPointF center, bool pressed); + + // Draw button functions + void DrawCircleButton(QPainter& p, QPointF center, bool pressed, float button_size); + void DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, float height, + Direction direction = Direction::None, float radius = 2); + void DrawMinusButton(QPainter& p, QPointF center, bool pressed, int button_size); + void DrawPlusButton(QPainter& p, QPointF center, bool pressed, int button_size); + void DrawGCButtonX(QPainter& p, QPointF center, bool pressed); + void DrawGCButtonY(QPainter& p, QPointF center, bool pressed); + void DrawGCButtonZ(QPainter& p, QPointF center, bool pressed); + void DrawArrowButtonOutline(QPainter& p, const QPointF center, float size = 1.0f); + void DrawArrowButton(QPainter& p, QPointF center, Direction direction, bool pressed, + float size = 1.0f); + void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, bool pressed); + + // Draw icon functions + void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size); + void DrawArrow(QPainter& p, QPointF center, Direction direction, float size); + + // Draw primitive types + template <size_t N> + void DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon); + void DrawCircle(QPainter& p, QPointF center, float size); + void DrawRectangle(QPainter& p, QPointF center, float width, float height); + void DrawRoundRectangle(QPainter& p, QPointF center, float width, float height, float round); + void DrawText(QPainter& p, QPointF center, float text_size, const QString& text); + void SetTextFont(QPainter& p, float text_size, + const QString& font_family = QStringLiteral("sans-serif")); + + using ButtonArray = + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::BUTTON_NS_END>; + using StickArray = + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>; + + bool mapping_active{}; + int blink_counter{}; + QColor button_color{}; + ColorMapping colors{}; + std::array<QColor, 4> led_color{}; + ButtonArray buttons{}; + StickArray sticks{}; + std::size_t player_index{}; + std::size_t button_mapping_index{Settings::NativeButton::BUTTON_NS_END}; + std::size_t analog_mapping_index{Settings::NativeAnalog::NUM_STICKS_HID}; + std::array<AxisValue, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{}; + std::array<bool, Settings::NativeButton::NumButtons> button_values{}; + Settings::ControllerType controller_type{Settings::ControllerType::ProController}; +}; diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp new file mode 100644 index 000000000..1f5cfa75b --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" + +ConfigureInputProfileDialog::ConfigureInputProfileDialog( + QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()), + profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) { + ui->setupUi(this); + + ui->controllerLayout->addWidget(profile_widget); + + connect(ui->clear_all_button, &QPushButton::clicked, this, + [this] { profile_widget->ClearAll(); }); + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + [this] { profile_widget->RestoreDefaults(); }); + + RetranslateUI(); +} + +ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default; + +void ConfigureInputProfileDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureInputProfileDialog::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h new file mode 100644 index 000000000..e6386bdbb --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.h @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +class QPushButton; + +class ConfigureInputPlayer; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputProfileDialog; +} + +class ConfigureInputProfileDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputProfileDialog(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles); + ~ConfigureInputProfileDialog() override; + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureInputProfileDialog> ui; + + ConfigureInputPlayer* profile_widget; +}; diff --git a/src/yuzu/configuration/configure_input_profile_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui new file mode 100644 index 000000000..726cf6905 --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.ui @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputProfileDialog</class> + <widget class="QDialog" name="ConfigureInputProfileDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>70</width> + <height>540</height> + </rect> + </property> + <property name="windowTitle"> + <string>Create Input Profile</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="controllerLayout"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="clear_all_button"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInputProfileDialog</receiver> + <slot>accept()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp new file mode 100644 index 000000000..1f2b792e4 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -0,0 +1,337 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <sstream> + +#include <QCloseEvent> +#include <QLabel> +#include <QMessageBox> +#include <QPushButton> +#include <QRegularExpression> +#include <QStringListModel> +#include <QVBoxLayout> + +#include "common/logging/log.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "input_common/udp/client.h" +#include "input_common/udp/udp.h" +#include "ui_configure_motion_touch.h" +#include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_touch_from_button.h" + +CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent, + const std::string& host, u16 port, + u8 pad_index, u16 client_id) + : QDialog(parent) { + layout = new QVBoxLayout; + status_label = new QLabel(tr("Communicating with the server...")); + cancel_button = new QPushButton(tr("Cancel")); + connect(cancel_button, &QPushButton::clicked, this, [this] { + if (!completed) { + job->Stop(); + } + accept(); + }); + layout->addWidget(status_label); + layout->addWidget(cancel_button); + setLayout(layout); + + using namespace InputCommon::CemuhookUDP; + job = std::make_unique<CalibrationConfigurationJob>( + host, port, pad_index, client_id, + [this](CalibrationConfigurationJob::Status status) { + QString text; + switch (status) { + case CalibrationConfigurationJob::Status::Ready: + text = tr("Touch the top left corner <br>of your touchpad."); + break; + case CalibrationConfigurationJob::Status::Stage1Completed: + text = tr("Now touch the bottom right corner <br>of your touchpad."); + break; + case CalibrationConfigurationJob::Status::Completed: + text = tr("Configuration completed!"); + break; + default: + break; + } + QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text)); + if (status == CalibrationConfigurationJob::Status::Completed) { + QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK"))); + } + }, + [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) { + completed = true; + min_x = min_x_; + min_y = min_y_; + max_x = max_x_; + max_y = max_y_; + }); +} + +CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default; + +void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) { + status_label->setText(text); +} + +void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) { + cancel_button->setText(text); +} + +ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique<Ui::ConfigureMotionTouch>()) { + ui->setupUi(this); + + ui->udp_learn_more->setOpenExternalLinks(true); + ui->udp_learn_more->setText( + tr("<a " + "href='https://yuzu-emu.org/wiki/" + "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " + "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); + + SetConfiguration(); + UpdateUiDisplay(); + ConnectEvents(); +} + +ConfigureMotionTouch::~ConfigureMotionTouch() = default; + +void ConfigureMotionTouch::SetConfiguration() { + const Common::ParamPackage motion_param(Settings::values.motion_device); + const Common::ParamPackage touch_param(Settings::values.touch_device); + + ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button); + touch_from_button_maps = Settings::values.touch_from_button_maps; + for (const auto& touch_map : touch_from_button_maps) { + ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); + } + ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index); + ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); + + min_x = touch_param.Get("min_x", 100); + min_y = touch_param.Get("min_y", 50); + max_x = touch_param.Get("max_x", 1800); + max_y = touch_param.Get("max_y", 850); + + ui->udp_server->setText(QString::fromStdString("127.0.0.1")); + ui->udp_port->setText(QString::number(26760)); + + udp_server_list_model = new QStringListModel(this); + udp_server_list_model->setStringList({}); + ui->udp_server_list->setModel(udp_server_list_model); + + std::stringstream ss(Settings::values.udp_input_servers); + std::string token; + + while (std::getline(ss, token, ',')) { + const int row = udp_server_list_model->rowCount(); + udp_server_list_model->insertRows(row, 1); + const QModelIndex index = udp_server_list_model->index(row); + udp_server_list_model->setData(index, QString::fromStdString(token)); + } +} + +void ConfigureMotionTouch::UpdateUiDisplay() { + const QString cemuhook_udp = QStringLiteral("cemuhookudp"); + + ui->motion_sensitivity_label->setVisible(true); + ui->motion_sensitivity->setVisible(true); + + ui->touch_calibration->setVisible(true); + ui->touch_calibration_config->setVisible(true); + ui->touch_calibration_label->setVisible(true); + ui->touch_calibration->setText( + QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y)); + + ui->udp_config_group_box->setVisible(true); +} + +void ConfigureMotionTouch::ConnectEvents() { + connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); + connect(ui->udp_add, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPAddServer); + connect(ui->udp_remove, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPDeleteServer); + connect(ui->touch_calibration_config, &QPushButton::clicked, this, + &ConfigureMotionTouch::OnConfigureTouchCalibration); + connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, + &ConfigureMotionTouch::OnConfigureTouchFromButton); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { + if (CanCloseDialog()) { + reject(); + } + }); +} + +void ConfigureMotionTouch::OnUDPAddServer() { + // Validator for IP address + const QRegularExpression re(QStringLiteral( + R"re(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)re")); + bool ok; + const QString port_text = ui->udp_port->text(); + const QString server_text = ui->udp_server->text(); + const QString server_string = tr("%1:%2").arg(server_text, port_text); + const int port_number = port_text.toInt(&ok, 10); + const int row = udp_server_list_model->rowCount(); + + if (!ok) { + QMessageBox::warning(this, tr("yuzu"), tr("Port number has invalid characters")); + return; + } + if (port_number < 0 || port_number > 65353) { + QMessageBox::warning(this, tr("yuzu"), tr("Port has to be in range 0 and 65353")); + return; + } + if (!re.match(server_text).hasMatch()) { + QMessageBox::warning(this, tr("yuzu"), tr("IP address is not valid")); + return; + } + // Search for duplicates + for (const auto& item : udp_server_list_model->stringList()) { + if (item == server_string) { + QMessageBox::warning(this, tr("yuzu"), tr("This UDP server already exists")); + return; + } + } + // Limit server count to 8 + if (row == 8) { + QMessageBox::warning(this, tr("yuzu"), tr("Unable to add more than 8 servers")); + return; + } + + udp_server_list_model->insertRows(row, 1); + QModelIndex index = udp_server_list_model->index(row); + udp_server_list_model->setData(index, server_string); + ui->udp_server_list->setCurrentIndex(index); +} + +void ConfigureMotionTouch::OnUDPDeleteServer() { + udp_server_list_model->removeRows(ui->udp_server_list->currentIndex().row(), 1); +} + +void ConfigureMotionTouch::OnCemuhookUDPTest() { + ui->udp_test->setEnabled(false); + ui->udp_test->setText(tr("Testing")); + udp_test_in_progress = true; + InputCommon::CemuhookUDP::TestCommunication( + ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), 0, + 24872, + [this] { + LOG_INFO(Frontend, "UDP input test success"); + QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true)); + }, + [this] { + LOG_ERROR(Frontend, "UDP input test failed"); + QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false)); + }); +} + +void ConfigureMotionTouch::OnConfigureTouchCalibration() { + ui->touch_calibration_config->setEnabled(false); + ui->touch_calibration_config->setText(tr("Configuring")); + CalibrationConfigurationDialog dialog(this, ui->udp_server->text().toStdString(), + static_cast<u16>(ui->udp_port->text().toUInt()), 0, + 24872); + dialog.exec(); + if (dialog.completed) { + min_x = dialog.min_x; + min_y = dialog.min_y; + max_x = dialog.max_x; + max_y = dialog.max_y; + LOG_INFO(Frontend, + "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}", + min_x, min_y, max_x, max_y); + UpdateUiDisplay(); + } else { + LOG_ERROR(Frontend, "UDP touchpad calibration config failed"); + } + ui->touch_calibration_config->setEnabled(true); + ui->touch_calibration_config->setText(tr("Configure")); +} + +void ConfigureMotionTouch::closeEvent(QCloseEvent* event) { + if (CanCloseDialog()) { + event->accept(); + } else { + event->ignore(); + } +} + +void ConfigureMotionTouch::ShowUDPTestResult(bool result) { + udp_test_in_progress = false; + if (result) { + QMessageBox::information(this, tr("Test Successful"), + tr("Successfully received data from the server.")); + } else { + QMessageBox::warning(this, tr("Test Failed"), + tr("Could not receive valid data from the server.<br>Please verify " + "that the server is set up correctly and " + "the address and port are correct.")); + } + ui->udp_test->setEnabled(true); + ui->udp_test->setText(tr("Test")); +} + +void ConfigureMotionTouch::OnConfigureTouchFromButton() { + ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem, + ui->touch_from_button_map->currentIndex()}; + if (dialog.exec() != QDialog::Accepted) { + return; + } + touch_from_button_maps = dialog.GetMaps(); + + while (ui->touch_from_button_map->count() > 0) { + ui->touch_from_button_map->removeItem(0); + } + for (const auto& touch_map : touch_from_button_maps) { + ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); + } + ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex()); +} + +bool ConfigureMotionTouch::CanCloseDialog() { + if (udp_test_in_progress) { + QMessageBox::warning(this, tr("yuzu"), + tr("UDP Test or calibration configuration is in progress.<br>Please " + "wait for them to finish.")); + return false; + } + return true; +} + +void ConfigureMotionTouch::ApplyConfiguration() { + if (!CanCloseDialog()) { + return; + } + + Common::ParamPackage touch_param{}; + touch_param.Set("min_x", min_x); + touch_param.Set("min_y", min_y); + touch_param.Set("max_x", max_x); + touch_param.Set("max_y", max_y); + + Settings::values.touch_device = touch_param.Serialize(); + Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked(); + Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); + Settings::values.touch_from_button_maps = touch_from_button_maps; + Settings::values.udp_input_servers = GetUDPServerString(); + input_subsystem->ReloadInputDevices(); + + accept(); +} + +std::string ConfigureMotionTouch::GetUDPServerString() const { + QString input_servers; + + for (const auto& item : udp_server_list_model->stringList()) { + input_servers += item; + input_servers += QLatin1Char{','}; + } + + // Remove last comma + input_servers.chop(1); + return input_servers.toStdString(); +} diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h new file mode 100644 index 000000000..15d61e8ba --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.h @@ -0,0 +1,95 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include "common/param_package.h" + +class QLabel; +class QPushButton; +class QStringListModel; +class QVBoxLayout; + +namespace InputCommon { +class InputSubsystem; +} + +namespace InputCommon::CemuhookUDP { +class CalibrationConfigurationJob; +} + +namespace Ui { +class ConfigureMotionTouch; +} + +/// A dialog for touchpad calibration configuration. +class CalibrationConfigurationDialog : public QDialog { + Q_OBJECT +public: + explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port, + u8 pad_index, u16 client_id); + ~CalibrationConfigurationDialog() override; + +private: + Q_INVOKABLE void UpdateLabelText(const QString& text); + Q_INVOKABLE void UpdateButtonText(const QString& text); + + QVBoxLayout* layout; + QLabel* status_label; + QPushButton* cancel_button; + std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job; + + // Configuration results + bool completed{}; + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; + + friend class ConfigureMotionTouch; +}; + +class ConfigureMotionTouch : public QDialog { + Q_OBJECT + +public: + explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); + ~ConfigureMotionTouch() override; + +public slots: + void ApplyConfiguration(); + +private slots: + void OnUDPAddServer(); + void OnUDPDeleteServer(); + void OnCemuhookUDPTest(); + void OnConfigureTouchCalibration(); + void OnConfigureTouchFromButton(); + +private: + void closeEvent(QCloseEvent* event) override; + Q_INVOKABLE void ShowUDPTestResult(bool result); + void SetConfiguration(); + void UpdateUiDisplay(); + void ConnectEvents(); + bool CanCloseDialog(); + std::string GetUDPServerString() const; + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr<Ui::ConfigureMotionTouch> ui; + QStringListModel* udp_server_list_model; + + // Coordinate system of the CemuhookUDP touch provider + int min_x{}; + int min_y{}; + int max_x{}; + int max_y{}; + + bool udp_test_in_progress{}; + + std::vector<Settings::TouchFromButtonMap> touch_from_button_maps; +}; diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui new file mode 100644 index 000000000..1e35ea946 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -0,0 +1,370 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMotionTouch</class> + <widget class="QDialog" name="ConfigureMotionTouch"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>482</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Motion / Touch</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="motion_group_box"> + <property name="title"> + <string>Mouse Motion</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="motion_sensitivity_label"> + <property name="text"> + <string>Sensitivity:</string> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="motion_sensitivity"> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="decimals"> + <number>4</number> + </property> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>10.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.001000000000000</double> + </property> + <property name="value"> + <double>0.010000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="touch_group_box"> + <property name="title"> + <string>Touch</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="touch_calibration_label"> + <property name="text"> + <string>UDP Calibration:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="touch_calibration"> + <property name="text"> + <string>(100, 50) - (1800, 850)</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="touch_calibration_config"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QCheckBox" name="touch_from_button_checkbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Use button mapping:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="touch_from_button_map"/> + </item> + <item> + <widget class="QPushButton" name="touch_from_button_config_btn"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="udp_config_group_box"> + <property name="title"> + <string>CemuhookUDP Config</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QLabel" name="udp_help"> + <property name="text"> + <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QListView" name="udp_server_list"/> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_server_label"> + <property name="text"> + <string>Server:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="udp_server"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_port_label"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="udp_port"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_learn_more"> + <property name="text"> + <string>Learn More</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="udp_test"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Test</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="udp_add"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add Server</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="udp_remove"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Remove Server</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>167</width> + <height>55</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureMotionTouch</receiver> + <slot>ApplyConfiguration()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui index 74552fdbd..5b99e1c37 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -15,7 +15,7 @@ </property> <property name="styleSheet"> <string notr="true">QPushButton { - min-width: 55px; + min-width: 60px; }</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -42,13 +42,13 @@ <widget class="QPushButton" name="forward_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -82,7 +82,7 @@ <widget class="QPushButton" name="back_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> @@ -110,7 +110,7 @@ <widget class="QPushButton" name="left_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> @@ -138,13 +138,13 @@ <widget class="QPushButton" name="middle_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -204,13 +204,13 @@ <widget class="QPushButton" name="right_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -256,13 +256,13 @@ <widget class="QPushButton" name="buttonClearAll"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -275,13 +275,13 @@ <widget class="QPushButton" name="buttonRestoreDefaults"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -324,32 +324,12 @@ <signal>accepted()</signal> <receiver>ConfigureMouseAdvanced</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>124</x> - <y>266</y> - </hint> - <hint type="destinationlabel"> - <x>124</x> - <y>143</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureMouseAdvanced</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>124</x> - <y>266</y> - </hint> - <hint type="destinationlabel"> - <x>124</x> - <y>143</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 1e49f0787..f598513df 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -16,6 +16,7 @@ #include "common/common_paths.h" #include "common/file_util.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" @@ -29,9 +30,10 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { - game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false); + game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id), + Config::ConfigType::PerGameConfig); - Settings::configuring_global = false; + Settings::SetConfiguringGlobal(false); ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -55,7 +57,7 @@ void ConfigurePerGame::ApplyConfiguration() { ui->graphicsAdvancedTab->ApplyConfiguration(); ui->audioTab->ApplyConfiguration(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); Settings::LogSettings(); game_config->Save(); @@ -88,9 +90,11 @@ void ConfigurePerGame::LoadConfiguration() { ui->display_title_id->setText( QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); - FileSys::PatchManager pm{title_id}; + auto& system = Core::System::GetInstance(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); - const auto loader = Loader::GetLoader(file); + const auto loader = Loader::GetLoader(system, file); if (control.first != nullptr) { ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index d2057c4ab..25975b3b9 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/src/yuzu/configuration/configure_per_game.ui @@ -319,32 +319,12 @@ <signal>accepted()</signal> <receiver>ConfigurePerGame</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigurePerGame</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 793fd8975..cdeeec01c 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -112,8 +112,10 @@ void ConfigurePerGameAddons::LoadConfiguration() { return; } - FileSys::PatchManager pm{title_id}; - const auto loader = Loader::GetLoader(file); + auto& system = Core::System::GetInstance(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto loader = Loader::GetLoader(system, file); FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 6334c4c50..51647a028 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -40,7 +40,7 @@ QString GetImagePath(Common::UUID uuid) { } QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) { - Service::Account::ProfileBase profile; + Service::Account::ProfileBase profile{}; if (!manager.GetProfileBase(uuid, profile)) { return {}; } @@ -116,8 +116,8 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) scene = new QGraphicsScene; ui->current_user_icon->setScene(scene); - SetConfiguration(); RetranslateUI(); + SetConfiguration(); } ConfigureProfileManager::~ConfigureProfileManager() = default; @@ -147,7 +147,7 @@ void ConfigureProfileManager::SetConfiguration() { void ConfigureProfileManager::PopulateUserList() { const auto& profiles = profile_manager->GetAllUsers(); for (const auto& user : profiles) { - Service::Account::ProfileBase profile; + Service::Account::ProfileBase profile{}; if (!profile_manager->GetProfileBase(user, profile)) continue; @@ -180,7 +180,7 @@ void ConfigureProfileManager::ApplyConfiguration() { return; } - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } void ConfigureProfileManager::SelectUser(const QModelIndex& index) { @@ -212,7 +212,7 @@ void ConfigureProfileManager::RenameUser() { const auto uuid = profile_manager->GetUser(user); ASSERT(uuid); - Service::Account::ProfileBase profile; + Service::Account::ProfileBase profile{}; if (!profile_manager->GetProfileBase(*uuid, profile)) return; diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp index 0de7a4f0b..b580cfff2 100644 --- a/src/yuzu/configuration/configure_service.cpp +++ b/src/yuzu/configuration/configure_service.cpp @@ -9,6 +9,7 @@ #include "ui_configure_service.h" #include "yuzu/configuration/configure_service.h" +#ifdef YUZU_ENABLE_BOXCAT namespace { QString FormatEventStatusString(const Service::BCAT::EventStatus& status) { QString out; @@ -32,6 +33,7 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) { return out; } } // Anonymous namespace +#endif ConfigureService::ConfigureService(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) { diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 9ad43ed8f..6cf2032da 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -12,6 +12,7 @@ #include "common/assert.h" #include "common/file_util.h" #include "core/core.h" +#include "core/hle/service/time/time.h" #include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configuration_shared.h" @@ -36,8 +37,8 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: } }); - ui->label_console_id->setVisible(Settings::configuring_global); - ui->button_regenerate_console_id->setVisible(Settings::configuring_global); + ui->label_console_id->setVisible(Settings::IsConfiguringGlobal()); + ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal()); SetupPerGameUI(); @@ -77,7 +78,7 @@ void ConfigureSystem::SetConfiguration() { Settings::values.rng_seed.UsingGlobal()); ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue()); ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue()); @@ -104,11 +105,29 @@ void ConfigureSystem::SetConfiguration() { void ConfigureSystem::ReadSystemSettings() {} void ConfigureSystem::ApplyConfiguration() { + auto& system = Core::System::GetInstance(); + + // Allow setting custom RTC even if system is powered on, + // to allow in-game time to be fast forwarded + if (Settings::values.custom_rtc.UsingGlobal()) { + if (ui->custom_rtc_checkbox->isChecked()) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + if (system.IsPoweredOn()) { + const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() + + Service::Time::TimeManager::GetExternalTimeZoneOffset()}; + system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); + } + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } + } + if (!enabled) { return; } - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value if (Settings::values.language_index.UsingGlobal()) { Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); @@ -131,15 +150,6 @@ void ConfigureSystem::ApplyConfiguration() { Settings::values.rng_seed.SetValue(std::nullopt); } } - - if (Settings::values.custom_rtc.UsingGlobal()) { - if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); - } else { - Settings::values.custom_rtc.SetValue(std::nullopt); - } - } } else { ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language); @@ -189,7 +199,7 @@ void ConfigureSystem::ApplyConfiguration() { } } - Settings::Apply(); + Settings::Apply(system); } void ConfigureSystem::RefreshConsoleID() { @@ -210,7 +220,7 @@ void ConfigureSystem::RefreshConsoleID() { } void ConfigureSystem::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal()); ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal()); ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp new file mode 100644 index 000000000..15557e4b8 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -0,0 +1,623 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QInputDialog> +#include <QKeyEvent> +#include <QMessageBox> +#include <QMouseEvent> +#include <QResizeEvent> +#include <QStandardItemModel> +#include <QTimer> +#include "common/param_package.h" +#include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_touch_from_button.h" +#include "yuzu/configuration/configure_touch_from_button.h" +#include "yuzu/configuration/configure_touch_widget.h" + +static QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return QString{}; + default: + return QKeySequence(key_code).toString(); + } +} + +static QString ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } + + if (param.Get("engine", "") == "keyboard") { + return GetKeyName(param.Get("code", 0)); + } + + if (param.Get("engine", "") == "sdl") { + if (param.Has("hat")) { + const QString hat_str = QString::fromStdString(param.Get("hat", "")); + const QString direction_str = QString::fromStdString(param.Get("direction", "")); + + return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); + } + + if (param.Has("axis")) { + const QString axis_str = QString::fromStdString(param.Get("axis", "")); + const QString direction_str = QString::fromStdString(param.Get("direction", "")); + + return QObject::tr("Axis %1%2").arg(axis_str, direction_str); + } + + if (param.Has("button")) { + const QString button_str = QString::fromStdString(param.Get("button", "")); + + return QObject::tr("Button %1").arg(button_str); + } + + return {}; + } + + return QObject::tr("[unknown]"); +} + +ConfigureTouchFromButton::ConfigureTouchFromButton( + QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps, + InputCommon::InputSubsystem* input_subsystem_, const int default_index) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), + touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { + ui->setupUi(this); + binding_list_model = new QStandardItemModel(0, 3, this); + binding_list_model->setHorizontalHeaderLabels( + {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")}); + ui->binding_list->setModel(binding_list_model); + ui->bottom_screen->SetCoordLabel(ui->coord_label); + + SetConfiguration(); + UpdateUiDisplay(); + ConnectEvents(); +} + +ConfigureTouchFromButton::~ConfigureTouchFromButton() = default; + +void ConfigureTouchFromButton::showEvent(QShowEvent* ev) { + QWidget::showEvent(ev); + + // width values are not valid in the constructor + const int w = + ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount(); + if (w <= 0) { + return; + } + ui->binding_list->setColumnWidth(0, w); + ui->binding_list->setColumnWidth(1, w); + ui->binding_list->setColumnWidth(2, w); +} + +void ConfigureTouchFromButton::SetConfiguration() { + for (const auto& touch_map : touch_maps) { + ui->mapping->addItem(QString::fromStdString(touch_map.name)); + } + + ui->mapping->setCurrentIndex(selected_index); +} + +void ConfigureTouchFromButton::UpdateUiDisplay() { + ui->button_delete->setEnabled(touch_maps.size() > 1); + ui->button_delete_bind->setEnabled(false); + + binding_list_model->removeRows(0, binding_list_model->rowCount()); + + for (const auto& button_str : touch_maps[selected_index].buttons) { + Common::ParamPackage package{button_str}; + QStandardItem* button = new QStandardItem(ButtonToText(package)); + button->setData(QString::fromStdString(button_str)); + button->setEditable(false); + QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0))); + QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0))); + binding_list_model->appendRow({button, xcoord, ycoord}); + + const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0)); + button->setData(dot, DataRoleDot); + } +} + +void ConfigureTouchFromButton::ConnectEvents() { + connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { + SaveCurrentMapping(); + selected_index = index; + UpdateUiDisplay(); + }); + connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping); + connect(ui->button_delete, &QPushButton::clicked, this, + &ConfigureTouchFromButton::DeleteMapping); + connect(ui->button_rename, &QPushButton::clicked, this, + &ConfigureTouchFromButton::RenameMapping); + connect(ui->button_delete_bind, &QPushButton::clicked, this, + &ConfigureTouchFromButton::DeleteBinding); + connect(ui->binding_list, &QTreeView::doubleClicked, this, + &ConfigureTouchFromButton::EditBinding); + connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this, + &ConfigureTouchFromButton::OnBindingSelection); + connect(binding_list_model, &QStandardItemModel::itemChanged, this, + &ConfigureTouchFromButton::OnBindingChanged); + connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this, + &ConfigureTouchFromButton::OnBindingDeleted); + connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this, + &ConfigureTouchFromButton::NewBinding); + connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this, + &ConfigureTouchFromButton::SetActiveBinding); + connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this, + &ConfigureTouchFromButton::SetCoordinates); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &ConfigureTouchFromButton::ApplyConfiguration); + + connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this]() { + Common::ParamPackage params; + for (auto& poller : device_pollers) { + params = poller->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } + }); +} + +void ConfigureTouchFromButton::SaveCurrentMapping() { + auto& map = touch_maps[selected_index]; + map.buttons.clear(); + for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) { + const auto bind_str = binding_list_model->index(i, 0) + .data(Qt::ItemDataRole::UserRole + 1) + .toString() + .toStdString(); + if (bind_str.empty()) { + continue; + } + Common::ParamPackage params{bind_str}; + if (!params.Has("engine")) { + continue; + } + params.Set("x", binding_list_model->index(i, 1).data().toInt()); + params.Set("y", binding_list_model->index(i, 2).data().toInt()); + map.buttons.emplace_back(params.Serialize()); + } +} + +void ConfigureTouchFromButton::NewMapping() { + const QString name = + QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile.")); + if (name.isEmpty()) { + return; + } + touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}}); + ui->mapping->addItem(name); + ui->mapping->setCurrentIndex(ui->mapping->count() - 1); +} + +void ConfigureTouchFromButton::DeleteMapping() { + const auto answer = QMessageBox::question( + this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText())); + if (answer != QMessageBox::Yes) { + return; + } + const bool blocked = ui->mapping->blockSignals(true); + ui->mapping->removeItem(selected_index); + ui->mapping->blockSignals(blocked); + touch_maps.erase(touch_maps.begin() + selected_index); + selected_index = ui->mapping->currentIndex(); + UpdateUiDisplay(); +} + +void ConfigureTouchFromButton::RenameMapping() { + const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:")); + if (new_name.isEmpty()) { + return; + } + ui->mapping->setItemText(selected_index, new_name); + touch_maps[selected_index].name = new_name.toStdString(); +} + +void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) { + binding_list_model->item(row_index, 0)->setText(tr("[press key]")); + + input_setter = [this, row_index, is_new](const Common::ParamPackage& params, + const bool cancel) { + auto* cell = binding_list_model->item(row_index, 0); + if (cancel) { + if (is_new) { + binding_list_model->removeRow(row_index); + } else { + cell->setText( + ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()})); + } + } else { + cell->setText(ButtonToText(params)); + cell->setData(QString::fromStdString(params.Serialize())); + } + }; + + device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button); + + for (auto& poller : device_pollers) { + poller->Start(); + } + + grabKeyboard(); + grabMouse(); + qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor)); + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + +void ConfigureTouchFromButton::NewBinding(const QPoint& pos) { + auto* button = new QStandardItem(); + button->setEditable(false); + auto* x_coord = new QStandardItem(QString::number(pos.x())); + auto* y_coord = new QStandardItem(QString::number(pos.y())); + + const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y()); + button->setData(dot_id, DataRoleDot); + + binding_list_model->appendRow({button, x_coord, y_coord}); + ui->binding_list->setFocus(); + ui->binding_list->setCurrentIndex(button->index()); + + GetButtonInput(binding_list_model->rowCount() - 1, true); +} + +void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) { + if (qi.row() >= 0 && qi.column() == 0) { + GetButtonInput(qi.row(), false); + } +} + +void ConfigureTouchFromButton::DeleteBinding() { + const int row_index = ui->binding_list->currentIndex().row(); + if (row_index < 0) { + return; + } + ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt()); + binding_list_model->removeRow(row_index); +} + +void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected, + const QItemSelection& deselected) { + ui->button_delete_bind->setEnabled(!selected.isEmpty()); + if (!selected.isEmpty()) { + const auto dot_data = selected.indexes().first().data(DataRoleDot); + if (dot_data.isValid()) { + ui->bottom_screen->HighlightDot(dot_data.toInt()); + } + } + if (!deselected.isEmpty()) { + const auto dot_data = deselected.indexes().first().data(DataRoleDot); + if (dot_data.isValid()) { + ui->bottom_screen->HighlightDot(dot_data.toInt(), false); + } + } +} + +void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) { + if (item->column() == 0) { + return; + } + + const bool blocked = binding_list_model->blockSignals(true); + item->setText(QString::number( + std::clamp(item->text().toInt(), 0, + static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width + : Layout::ScreenUndocked::Height) - + 1)))); + binding_list_model->blockSignals(blocked); + + const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot); + if (dot_data.isValid()) { + ui->bottom_screen->MoveDot(dot_data.toInt(), + binding_list_model->item(item->row(), 1)->text().toInt(), + binding_list_model->item(item->row(), 2)->text().toInt()); + } +} + +void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) { + for (int i = first; i <= last; ++i) { + const auto ix = binding_list_model->index(i, 0); + if (!ix.isValid()) { + return; + } + const auto dot_data = ix.data(DataRoleDot); + if (dot_data.isValid()) { + ui->bottom_screen->RemoveDot(dot_data.toInt()); + } + } +} + +void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) { + for (int i = 0; i < binding_list_model->rowCount(); ++i) { + if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) { + ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0)); + ui->binding_list->setFocus(); + return; + } + } +} + +void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) { + for (int i = 0; i < binding_list_model->rowCount(); ++i) { + if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) { + binding_list_model->item(i, 1)->setText(QString::number(pos.x())); + binding_list_model->item(i, 2)->setText(QString::number(pos.y())); + return; + } + } +} + +void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params, + const bool cancel) { + releaseKeyboard(); + releaseMouse(); + qApp->restoreOverrideCursor(); + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + if (input_setter) { + (*input_setter)(params, cancel); + input_setter.reset(); + } +} + +void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) { + if (!input_setter && event->key() == Qt::Key_Delete) { + DeleteBinding(); + return; + } + + if (!input_setter) { + return QDialog::keyPressEvent(event); + } + + if (event->key() != Qt::Key_Escape) { + SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + SetPollingResult({}, true); + } +} + +void ConfigureTouchFromButton::ApplyConfiguration() { + SaveCurrentMapping(); + accept(); +} + +int ConfigureTouchFromButton::GetSelectedIndex() const { + return selected_index; +} + +std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const { + return touch_maps; +} + +TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) { + setBackgroundRole(QPalette::ColorRole::Base); +} + +TouchScreenPreview::~TouchScreenPreview() = default; + +void TouchScreenPreview::SetCoordLabel(QLabel* const label) { + coord_label = label; +} + +int TouchScreenPreview::AddDot(const int device_x, const int device_y) { + QFont dot_font{QStringLiteral("monospace")}; + dot_font.setStyleHint(QFont::Monospace); + dot_font.setPointSize(20); + + auto* dot = new QLabel(this); + dot->setAttribute(Qt::WA_TranslucentBackground); + dot->setFont(dot_font); + dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign + dot->setAlignment(Qt::AlignmentFlag::AlignCenter); + dot->setProperty(PropId, ++max_dot_id); + dot->setProperty(PropX, device_x); + dot->setProperty(PropY, device_y); + dot->setCursor(Qt::CursorShape::PointingHandCursor); + dot->setMouseTracking(true); + dot->installEventFilter(this); + dot->show(); + PositionDot(dot, device_x, device_y); + dots.emplace_back(max_dot_id, dot); + return max_dot_id; +} + +void TouchScreenPreview::RemoveDot(const int id) { + const auto iter = std::find_if(dots.begin(), dots.end(), + [id](const auto& entry) { return entry.first == id; }); + if (iter == dots.cend()) { + return; + } + + iter->second->deleteLater(); + dots.erase(iter); +} + +void TouchScreenPreview::HighlightDot(const int id, const bool active) const { + for (const auto& dot : dots) { + if (dot.first == id) { + // use color property from the stylesheet, or fall back to the default palette + if (dot_highlight_color.isValid()) { + dot.second->setStyleSheet( + active ? QStringLiteral("color: %1").arg(dot_highlight_color.name()) + : QString{}); + } else { + dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited + : QPalette::ColorRole::NoRole); + } + if (active) { + dot.second->raise(); + } + return; + } + } +} + +void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const { + const auto iter = std::find_if(dots.begin(), dots.end(), + [id](const auto& entry) { return entry.first == id; }); + if (iter == dots.cend()) { + return; + } + + iter->second->setProperty(PropX, device_x); + iter->second->setProperty(PropY, device_y); + PositionDot(iter->second, device_x, device_y); +} + +void TouchScreenPreview::resizeEvent(QResizeEvent* event) { + if (ignore_resize) { + return; + } + + const int target_width = std::min(width(), height() * 4 / 3); + const int target_height = std::min(height(), width() * 3 / 4); + if (target_width == width() && target_height == height()) { + return; + } + ignore_resize = true; + setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width, + target_height); + ignore_resize = false; + + if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) { + for (const auto& dot : dots) { + PositionDot(dot.second); + } + } +} + +void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) { + if (!coord_label) { + return; + } + const auto pos = MapToDeviceCoords(event->x(), event->y()); + if (pos) { + coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y())); + } else { + coord_label->clear(); + } +} + +void TouchScreenPreview::leaveEvent(QEvent* event) { + if (coord_label) { + coord_label->clear(); + } +} + +void TouchScreenPreview::mousePressEvent(QMouseEvent* event) { + if (event->button() != Qt::MouseButton::LeftButton) { + return; + } + const auto pos = MapToDeviceCoords(event->x(), event->y()); + if (pos) { + emit DotAdded(*pos); + } +} + +bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { + switch (event->type()) { + case QEvent::Type::MouseButtonPress: { + const auto mouse_event = static_cast<QMouseEvent*>(event); + if (mouse_event->button() != Qt::MouseButton::LeftButton) { + break; + } + emit DotSelected(obj->property(PropId).toInt()); + + drag_state.dot = qobject_cast<QLabel*>(obj); + drag_state.start_pos = mouse_event->globalPos(); + return true; + } + case QEvent::Type::MouseMove: { + if (!drag_state.dot) { + break; + } + const auto mouse_event = static_cast<QMouseEvent*>(event); + if (!drag_state.active) { + drag_state.active = + (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >= + QApplication::startDragDistance(); + if (!drag_state.active) { + break; + } + } + auto current_pos = mapFromGlobal(mouse_event->globalPos()); + current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(), + contentsMargins().left() + contentsRect().width() - 1)); + current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(), + contentsMargins().top() + contentsRect().height() - 1)); + const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y()); + if (device_coord) { + drag_state.dot->setProperty(PropX, device_coord->x()); + drag_state.dot->setProperty(PropY, device_coord->y()); + PositionDot(drag_state.dot, device_coord->x(), device_coord->y()); + emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord); + if (coord_label) { + coord_label->setText( + QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y())); + } + } + return true; + } + case QEvent::Type::MouseButtonRelease: { + drag_state.dot.clear(); + drag_state.active = false; + return true; + } + default: + break; + } + return obj->eventFilter(obj, event); +} + +std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x, + const int screen_y) const { + const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) * + (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1); + const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) * + (Layout::ScreenUndocked::Height - 1) / + (contentsRect().height() - 1); + if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f && + t_y < Layout::ScreenUndocked::Height) { + + return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)}; + } + return std::nullopt; +} + +void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x, + const int device_y) const { + const float device_coord_x = + static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt()); + int x_coord = static_cast<int>( + device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) + + contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f); + + const float device_coord_y = + static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt()); + const int y_coord = static_cast<int>( + device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) + + contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f); + + dot->move(x_coord, y_coord); +} diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h new file mode 100644 index 000000000..d9513e3bc --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.h @@ -0,0 +1,92 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <optional> +#include <vector> +#include <QDialog> + +class QItemSelection; +class QModelIndex; +class QStandardItemModel; +class QStandardItem; +class QTimer; + +namespace Common { +class ParamPackage; +} + +namespace InputCommon { +class InputSubsystem; +} + +namespace InputCommon::Polling { +class DevicePoller; +} + +namespace Settings { +struct TouchFromButtonMap; +} + +namespace Ui { +class ConfigureTouchFromButton; +} + +class ConfigureTouchFromButton : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTouchFromButton(QWidget* parent, + const std::vector<Settings::TouchFromButtonMap>& touch_maps, + InputCommon::InputSubsystem* input_subsystem_, + int default_index = 0); + ~ConfigureTouchFromButton() override; + + int GetSelectedIndex() const; + std::vector<Settings::TouchFromButtonMap> GetMaps() const; + +public slots: + void ApplyConfiguration(); + void NewBinding(const QPoint& pos); + void SetActiveBinding(int dot_id); + void SetCoordinates(int dot_id, const QPoint& pos); + +protected: + void showEvent(QShowEvent* ev) override; + void keyPressEvent(QKeyEvent* event) override; + +private slots: + void NewMapping(); + void DeleteMapping(); + void RenameMapping(); + void EditBinding(const QModelIndex& qi); + void DeleteBinding(); + void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected); + void OnBindingChanged(QStandardItem* item); + void OnBindingDeleted(const QModelIndex& parent, int first, int last); + +private: + void SetConfiguration(); + void UpdateUiDisplay(); + void ConnectEvents(); + void GetButtonInput(int row_index, bool is_new); + void SetPollingResult(const Common::ParamPackage& params, bool cancel); + void SaveCurrentMapping(); + + std::unique_ptr<Ui::ConfigureTouchFromButton> ui; + std::vector<Settings::TouchFromButtonMap> touch_maps; + QStandardItemModel* binding_list_model; + InputCommon::InputSubsystem* input_subsystem; + int selected_index; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter; + + static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2; +}; diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui new file mode 100644 index 000000000..757219d54 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.ui @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTouchFromButton</class> + <widget class="QDialog" name="ConfigureTouchFromButton"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Touchscreen Mappings</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Mapping:</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="mapping"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="button_new"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>New</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="button_delete"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="button_rename"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Rename</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Click the bottom area to add a point, then press a button to bind. +Drag points to change position, or double-click table cells to edit values.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="button_delete_bind"> + <property name="text"> + <string>Delete Point</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="binding_list"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="TouchScreenPreview" name="bottom_screen"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>160</width> + <height>120</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="cursor"> + <cursorShape>CrossCursor</cursorShape> + </property> + <property name="mouseTracking"> + <bool>true</bool> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="coord_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>TouchScreenPreview</class> + <extends>QFrame</extends> + <header>yuzu/configuration/configure_touch_widget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureTouchFromButton</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h new file mode 100644 index 000000000..347b46583 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_widget.h @@ -0,0 +1,62 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <utility> +#include <vector> +#include <QFrame> +#include <QPointer> + +class QLabel; + +// Widget for representing touchscreen coordinates +class TouchScreenPreview : public QFrame { + Q_OBJECT + Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color) + +public: + explicit TouchScreenPreview(QWidget* parent); + ~TouchScreenPreview() override; + + void SetCoordLabel(QLabel*); + int AddDot(int device_x, int device_y); + void RemoveDot(int id); + void HighlightDot(int id, bool active = true) const; + void MoveDot(int id, int device_x, int device_y) const; + +signals: + void DotAdded(const QPoint& pos); + void DotSelected(int dot_id); + void DotMoved(int dot_id, const QPoint& pos); + +protected: + void resizeEvent(QResizeEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + void leaveEvent(QEvent*) override; + void mousePressEvent(QMouseEvent*) override; + bool eventFilter(QObject*, QEvent*) override; + +private: + std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const; + void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const; + + bool ignore_resize = false; + QPointer<QLabel> coord_label; + + std::vector<std::pair<int, QLabel*>> dots; + int max_dot_id = 0; + QColor dot_highlight_color; + static constexpr char PropId[] = "dot_id"; + static constexpr char PropX[] = "device_x"; + static constexpr char PropY[] = "device_y"; + + struct DragState { + bool active = false; + QPointer<QLabel> dot; + QPoint start_pos; + }; + DragState drag_state; +}; diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp index 7d7cc00b7..29c86c7bc 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -33,21 +33,18 @@ void ConfigureTouchscreenAdvanced::RetranslateUI() { } void ConfigureTouchscreenAdvanced::ApplyConfiguration() { - Settings::values.touchscreen.finger = ui->finger_box->value(); Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value(); Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value(); Settings::values.touchscreen.rotation_angle = ui->angle_box->value(); } void ConfigureTouchscreenAdvanced::LoadConfiguration() { - ui->finger_box->setValue(Settings::values.touchscreen.finger); ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x); ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y); ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle); } void ConfigureTouchscreenAdvanced::RestoreDefaults() { - ui->finger_box->setValue(0); ui->diameter_x_box->setValue(15); ui->diameter_y_box->setValue(15); ui->angle_box->setValue(0); diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui index 1171c2dd1..88e7cf050 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.ui +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -65,20 +65,13 @@ </property> </spacer> </item> - <item row="2" column="1"> + <item row="1" column="1"> <widget class="QLabel" name="label_4"> <property name="text"> <string>Touch Diameter Y</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Finger</string> - </property> - </widget> - </item> <item row="0" column="3"> <spacer name="horizontalSpacer_2"> <property name="orientation"> @@ -92,37 +85,27 @@ </property> </spacer> </item> - <item row="1" column="1"> + <item row="0" column="1"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Touch Diameter X</string> </property> </widget> </item> - <item row="0" column="2"> - <widget class="QSpinBox" name="finger_box"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="3" column="1"> + <item row="2" column="1"> <widget class="QLabel" name="label_5"> <property name="text"> <string>Rotational Angle</string> </property> </widget> </item> - <item row="1" column="2"> + <item row="0" column="2"> <widget class="QSpinBox" name="diameter_x_box"/> </item> - <item row="2" column="2"> + <item row="1" column="2"> <widget class="QSpinBox" name="diameter_y_box"/> </item> - <item row="3" column="2"> + <item row="2" column="2"> <widget class="QSpinBox" name="angle_box"/> </item> </layout> @@ -168,32 +151,12 @@ <signal>accepted()</signal> <receiver>ConfigureTouchscreenAdvanced</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureTouchscreenAdvanced</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> - </connection> + </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index dbe3f78c8..aed876008 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -9,6 +9,7 @@ #include <QDirIterator> #include "common/common_types.h" #include "common/file_util.h" +#include "core/core.h" #include "core/settings.h" #include "ui_configure_ui.h" #include "yuzu/configuration/configure_ui.h" @@ -84,7 +85,7 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, ui->screenshot_path_edit->text().toStdString()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } void ConfigureUi::RequestGameListUpdate() { diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp new file mode 100644 index 000000000..7dcb2c5b9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -0,0 +1,146 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <unordered_map> + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_vibration.h" +#include "yuzu/configuration/configure_vibration.h" + +ConfigureVibration::ConfigureVibration(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) { + ui->setupUi(this); + + vibration_groupboxes = { + ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3, + ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6, + ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8, + }; + + vibration_spinboxes = { + ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3, + ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6, + ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8, + }; + + const auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + vibration_groupboxes[i]->setChecked(players[i].vibration_enabled); + vibration_spinboxes[i]->setValue(players[i].vibration_strength); + } + + ui->checkBoxAccurateVibration->setChecked( + Settings::values.enable_accurate_vibrations.GetValue()); + + if (!Settings::IsConfiguringGlobal()) { + ui->checkBoxAccurateVibration->setDisabled(true); + } + + RetranslateUI(); +} + +ConfigureVibration::~ConfigureVibration() = default; + +void ConfigureVibration::ApplyConfiguration() { + auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + players[i].vibration_enabled = vibration_groupboxes[i]->isChecked(); + players[i].vibration_strength = vibration_spinboxes[i]->value(); + } + + Settings::values.enable_accurate_vibrations.SetValue( + ui->checkBoxAccurateVibration->isChecked()); +} + +void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { + using namespace Settings::NativeButton; + static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{ + {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons + {A, B, X, Y, R, ZR}, // Right Buttons + }}; + + auto& player = Settings::values.players.GetValue()[player_index]; + + for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { + std::unordered_map<std::string, int> params_count; + + for (const auto button_index : buttons[device_idx]) { + const auto& player_button = player.buttons[button_index]; + + if (params_count.find(player_button) != params_count.end()) { + ++params_count[player_button]; + continue; + } + + params_count.insert_or_assign(player_button, 1); + } + + const auto it = std::max_element( + params_count.begin(), params_count.end(), + [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); + + auto& vibration_param_str = player.vibrations[device_idx]; + vibration_param_str.clear(); + + if (it->first.empty()) { + continue; + } + + const auto param = Common::ParamPackage(it->first); + + const auto engine = param.Get("engine", ""); + const auto guid = param.Get("guid", ""); + const auto port = param.Get("port", ""); + + if (engine.empty() || engine == "keyboard" || engine == "mouse") { + continue; + } + + vibration_param_str += fmt::format("engine:{}", engine); + + if (!port.empty()) { + vibration_param_str += fmt::format(",port:{}", port); + } + if (!guid.empty()) { + vibration_param_str += fmt::format(",guid:{}", guid); + } + } + + if (player.vibrations[0] != player.vibrations[1]) { + return; + } + + if (!player.vibrations[0].empty() && + player.controller_type != Settings::ControllerType::RightJoycon) { + player.vibrations[1].clear(); + } else if (!player.vibrations[1].empty() && + player.controller_type == Settings::ControllerType::RightJoycon) { + player.vibrations[0].clear(); + } +} + +void ConfigureVibration::SetAllVibrationDevices() { + // Set vibration devices for all player indices including handheld + for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { + SetVibrationDevices(player_idx); + } +} + +void ConfigureVibration::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureVibration::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h new file mode 100644 index 000000000..07411a86f --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.h @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> + +class QGroupBox; +class QSpinBox; + +namespace Ui { +class ConfigureVibration; +} + +class ConfigureVibration : public QDialog { + Q_OBJECT + +public: + explicit ConfigureVibration(QWidget* parent); + ~ConfigureVibration() override; + + void ApplyConfiguration(); + + static void SetVibrationDevices(std::size_t player_index); + static void SetAllVibrationDevices(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureVibration> ui; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Groupboxes encapsulating the vibration strength spinbox. + std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes; + + // Spinboxes representing the vibration strength percentage. + std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes; +}; diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui new file mode 100644 index 000000000..efdf317a9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.ui @@ -0,0 +1,546 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureVibration</class> + <widget class="QDialog" name="ConfigureVibration"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>364</width> + <height>242</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Vibration</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="vibrationStrengthGroup"> + <property name="title"> + <string>Vibration</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0"> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <widget class="QWidget" name="player14Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer1"> + <property name="title"> + <string>Player 1</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer1"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer2"> + <property name="title"> + <string>Player 2</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer2"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer3"> + <property name="title"> + <string>Player 3</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer3"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer4"> + <property name="title"> + <string>Player 4</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer4"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player58Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer7"> + <property name="title"> + <string>Player 5</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer7"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer8"> + <property name="title"> + <string>Player 6</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer8"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer5"> + <property name="title"> + <string>Player 7</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer5"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer6"> + <property name="title"> + <string>Player 8</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpinPlayer6"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationSettingsGroup"> + <property name="title"> + <string>Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="checkBoxAccurateVibration"> + <property name="text"> + <string>Enable Accurate Vibration</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="spacerVibration"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>167</width> + <height>55</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBoxVibration"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBoxVibration</sender> + <signal>accepted()</signal> + <receiver>ConfigureVibration</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBoxVibration</sender> + <signal>rejected()</signal> + <receiver>ConfigureVibration</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp new file mode 100644 index 000000000..e87aededb --- /dev/null +++ b/src/yuzu/configuration/input_profiles.cpp @@ -0,0 +1,131 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/format.h> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/input_profiles.h" + +namespace FS = Common::FS; + +namespace { + +bool ProfileExistsInFilesystem(std::string_view profile_name) { + return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); +} + +bool IsINI(std::string_view filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return false; + } + + return filename.substr(index) == ".ini"; +} + +std::string GetNameWithoutExtension(const std::string& filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return filename; + } + + return filename.substr(0, index); +} + +} // namespace + +InputProfiles::InputProfiles() { + const std::string input_profile_loc = + fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); + + FS::ForeachDirectoryEntry( + nullptr, input_profile_loc, + [this](u64* entries_out, const std::string& directory, const std::string& filename) { + if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { + map_profiles.insert_or_assign( + GetNameWithoutExtension(filename), + std::make_unique<Config>(GetNameWithoutExtension(filename), + Config::ConfigType::InputProfile)); + } + return true; + }); +} + +InputProfiles::~InputProfiles() = default; + +std::vector<std::string> InputProfiles::GetInputProfileNames() { + std::vector<std::string> profile_names; + profile_names.reserve(map_profiles.size()); + + for (const auto& [profile_name, config] : map_profiles) { + if (!ProfileExistsInFilesystem(profile_name)) { + DeleteProfile(profile_name); + continue; + } + + profile_names.push_back(profile_name); + } + + return profile_names; +} + +bool InputProfiles::IsProfileNameValid(std::string_view profile_name) { + return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; +} + +bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) { + if (ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles.insert_or_assign( + profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); + + return SaveProfile(profile_name, player_index); +} + +bool InputProfiles::DeleteProfile(const std::string& profile_name) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name) || + FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { + map_profiles.erase(profile_name); + } + + return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); +} + +bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name)) { + map_profiles.erase(profile_name); + return false; + } + + map_profiles[profile_name]->ReadControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles[profile_name]->SaveControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const { + return map_profiles.find(profile_name) != map_profiles.end(); +} diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h new file mode 100644 index 000000000..cb41fd9be --- /dev/null +++ b/src/yuzu/configuration/input_profiles.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <string_view> +#include <unordered_map> + +class Config; + +class InputProfiles { + +public: + explicit InputProfiles(); + virtual ~InputProfiles(); + + std::vector<std::string> GetInputProfileNames(); + + static bool IsProfileNameValid(std::string_view profile_name); + + bool CreateProfile(const std::string& profile_name, std::size_t player_index); + bool DeleteProfile(const std::string& profile_name); + bool LoadProfile(const std::string& profile_name, std::size_t player_index); + bool SaveProfile(const std::string& profile_name, std::size_t player_index); + +private: + bool ProfileExistsInMap(const std::string& profile_name) const; + + std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; +}; diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp new file mode 100644 index 000000000..2731d948d --- /dev/null +++ b/src/yuzu/debugger/controller.cpp @@ -0,0 +1,66 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QAction> +#include <QLayout> +#include <QString> +#include "core/settings.h" +#include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/debugger/controller.h" + +ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { + setObjectName(QStringLiteral("Controller")); + setWindowTitle(tr("Controller P1")); + resize(500, 350); + setMinimumSize(500, 350); + // Remove the "?" button from the titlebar and enable the maximize button + setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | + Qt::WindowMaximizeButtonHint); + + widget = new PlayerControlPreview(this); + refreshConfiguration(); + QLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(widget); + setLayout(layout); + + // Configure focus so that widget is focusable and the dialog automatically forwards focus to + // it. + setFocusProxy(widget); + widget->setFocusPolicy(Qt::StrongFocus); + widget->setFocus(); +} + +void ControllerDialog::refreshConfiguration() { + const auto& players = Settings::values.players.GetValue(); + constexpr std::size_t player = 0; + widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); + widget->SetConnectedStatus(players[player].connected); + widget->SetControllerType(players[player].controller_type); +} + +QAction* ControllerDialog::toggleViewAction() { + if (toggle_view_action == nullptr) { + toggle_view_action = new QAction(tr("&Controller P1"), this); + toggle_view_action->setCheckable(true); + toggle_view_action->setChecked(isVisible()); + connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible); + } + + return toggle_view_action; +} + +void ControllerDialog::showEvent(QShowEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::showEvent(ev); +} + +void ControllerDialog::hideEvent(QHideEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::hideEvent(ev); +} diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h new file mode 100644 index 000000000..c54750070 --- /dev/null +++ b/src/yuzu/debugger/controller.h @@ -0,0 +1,31 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QWidget> + +class QAction; +class QHideEvent; +class QShowEvent; +class PlayerControlPreview; + +class ControllerDialog : public QWidget { + Q_OBJECT + +public: + explicit ControllerDialog(QWidget* parent = nullptr); + + /// Returns a QAction that can be used to toggle visibility of this dialog. + QAction* toggleViewAction(); + void refreshConfiguration(); + +protected: + void showEvent(QShowEvent* ev) override; + void hideEvent(QHideEvent* ev) override; + +private: + QAction* toggle_view_action = nullptr; + PlayerControlPreview* widget; +}; diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 0e26f765b..efdc6aa50 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -48,7 +48,7 @@ private: MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { setObjectName(QStringLiteral("MicroProfile")); - setWindowTitle(tr("MicroProfile")); + setWindowTitle(tr("&MicroProfile")); resize(1000, 600); // Remove the "?" button from the titlebar and enable the maximize button setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 3439cb333..3bca6277b 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -13,19 +13,19 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/synchronization_object.h" -#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/svc_common.h" +#include "core/hle/kernel/svc_types.h" #include "core/memory.h" namespace { constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{ {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green}, - {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green}, {Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan}, {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, @@ -91,9 +91,9 @@ std::size_t WaitTreeItem::Row() const { std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { std::vector<std::unique_ptr<WaitTreeThread>> item_list; std::size_t row = 0; - auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::Thread>>& threads) { + auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::KThread>>& threads) { for (std::size_t i = 0; i < threads.size(); ++i) { - if (!threads[i]->IsHLEThread()) { + if (threads[i]->GetThreadTypeForDebugging() == Kernel::ThreadType::User) { item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); item_list.back()->row = row; } @@ -102,7 +102,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() }; const auto& system = Core::System::GetInstance(); - add_threads(system.GlobalScheduler().GetThreadList()); + add_threads(system.GlobalSchedulerContext().GetThreadList()); return item_list; } @@ -117,8 +117,8 @@ QString WaitTreeText::GetText() const { WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::HandleTable& handle_table) : mutex_address(mutex_address) { mutex_value = Core::System::GetInstance().Memory().Read32(mutex_address); - owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask); - owner = handle_table.Get<Kernel::Thread>(owner_handle); + owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Svc::HandleWaitMask); + owner = handle_table.Get<Kernel::KThread>(owner_handle); } WaitTreeMutexInfo::~WaitTreeMutexInfo() = default; @@ -128,7 +128,7 @@ QString WaitTreeMutexInfo::GetText() const { } std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const { - const bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0; + const bool has_waiters = (mutex_value & Kernel::Svc::HandleWaitMask) != 0; std::vector<std::unique_ptr<WaitTreeItem>> list; list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters))); @@ -140,7 +140,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() cons return list; } -WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {} +WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread) : thread(thread) {} WaitTreeCallstack::~WaitTreeCallstack() = default; QString WaitTreeCallstack::GetText() const { @@ -150,7 +150,7 @@ QString WaitTreeCallstack::GetText() const { std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const { std::vector<std::unique_ptr<WaitTreeItem>> list; - if (thread.IsHLEThread()) { + if (thread.GetThreadTypeForDebugging() != Kernel::ThreadType::User) { return list; } @@ -170,7 +170,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons return list; } -WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& o) +WaitTreeSynchronizationObject::WaitTreeSynchronizationObject( + const Kernel::KSynchronizationObject& o) : object(o) {} WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default; @@ -189,12 +190,12 @@ QString WaitTreeSynchronizationObject::GetText() const { } std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make( - const Kernel::SynchronizationObject& object) { + const Kernel::KSynchronizationObject& object) { switch (object.GetHandleType()) { case Kernel::HandleType::ReadableEvent: - return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object)); + return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::KReadableEvent&>(object)); case Kernel::HandleType::Thread: - return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object)); + return std::make_unique<WaitTreeThread>(static_cast<const Kernel::KThread&>(object)); default: return std::make_unique<WaitTreeSynchronizationObject>(object); } @@ -203,7 +204,7 @@ std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::ma std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChildren() const { std::vector<std::unique_ptr<WaitTreeItem>> list; - const auto& threads = object.GetWaitingThreads(); + const auto& threads = object.GetWaitingThreadsForDebugging(); if (threads.empty()) { list.push_back(std::make_unique<WaitTreeText>(tr("waited by no thread"))); } else { @@ -212,8 +213,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChi return list; } -WaitTreeObjectList::WaitTreeObjectList( - const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list, bool w_all) +WaitTreeObjectList::WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, + bool w_all) : object_list(list), wait_all(w_all) {} WaitTreeObjectList::~WaitTreeObjectList() = default; @@ -231,57 +232,54 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() con return list; } -WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread) +WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread) : WaitTreeSynchronizationObject(thread) {} WaitTreeThread::~WaitTreeThread() = default; QString WaitTreeThread::GetText() const { - const auto& thread = static_cast<const Kernel::Thread&>(object); + const auto& thread = static_cast<const Kernel::KThread&>(object); QString status; - switch (thread.GetStatus()) { - case Kernel::ThreadStatus::Running: - status = tr("running"); - break; - case Kernel::ThreadStatus::Ready: - if (!thread.IsPaused()) { - if (thread.WasRunning()) { - status = tr("running"); - } else { - status = tr("ready"); - } + switch (thread.GetState()) { + case Kernel::ThreadState::Runnable: + if (!thread.IsSuspended()) { + status = tr("runnable"); } else { status = tr("paused"); } break; - case Kernel::ThreadStatus::Paused: - status = tr("paused"); - break; - case Kernel::ThreadStatus::WaitHLEEvent: - status = tr("waiting for HLE return"); - break; - case Kernel::ThreadStatus::WaitSleep: - status = tr("sleeping"); - break; - case Kernel::ThreadStatus::WaitIPC: - status = tr("waiting for IPC reply"); - break; - case Kernel::ThreadStatus::WaitSynch: - status = tr("waiting for objects"); - break; - case Kernel::ThreadStatus::WaitMutex: - status = tr("waiting for mutex"); - break; - case Kernel::ThreadStatus::WaitCondVar: - status = tr("waiting for condition variable"); + case Kernel::ThreadState::Waiting: + switch (thread.GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::Sleep: + status = tr("sleeping"); + break; + case Kernel::ThreadWaitReasonForDebugging::IPC: + status = tr("waiting for IPC reply"); + break; + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + status = tr("waiting for objects"); + break; + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + status = tr("waiting for condition variable"); + break; + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + status = tr("waiting for address arbiter"); + break; + case Kernel::ThreadWaitReasonForDebugging::Suspended: + status = tr("waiting for suspend resume"); + break; + default: + status = tr("waiting"); + break; + } break; - case Kernel::ThreadStatus::WaitArb: - status = tr("waiting for address arbiter"); + case Kernel::ThreadState::Initialized: + status = tr("initialized"); break; - case Kernel::ThreadStatus::Dormant: - status = tr("dormant"); + case Kernel::ThreadState::Terminated: + status = tr("terminated"); break; - case Kernel::ThreadStatus::Dead: - status = tr("dead"); + default: + status = tr("unknown"); break; } @@ -296,36 +294,33 @@ QString WaitTreeThread::GetText() const { QColor WaitTreeThread::GetColor() const { const std::size_t color_index = IsDarkTheme() ? 1 : 0; - const auto& thread = static_cast<const Kernel::Thread&>(object); - switch (thread.GetStatus()) { - case Kernel::ThreadStatus::Running: - return QColor(WaitTreeColors[0][color_index]); - case Kernel::ThreadStatus::Ready: - if (!thread.IsPaused()) { - if (thread.WasRunning()) { - return QColor(WaitTreeColors[1][color_index]); - } else { - return QColor(WaitTreeColors[2][color_index]); - } + const auto& thread = static_cast<const Kernel::KThread&>(object); + switch (thread.GetState()) { + case Kernel::ThreadState::Runnable: + if (!thread.IsSuspended()) { + return QColor(WaitTreeColors[0][color_index]); } else { + return QColor(WaitTreeColors[2][color_index]); + } + case Kernel::ThreadState::Waiting: + switch (thread.GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::IPC: + return QColor(WaitTreeColors[4][color_index]); + case Kernel::ThreadWaitReasonForDebugging::Sleep: + return QColor(WaitTreeColors[5][color_index]); + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + case Kernel::ThreadWaitReasonForDebugging::Suspended: + return QColor(WaitTreeColors[6][color_index]); + break; + default: return QColor(WaitTreeColors[3][color_index]); } - case Kernel::ThreadStatus::Paused: - return QColor(WaitTreeColors[4][color_index]); - case Kernel::ThreadStatus::WaitHLEEvent: - case Kernel::ThreadStatus::WaitIPC: - return QColor(WaitTreeColors[5][color_index]); - case Kernel::ThreadStatus::WaitSleep: - return QColor(WaitTreeColors[6][color_index]); - case Kernel::ThreadStatus::WaitSynch: - case Kernel::ThreadStatus::WaitMutex: - case Kernel::ThreadStatus::WaitCondVar: - case Kernel::ThreadStatus::WaitArb: + case Kernel::ThreadState::Initialized: return QColor(WaitTreeColors[7][color_index]); - case Kernel::ThreadStatus::Dormant: + case Kernel::ThreadState::Terminated: return QColor(WaitTreeColors[8][color_index]); - case Kernel::ThreadStatus::Dead: - return QColor(WaitTreeColors[9][color_index]); default: return WaitTreeItem::GetColor(); } @@ -334,37 +329,31 @@ QColor WaitTreeThread::GetColor() const { std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeSynchronizationObject::GetChildren()); - const auto& thread = static_cast<const Kernel::Thread&>(object); + const auto& thread = static_cast<const Kernel::KThread&>(object); QString processor; - switch (thread.GetProcessorID()) { - case Kernel::ThreadProcessorId::THREADPROCESSORID_IDEAL: + switch (thread.GetActiveCore()) { + case Kernel::Svc::IdealCoreUseProcessValue: processor = tr("ideal"); break; - case Kernel::ThreadProcessorId::THREADPROCESSORID_0: - case Kernel::ThreadProcessorId::THREADPROCESSORID_1: - case Kernel::ThreadProcessorId::THREADPROCESSORID_2: - case Kernel::ThreadProcessorId::THREADPROCESSORID_3: - processor = tr("core %1").arg(thread.GetProcessorID()); - break; default: - processor = tr("Unknown processor %1").arg(thread.GetProcessorID()); + processor = tr("core %1").arg(thread.GetActiveCore()); break; } list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); - list.push_back( - std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore()))); - list.push_back( - std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask()))); + list.push_back(std::make_unique<WaitTreeText>( + tr("ideal core = %1").arg(thread.GetIdealCoreForDebugging()))); + list.push_back(std::make_unique<WaitTreeText>( + tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask()))); list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID()))); list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") .arg(thread.GetPriority()) - .arg(thread.GetNominalPriority()))); + .arg(thread.GetBasePriority()))); list.push_back(std::make_unique<WaitTreeText>( - tr("last running ticks = %1").arg(thread.GetLastRunningTicks()))); + tr("last running ticks = %1").arg(thread.GetLastScheduledTick()))); - const VAddr mutex_wait_address = thread.GetMutexWaitAddress(); + const VAddr mutex_wait_address = thread.GetMutexWaitAddressForDebugging(); if (mutex_wait_address != 0) { const auto& handle_table = thread.GetOwnerProcess()->GetHandleTable(); list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address, handle_table)); @@ -372,9 +361,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); } - if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) { - list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetSynchronizationObjects(), - thread.IsWaitingSync())); + if (thread.GetState() == Kernel::ThreadState::Waiting && + thread.GetWaitReasonForDebugging() == + Kernel::ThreadWaitReasonForDebugging::Synchronization) { + list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjectsForDebugging(), + thread.IsCancellable())); } list.push_back(std::make_unique<WaitTreeCallstack>(thread)); @@ -382,11 +373,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { return list; } -WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object) +WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object) : WaitTreeSynchronizationObject(object) {} WaitTreeEvent::~WaitTreeEvent() = default; -WaitTreeThreadList::WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list) +WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::KThread*>& list) : thread_list(list) {} WaitTreeThreadList::~WaitTreeThreadList() = default; @@ -463,7 +454,7 @@ void WaitTreeModel::InitItems() { thread_items = WaitTreeItem::MakeThreadItemList(); } -WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) { +WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("&Wait Tree"), parent) { setObjectName(QStringLiteral("WaitTreeWidget")); view = new QTreeView(this); view->setHeaderHidden(true); diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 8e3bc4b24..3da2fdfd2 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -18,9 +18,9 @@ class EmuThread; namespace Kernel { class HandleTable; -class ReadableEvent; -class SynchronizationObject; -class Thread; +class KReadableEvent; +class KSynchronizationObject; +class KThread; } // namespace Kernel class WaitTreeThread; @@ -83,56 +83,55 @@ private: VAddr mutex_address; u32 mutex_value; Kernel::Handle owner_handle; - std::shared_ptr<Kernel::Thread> owner; + std::shared_ptr<Kernel::KThread> owner; }; class WaitTreeCallstack : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeCallstack(const Kernel::Thread& thread); + explicit WaitTreeCallstack(const Kernel::KThread& thread); ~WaitTreeCallstack() override; QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; private: - const Kernel::Thread& thread; + const Kernel::KThread& thread; }; class WaitTreeSynchronizationObject : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& object); + explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object); ~WaitTreeSynchronizationObject() override; static std::unique_ptr<WaitTreeSynchronizationObject> make( - const Kernel::SynchronizationObject& object); + const Kernel::KSynchronizationObject& object); QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; protected: - const Kernel::SynchronizationObject& object; + const Kernel::KSynchronizationObject& object; }; class WaitTreeObjectList : public WaitTreeExpandableItem { Q_OBJECT public: - WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list, - bool wait_all); + WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, bool wait_all); ~WaitTreeObjectList() override; QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; private: - const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& object_list; + const std::vector<Kernel::KSynchronizationObject*>& object_list; bool wait_all; }; class WaitTreeThread : public WaitTreeSynchronizationObject { Q_OBJECT public: - explicit WaitTreeThread(const Kernel::Thread& thread); + explicit WaitTreeThread(const Kernel::KThread& thread); ~WaitTreeThread() override; QString GetText() const override; @@ -143,21 +142,21 @@ public: class WaitTreeEvent : public WaitTreeSynchronizationObject { Q_OBJECT public: - explicit WaitTreeEvent(const Kernel::ReadableEvent& object); + explicit WaitTreeEvent(const Kernel::KReadableEvent& object); ~WaitTreeEvent() override; }; class WaitTreeThreadList : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list); + explicit WaitTreeThreadList(const std::vector<Kernel::KThread*>& list); ~WaitTreeThreadList() override; QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; private: - const std::vector<std::shared_ptr<Kernel::Thread>>& thread_list; + const std::vector<Kernel::KThread*>& thread_list; }; class WaitTreeModel : public QAbstractItemModel { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 6a71d9644..9afd5b45f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -25,7 +25,8 @@ #include "yuzu/main.h" #include "yuzu/uisettings.h" -GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} +GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent) + : QObject(parent), gamelist{gamelist} {} // EventFilter in order to process systemkeys while editing the searchfield bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { @@ -56,7 +57,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve case Qt::Key_Return: case Qt::Key_Enter: { if (gamelist->search_field->visible == 1) { - QString file_path = gamelist->getLastFilterResultItem(); + const QString file_path = gamelist->GetLastFilterResultItem(); // To avoid loading error dialog loops while confirming them using enter // Also users usually want to run a different game after closing one @@ -83,22 +84,25 @@ void GameListSearchField::setFilterResult(int visible, int total) { label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } -QString GameList::getLastFilterResultItem() const { - QStandardItem* folder; - QStandardItem* child; +QString GameList::GetLastFilterResultItem() const { QString file_path; const int folder_count = item_model->rowCount(); + for (int i = 0; i < folder_count; ++i) { - folder = item_model->item(i, 0); + const QStandardItem* folder = item_model->item(i, 0); const QModelIndex folder_index = folder->index(); const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { - if (!tree_view->isRowHidden(j, folder_index)) { - child = folder->child(j, 0); - file_path = child->data(GameListItemPath::FullPathRole).toString(); + if (tree_view->isRowHidden(j, folder_index)) { + continue; } + + const QStandardItem* child = folder->child(j, 0); + file_path = child->data(GameListItemPath::FullPathRole).toString(); } } + return file_path; } @@ -113,9 +117,9 @@ void GameListSearchField::setFocus() { } GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { - auto* const key_release_eater = new KeyReleaseEater(parent); + auto* const key_release_eater = new KeyReleaseEater(parent, this); layout_filter = new QHBoxLayout; - layout_filter->setMargin(8); + layout_filter->setContentsMargins(8, 8, 8, 8); label_filter = new QLabel; label_filter->setText(tr("Filter:")); edit_filter = new QLineEdit; @@ -123,7 +127,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { edit_filter->setPlaceholderText(tr("Enter pattern to filter")); edit_filter->installEventFilter(key_release_eater); edit_filter->setClearButtonEnabled(true); - connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged); + connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); label_filter_result = new QLabel; button_filter_close = new QToolButton(this); button_filter_close->setText(QStringLiteral("X")); @@ -133,7 +137,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { "#000000; font-weight: bold; background: #F0F0F0; }" "QToolButton:hover{ border: none; padding: 0px; color: " "#EEEEEE; font-weight: bold; background: #E81123}")); - connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked); + connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked); layout_filter->setSpacing(10); layout_filter->addWidget(label_filter); layout_filter->addWidget(edit_filter); @@ -159,16 +163,22 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput) } // Syncs the expanded state of Game Directories with settings to persist across sessions -void GameList::onItemExpanded(const QModelIndex& item) { +void GameList::OnItemExpanded(const QModelIndex& item) { const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>(); - if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || - type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) - item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded = - tree_view->isExpanded(item); + const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || + type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir; + + if (!is_dir) { + return; + } + + UISettings::values.game_dirs[item.data(GameListDir::GameDirRole).toInt()].expanded = + tree_view->isExpanded(item); } // Event in order to filter the gamelist after editing the searchfield -void GameList::onTextChanged(const QString& new_text) { +void GameList::OnTextChanged(const QString& new_text) { const int folder_count = tree_view->model()->rowCount(); QString edit_filter_text = new_text.toLower(); QStandardItem* folder; @@ -224,7 +234,7 @@ void GameList::onTextChanged(const QString& new_text) { } } -void GameList::onUpdateThemedIcons() { +void GameList::OnUpdateThemedIcons() { for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { QStandardItem* child = item_model->invisibleRootItem()->child(i); @@ -252,9 +262,9 @@ void GameList::onUpdateThemedIcons() { Qt::DecorationRole); break; case GameListItemType::CustomDir: { - const UISettings::GameDir* game_dir = - child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); - const QString icon_name = QFileInfo::exists(game_dir->path) + const UISettings::GameDir& game_dir = + UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()]; + const QString icon_name = QFileInfo::exists(game_dir.path) ? QStringLiteral("folder") : QStringLiteral("bad_folder"); child->setData( @@ -276,7 +286,7 @@ void GameList::onUpdateThemedIcons() { } } -void GameList::onFilterCloseClicked() { +void GameList::OnFilterCloseClicked() { main_window->filterBarSetChecked(false); } @@ -317,11 +327,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide } item_model->setSortRole(GameListItemPath::SortRole); - connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); + connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); - connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded); - connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded); + connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded); + connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded); // We must register all custom types with the Qt Automoc system so that we are able to use // it with signals/slots. In this case, QList falls under the umbrells of custom types. @@ -338,17 +348,17 @@ GameList::~GameList() { emit ShouldCancelWorker(); } -void GameList::setFilterFocus() { +void GameList::SetFilterFocus() { if (tree_view->model()->rowCount() > 0) { search_field->setFocus(); } } -void GameList::setFilterVisible(bool visibility) { +void GameList::SetFilterVisible(bool visibility) { search_field->setVisible(visibility); } -void GameList::clearFilter() { +void GameList::ClearFilter() { search_field->clear(); } @@ -356,7 +366,7 @@ void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); tree_view->setExpanded( entry_items->index(), - entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded); + UISettings::values.game_dirs[entry_items->data(GameListDir::GameDirRole).toInt()].expanded); } void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) { @@ -397,10 +407,11 @@ void GameList::ValidateEntry(const QModelIndex& item) { } } -bool GameList::isEmpty() const { +bool GameList::IsEmpty() const { for (int i = 0; i < item_model->rowCount(); i++) { const QStandardItem* child = item_model->invisibleRootItem()->child(i); const auto type = static_cast<GameListItemType>(child->type()); + if (!child->hasChildren() && (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)) { @@ -408,11 +419,12 @@ bool GameList::isEmpty() const { i--; } } + return !item_model->invisibleRootItem()->hasChildren(); } -void GameList::DonePopulating(QStringList watch_list) { - emit ShowList(!isEmpty()); +void GameList::DonePopulating(const QStringList& watch_list) { + emit ShowList(!IsEmpty()); item_model->invisibleRootItem()->appendRow(new GameListAddDir()); @@ -472,7 +484,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); } -void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { +void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) { QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = @@ -537,7 +549,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string pat void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { UISettings::GameDir& game_dir = - *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()]; QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); @@ -556,8 +568,7 @@ void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { } void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { - UISettings::GameDir& game_dir = - *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + const int game_dir_index = selected.data(GameListDir::GameDirRole).toInt(); QAction* move_up = context_menu.addAction(tr("\u25B2 Move Up")); QAction* move_down = context_menu.addAction(tr("\u25bc Move Down")); @@ -568,34 +579,39 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { move_up->setEnabled(row > 0); move_down->setEnabled(row < item_model->rowCount() - 2); - connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] { - // find the indices of the items in settings and swap them - std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], - UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( - *selected.sibling(row - 1, 0) - .data(GameListDir::GameDirRole) - .value<UISettings::GameDir*>())]); + connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] { + const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt(); + // swap the items in the settings + std::swap(UISettings::values.game_dirs[game_dir_index], + UISettings::values.game_dirs[other_index]); + // swap the indexes held by the QVariants + item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole); + item_model->setData(selected.sibling(row - 1, 0), QVariant(game_dir_index), + GameListDir::GameDirRole); // move the treeview items QList<QStandardItem*> item = item_model->takeRow(row); item_model->invisibleRootItem()->insertRow(row - 1, item); - tree_view->setExpanded(selected, game_dir.expanded); + tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded); }); - connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] { - // find the indices of the items in settings and swap them - std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], - UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( - *selected.sibling(row + 1, 0) - .data(GameListDir::GameDirRole) - .value<UISettings::GameDir*>())]); + connect(move_down, &QAction::triggered, [this, selected, row, game_dir_index] { + const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt(); + // swap the items in the settings + std::swap(UISettings::values.game_dirs[game_dir_index], + UISettings::values.game_dirs[other_index]); + // swap the indexes held by the QVariants + item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole); + item_model->setData(selected.sibling(row + 1, 0), QVariant(game_dir_index), + GameListDir::GameDirRole); // move the treeview items const QList<QStandardItem*> item = item_model->takeRow(row); item_model->invisibleRootItem()->insertRow(row + 1, item); - tree_view->setExpanded(selected, game_dir.expanded); + tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded); }); - connect(open_directory_location, &QAction::triggered, - [this, game_dir] { emit OpenDirectory(game_dir.path); }); + connect(open_directory_location, &QAction::triggered, [this, game_dir_index] { + emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path); + }); } void GameList::LoadCompatibilityList() { @@ -690,12 +706,15 @@ void GameList::SaveInterfaceLayout() { } void GameList::LoadInterfaceLayout() { - auto header = tree_view->header(); - if (!header->restoreState(UISettings::values.gamelist_header_state)) { - // We are using the name column to display icons and titles - // so make it as large as possible as default. - header->resizeSection(COLUMN_NAME, header->width()); + auto* header = tree_view->header(); + + if (header->restoreState(UISettings::values.gamelist_header_state)) { + return; } + + // We are using the name column to display icons and titles + // so make it as large as possible as default. + header->resizeSection(COLUMN_NAME, header->width()); } const QStringList GameList::supported_file_extensions = { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 78e2ba169..58059a3c4 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -67,11 +67,11 @@ public: FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); ~GameList() override; - QString getLastFilterResultItem() const; - void clearFilter(); - void setFilterFocus(); - void setFilterVisible(bool visibility); - bool isEmpty() const; + QString GetLastFilterResultItem() const; + void ClearFilter(); + void SetFilterFocus(); + void SetFilterVisible(bool visibility); + bool IsEmpty() const; void LoadCompatibilityList(); void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); @@ -82,7 +82,7 @@ public: static const QStringList supported_file_extensions; signals: - void GameChosen(QString game_path); + void GameChosen(const QString& game_path); void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target, const std::string& game_path); @@ -99,21 +99,21 @@ signals: void ShowList(bool show); private slots: - void onItemExpanded(const QModelIndex& item); - void onTextChanged(const QString& new_text); - void onFilterCloseClicked(); - void onUpdateThemedIcons(); + void OnItemExpanded(const QModelIndex& item); + void OnTextChanged(const QString& new_text); + void OnFilterCloseClicked(); + void OnUpdateThemedIcons(); private: void AddDirEntry(GameListDir* entry_items); void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); void ValidateEntry(const QModelIndex& item); - void DonePopulating(QStringList watch_list); + void DonePopulating(const QStringList& watch_list); void RefreshGameDirectory(); void PopupContextMenu(const QPoint& menu_location); - void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path); + void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path); void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 0cd0054c8..f25445f18 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -49,10 +49,10 @@ class GameListItem : public QStandardItem { public: // used to access type from item index - static const int TypeRole = Qt::UserRole + 1; - static const int SortRole = Qt::UserRole + 2; + static constexpr int TypeRole = Qt::UserRole + 1; + static constexpr int SortRole = Qt::UserRole + 2; GameListItem() = default; - GameListItem(const QString& string) : QStandardItem(string) { + explicit GameListItem(const QString& string) : QStandardItem(string) { setData(string, SortRole); } }; @@ -65,10 +65,10 @@ public: */ class GameListItemPath : public GameListItem { public: - static const int TitleRole = SortRole + 1; - static const int FullPathRole = SortRole + 2; - static const int ProgramIdRole = SortRole + 3; - static const int FileTypeRole = SortRole + 4; + static constexpr int TitleRole = SortRole + 1; + static constexpr int FullPathRole = SortRole + 2; + static constexpr int ProgramIdRole = SortRole + 3; + static constexpr int FileTypeRole = SortRole + 4; GameListItemPath() = default; GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, @@ -110,18 +110,22 @@ public: const auto& row1 = row_data.at(UISettings::values.row_1_text_id); const int row2_id = UISettings::values.row_2_text_id; - if (role == SortRole) + if (role == SortRole) { return row1.toLower(); + } - if (row2_id == 4) // None + // None + if (row2_id == 4) { return row1; + } const auto& row2 = row_data.at(row2_id); - if (row1 == row2) + if (row1 == row2) { return row1; + } - return QString(row1 + QStringLiteral("\n ") + row2); + return QStringLiteral("%1\n %2").arg(row1, row2); } return GameListItem::data(role); @@ -131,7 +135,7 @@ public: class GameListItemCompat : public GameListItem { Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) public: - static const int CompatNumberRole = SortRole; + static constexpr int CompatNumberRole = SortRole; GameListItemCompat() = default; explicit GameListItemCompat(const QString& compatibility) { setData(type(), TypeRole); @@ -170,7 +174,8 @@ public: } bool operator<(const QStandardItem& other) const override { - return data(CompatNumberRole) < other.data(CompatNumberRole); + return data(CompatNumberRole).value<QString>() < + other.data(CompatNumberRole).value<QString>(); } }; @@ -181,7 +186,7 @@ public: */ class GameListItemSize : public GameListItem { public: - static const int SizeRole = SortRole; + static constexpr int SizeRole = SortRole; GameListItemSize() = default; explicit GameListItemSize(const qulonglong size_bytes) { @@ -217,7 +222,7 @@ public: class GameListDir : public GameListItem { public: - static const int GameDirRole = Qt::UserRole + 2; + static constexpr int GameDirRole = Qt::UserRole + 2; explicit GameListDir(UISettings::GameDir& directory, GameListItemType dir_type = GameListItemType::CustomDir) @@ -225,7 +230,7 @@ public: setData(type(), TypeRole); UISettings::GameDir* game_dir = &directory; - setData(QVariant::fromValue(game_dir), GameDirRole); + setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); switch (dir_type) { @@ -326,7 +331,7 @@ public: private: class KeyReleaseEater : public QObject { public: - explicit KeyReleaseEater(GameList* gamelist); + explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr); private: GameList* gamelist = nullptr; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index e0ce45fd9..23643aea2 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -235,12 +235,11 @@ GameListWorker::~GameListWorker() = default; void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { using namespace FileSys; - const auto& cache = - dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider()); + auto& system = Core::System::GetInstance(); + const auto& cache = dynamic_cast<ContentProviderUnion&>(system.GetContentProvider()); - std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games; - installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, - ContentRecordType::Program); + auto installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, + ContentRecordType::Program); if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) { installed_games = cache.ListEntriesFilterOrigin( @@ -254,23 +253,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } for (const auto& [slot, game] : installed_games) { - if (slot == ContentProviderUnionSlot::FrontendManual) + if (slot == ContentProviderUnionSlot::FrontendManual) { continue; + } const auto file = cache.GetEntryUnparsed(game.title_id, game.type); - std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); - if (!loader) + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(system, file); + if (!loader) { continue; + } std::vector<u8> icon; std::string name; u64 program_id = 0; loader->ReadProgramId(program_id); - const PatchManager patch{program_id}; + const PatchManager patch{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); - if (control != nullptr) + if (control != nullptr) { GetMetadataFromControlNCA(patch, *control, icon, name); + } emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, compatibility_list, patch), @@ -280,9 +283,11 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, GameListDir* parent_dir) { - const auto callback = [this, target, recursion, - parent_dir](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + auto& system = Core::System::GetInstance(); + + const auto callback = [this, target, recursion, parent_dir, + &system](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { if (stop_processing) { // Breaks the callback loop. return false; @@ -293,7 +298,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); - auto loader = Loader::GetLoader(file); + auto loader = Loader::GetLoader(system, file); if (!loader) { return true; } @@ -331,7 +336,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa std::string name = " "; [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - const FileSys::PatchManager patch{program_id}; + const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, compatibility_list, patch), diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h index e4aba1b06..68e03fe4e 100644 --- a/src/yuzu/install_dialog.h +++ b/src/yuzu/install_dialog.h @@ -20,9 +20,8 @@ public: explicit InstallDialog(QWidget* parent, const QStringList& files); ~InstallDialog() override; - QStringList GetFiles() const; - bool ShouldOverwriteFiles() const; - int GetMinimumWidth() const; + [[nodiscard]] QStringList GetFiles() const; + [[nodiscard]] int GetMinimumWidth() const; private: QListWidget* file_list; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a1b61d119..0ba7c07cc 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,21 +11,24 @@ #endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/controller.h" #include "applets/error.h" #include "applets/profile_select.h" #include "applets/software_keyboard.h" #include "applets/web_browser.h" +#include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" +#include "core/frontend/applets/software_keyboard.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. @@ -47,12 +50,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QDesktopServices> #include <QDesktopWidget> #include <QDialogButtonBox> +#include <QDir> #include <QFile> #include <QFileDialog> #include <QInputDialog> #include <QMessageBox> #include <QProgressBar> #include <QProgressDialog> +#include <QPushButton> #include <QShortcut> #include <QStatusBar> #include <QSysInfo> @@ -77,6 +82,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/core.h" #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -84,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" -#include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" @@ -104,6 +109,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" #include "yuzu/debugger/console.h" +#include "yuzu/debugger/controller.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/wait_tree.h" #include "yuzu/discord.h" @@ -119,14 +125,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/discord_impl.h" #endif -#ifdef YUZU_USE_QT_WEB_ENGINE -#include <QWebEngineProfile> -#include <QWebEngineScript> -#include <QWebEngineScriptCollection> -#include <QWebEngineSettings> -#include <QWebEngineView> -#endif - #ifdef QT_STATICPLUGIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif @@ -143,12 +141,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; constexpr int default_mouse_timeout = 2500; -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; - /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the - * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones. + * user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones. */ enum class CalloutFlag : uint32_t { Telemetry = 0x1, @@ -167,7 +163,7 @@ void GMainWindow::ShowTelemetryCallout() { "<br/><br/>Would you like to share your usage data with us?"); if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { Settings::values.enable_telemetry = false; - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } } @@ -186,6 +182,30 @@ static void InitializeLogging() { #endif } +static void RemoveCachedContents() { + const auto offline_fonts = Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + + const auto offline_manual = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_manual", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + const auto offline_legal_information = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_legal_information", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + const auto offline_system_data = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_system_data", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + + Common::FS::DeleteDirRecursively(offline_fonts); + Common::FS::DeleteDirRecursively(offline_manual); + Common::FS::DeleteDirRecursively(offline_legal_information); + Common::FS::DeleteDirRecursively(offline_system_data); +} + GMainWindow::GMainWindow() : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, @@ -254,6 +274,9 @@ GMainWindow::GMainWindow() FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); + // Remove cached contents generated during the previous session + RemoveCachedContents(); + // Gen keys if necessary OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); @@ -271,9 +294,47 @@ GMainWindow::GMainWindow() connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); connect(ui.menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); + MigrateConfigFiles(); + + ui.action_Fullscreen->setChecked(false); + QStringList args = QApplication::arguments(); - if (args.length() >= 2) { - BootGame(args[1]); + + if (args.size() < 2) { + return; + } + + QString game_path; + + for (int i = 1; i < args.size(); ++i) { + // Preserves drag/drop functionality + if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) { + game_path = args[1]; + break; + } + + // Launch game in fullscreen mode + if (args[i] == QStringLiteral("-f")) { + ui.action_Fullscreen->setChecked(true); + continue; + } + + // Launch game at path + if (args[i] == QStringLiteral("-g")) { + if (i >= args.size() - 1) { + continue; + } + + if (args[i + 1].startsWith(QChar::fromLatin1('-'))) { + continue; + } + + game_path = args[++i]; + } + } + + if (!game_path.isEmpty()) { + BootGame(game_path); } } @@ -283,22 +344,37 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) { + QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + emit ControllerSelectorReconfigureFinished(); + + // Don't forget to apply settings. + Settings::Apply(Core::System::GetInstance()); + config->Save(); + + UpdateStatusButtons(); +} + void GMainWindow::ProfileSelectorSelectProfile() { - const Service::Account::ProfileManager manager; - int index = 0; - if (manager.GetUserCount() != 1) { - QtProfileSelectionDialog dialog(this); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) { - emit ProfileSelectorFinishedSelection(std::nullopt); - return; - } - index = dialog.GetIndex(); + QtProfileSelectionDialog dialog(this); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Rejected) { + emit ProfileSelectorFinishedSelection(std::nullopt); + return; } - const auto uuid = manager.GetUser(static_cast<std::size_t>(index)); + const Service::Account::ProfileManager manager; + const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex())); if (!uuid.has_value()) { emit ProfileSelectorFinishedSelection(std::nullopt); return; @@ -310,8 +386,9 @@ void GMainWindow::ProfileSelectorSelectProfile() { void GMainWindow::SoftwareKeyboardGetText( const Core::Frontend::SoftwareKeyboardParameters& parameters) { QtSoftwareKeyboardDialog dialog(this, parameters); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec() == QDialog::Rejected) { @@ -327,148 +404,141 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message emit SoftwareKeyboardFinishedCheckDialog(); } +void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { - NXInputWebEngineView web_browser_view(this); + if (disable_web_applet) { + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, + "http://localhost/"); + return; + } - // Scope to contain the QProgressDialog for initialization - { - QProgressDialog progress(this); - progress.setMinimumDuration(200); - progress.setLabelText(tr("Loading Web Applet...")); - progress.setRange(0, 4); - progress.setValue(0); - progress.show(); + QtNXWebEngineView web_browser_view(this, Core::System::GetInstance(), input_subsystem.get()); + + ui.action_Pause->setEnabled(false); + ui.action_Restart->setEnabled(false); + ui.action_Stop->setEnabled(false); - auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); }); + { + QProgressDialog loading_progress(this); + loading_progress.setLabelText(tr("Loading Web Applet...")); + loading_progress.setRange(0, 3); + loading_progress.setValue(0); - while (!future.isFinished()) - QApplication::processEvents(); + if (is_local && !Common::FS::Exists(std::string(main_url))) { + loading_progress.show(); - progress.setValue(1); + auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); }); - // Load the special shim script to handle input and exit. - QWebEngineScript nx_shim; - nx_shim.setSourceCode(GetNXShimInjectionScript()); - nx_shim.setWorldId(QWebEngineScript::MainWorld); - nx_shim.setName(QStringLiteral("nx_inject.js")); - nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); - nx_shim.setRunsOnSubFrames(true); - web_browser_view.page()->profile()->scripts()->insert(nx_shim); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + } - web_browser_view.load( - QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() + - QString::fromStdString(std::string(additional_args)))); + loading_progress.setValue(1); - progress.setValue(2); + if (is_local) { + web_browser_view.LoadLocalWebPage(main_url, additional_args); + } else { + web_browser_view.LoadExternalWebPage(main_url, additional_args); + } - render_window->hide(); - web_browser_view.setFocus(); + if (render_window->IsLoadingComplete()) { + render_window->hide(); + } const auto& layout = render_window->GetFramebufferLayout(); web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / - Layout::ScreenUndocked::Width); - web_browser_view.settings()->setAttribute( - QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + static_cast<qreal>(Layout::ScreenUndocked::Width)); + web_browser_view.setFocus(); web_browser_view.show(); - progress.setValue(3); + loading_progress.setValue(2); - QApplication::processEvents(); + QCoreApplication::processEvents(); - progress.setValue(4); + loading_progress.setValue(3); } - bool finished = false; - QAction* exit_action = new QAction(tr("Exit Web Applet"), this); - connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; }); - ui.menubar->addAction(exit_action); + bool exit_check = false; - auto& npad = - Core::System::GetInstance() - .ServiceManager() - .GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); - - const auto fire_js_keypress = [&web_browser_view](u32 key_code) { - web_browser_view.page()->runJavaScript( - QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") - .arg(key_code)); - }; + // TODO (Morph): Remove this + QAction* exit_action = new QAction(tr("Disable Web Applet"), this); + connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { + const auto result = QMessageBox::warning( + this, tr("Disable Web Applet"), + tr("Disabling the web applet will cause it to not be shown again for the rest of the " + "emulated session. This can lead to undefined behavior and should only be used with " + "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"), + QMessageBox::Yes | QMessageBox::No); + if (result == QMessageBox::Yes) { + disable_web_applet = true; + web_browser_view.SetFinished(true); + } + }); + ui.menubar->addAction(exit_action); - QMessageBox::information( - this, tr("Exit"), - tr("To exit the web application, use the game provided controls to select exit, select the " - "'Exit Web Applet' option in the menu bar, or press the 'Enter' key.")); - - bool running_exit_check = false; - while (!finished) { - QApplication::processEvents(); - - if (!running_exit_check) { - web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"), - [&](const QVariant& res) { - running_exit_check = false; - if (res.toBool()) - finished = true; - }); - running_exit_check = true; + while (!web_browser_view.IsFinished()) { + QCoreApplication::processEvents(); + + if (!exit_check) { + web_browser_view.page()->runJavaScript( + QStringLiteral("end_applet;"), [&](const QVariant& variant) { + exit_check = false; + if (variant.toBool()) { + web_browser_view.SetFinished(true); + web_browser_view.SetExitReason( + Service::AM::Applets::WebExitReason::EndButtonPressed); + } + }); + + exit_check = true; } - const auto input = npad.GetAndResetPressState(); - for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) { - if ((input & (1 << i)) != 0) { - LOG_DEBUG(Frontend, "firing input for button id={:02X}", i); - web_browser_view.page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1]();").arg(i)); + if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { + if (!web_browser_view.IsFinished()) { + web_browser_view.SetFinished(true); + web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); } + + web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); } - if (input & 0x00888000) // RStick Down | LStick Down | DPad Down - fire_js_keypress(40); // Down Arrow Key - else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right - fire_js_keypress(39); // Right Arrow Key - else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up - fire_js_keypress(38); // Up Arrow Key - else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left - fire_js_keypress(37); // Left Arrow Key - else if (input & 0x00000001) // A Button - fire_js_keypress(13); // Enter Key + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + const auto exit_reason = web_browser_view.GetExitReason(); + const auto last_url = web_browser_view.GetLastURL(); + web_browser_view.hide(); - render_window->show(); + render_window->setFocus(); - ui.menubar->removeAction(exit_action); - // Needed to update render window focus/show and remove menubar action - QApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + if (render_window->IsLoadingComplete()) { + render_window->show(); + } -#else + ui.action_Pause->setEnabled(true); + ui.action_Restart->setEnabled(true); + ui.action_Stop->setEnabled(true); -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { - QMessageBox::warning( - this, tr("Web Applet"), - tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot " - "properly display the game manual or web page requested."), - QMessageBox::Ok, QMessageBox::Ok); + ui.menubar->removeAction(exit_action); - LOG_INFO(Frontend, - "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at " - "'{}' with arguments '{}'!", - filename, additional_args); + QCoreApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + emit WebBrowserClosed(exit_reason, last_url); + +#else + + // Utilize the same fallback as the default web browser applet. + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); #endif +} void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING @@ -530,13 +600,14 @@ void GMainWindow::InitializeWidgets() { dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); dock_status_button->setFocusPolicy(Qt::NoFocus); connect(dock_status_button, &QPushButton::clicked, [&] { - Settings::values.use_docked_mode = !Settings::values.use_docked_mode; - dock_status_button->setChecked(Settings::values.use_docked_mode); - OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode); + Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue()); + dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); + OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(), + Settings::values.use_docked_mode.GetValue()); }); dock_status_button->setText(tr("DOCK")); dock_status_button->setCheckable(true); - dock_status_button->setChecked(Settings::values.use_docked_mode); + dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); statusBar()->insertPermanentWidget(0, dock_status_button); // Setup ASync button @@ -547,11 +618,10 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + !Settings::values.use_asynchronous_gpu_emulation.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); async_status_button->setText(tr("ASYNC")); async_status_button->setCheckable(true); @@ -566,16 +636,13 @@ void GMainWindow::InitializeWidgets() { return; } Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); - bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); multicore_status_button->setText(tr("MULTICORE")); multicore_status_button->setCheckable(true); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, async_status_button); @@ -589,11 +656,6 @@ void GMainWindow::InitializeWidgets() { }); renderer_status_button->toggle(); -#ifndef HAS_VULKAN - renderer_status_button->setChecked(false); - renderer_status_button->setCheckable(false); - renderer_status_button->setDisabled(true); -#else renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); connect(renderer_status_button, &QPushButton::clicked, [this] { @@ -606,9 +668,8 @@ void GMainWindow::InitializeWidgets() { Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL); } - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); -#endif // HAS_VULKAN statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->setVisible(true); @@ -628,6 +689,11 @@ void GMainWindow::InitializeDebugWidgets() { addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); debug_menu->addAction(waitTreeWidget->toggleViewAction()); + + controller_dialog = new ControllerDialog(this); + controller_dialog->hide(); + debug_menu->addAction(controller_dialog->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, waitTreeWidget, &WaitTreeWidget::OnEmulationStarting); connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, @@ -644,7 +710,7 @@ void GMainWindow::InitializeRecentFileMenuActions() { } ui.menu_recent_files->addSeparator(); QAction* action_clear_recent_files = new QAction(this); - action_clear_recent_files->setText(tr("Clear Recent Files")); + action_clear_recent_files->setText(tr("&Clear Recent Files")); connect(action_clear_recent_files, &QAction::triggered, this, [this] { UISettings::values.recent_files.clear(); UpdateRecentFiles(); @@ -775,14 +841,25 @@ void GMainWindow::InitializeHotkeys() { }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this), &QShortcut::activated, this, [&] { - Settings::values.use_docked_mode = !Settings::values.use_docked_mode; - OnDockedModeChanged(!Settings::values.use_docked_mode, - Settings::values.use_docked_mode); - dock_status_button->setChecked(Settings::values.use_docked_mode); + Settings::values.use_docked_mode.SetValue( + !Settings::values.use_docked_mode.GetValue()); + OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(), + Settings::values.use_docked_mode.GetValue()); + dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this), &QShortcut::activated, this, [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); + + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this), + &QShortcut::activated, this, [&] { + Settings::values.mouse_panning = !Settings::values.mouse_panning; + if (UISettings::values.hide_mouse || Settings::values.mouse_panning) { + mouse_hide_timer.start(); + render_window->installEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, true); + } + }); } void GMainWindow::SetDefaultUIGeometry() { @@ -817,7 +894,7 @@ void GMainWindow::RestoreUIState() { OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); - game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); + game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked()); ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); @@ -905,7 +982,10 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::OnDisplayTitleBars); connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); - connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize); + connect(ui.action_Reset_Window_Size_720, &QAction::triggered, this, + &GMainWindow::ResetWindowSize720); + connect(ui.action_Reset_Window_Size_1080, &QAction::triggered, this, + &GMainWindow::ResetWindowSize1080); // Fullscreen connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); @@ -953,7 +1033,7 @@ void GMainWindow::AllowOSSleep() { #endif } -bool GMainWindow::LoadROM(const QString& filename) { +bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -966,18 +1046,17 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetFilesystem(vfs); system.SetAppletFrontendSet({ - nullptr, // Parental Controls - std::make_unique<QtErrorDisplay>(*this), // - nullptr, // Photo Viewer - std::make_unique<QtProfileSelector>(*this), // - std::make_unique<QtSoftwareKeyboard>(*this), // - std::make_unique<QtWebBrowser>(*this), // - nullptr, // E-Commerce + std::make_unique<QtControllerSelector>(*this), // Controller Selector + std::make_unique<QtErrorDisplay>(*this), // Error Display + nullptr, // Parental Controls + nullptr, // Photo Viewer + std::make_unique<QtProfileSelector>(*this), // Profile Selector + std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard + std::make_unique<QtWebBrowser>(*this), // Web Browser }); - system.RegisterHostThread(); - - const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; + const Core::System::ResultStatus result{ + system.Load(*render_window, filename.toStdString(), program_index)}; const auto drd_callout = (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; @@ -1017,20 +1096,24 @@ bool GMainWindow::LoadROM(const QString& filename) { break; default: - if (static_cast<u32>(result) > - static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { + if (result > Core::System::ResultStatus::ErrorLoader) { const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); const u16 error_id = static_cast<u16>(result) - loader_id; const std::string error_code = fmt::format("({:04X}-{:04X})", loader_id, error_id); LOG_CRITICAL(Frontend, "Failed to load ROM! {}", error_code); - QMessageBox::critical( - this, - tr("Error while loading ROM! ").append(QString::fromStdString(error_code)), - QString::fromStdString(fmt::format( - "{}<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the " - "yuzu quickstart guide</a> to redump your files.<br>You can refer " - "to the yuzu wiki</a> or the yuzu Discord</a> for help.", - static_cast<Loader::ResultStatus>(error_id)))); + + const auto title = + tr("Error while loading ROM! %1", "%1 signifies a numeric error code.") + .arg(QString::fromStdString(error_code)); + const auto description = + tr("%1<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the " + "yuzu quickstart guide</a> to redump your files.<br>You can refer " + "to the yuzu wiki</a> or the yuzu Discord</a> for help.", + "%1 signifies an error string.") + .arg(QString::fromStdString( + GetResultStatusString(static_cast<Loader::ResultStatus>(error_id)))); + + QMessageBox::critical(this, title, description); } else { QMessageBox::critical( this, tr("Error while loading ROM!"), @@ -1059,26 +1142,37 @@ void GMainWindow::SelectAndSetCurrentUser() { Settings::values.current_user = dialog.GetIndex(); } -void GMainWindow::BootGame(const QString& filename) { +void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list u64 title_id{0}; + last_filename_booted = filename; + + auto& system = Core::System::GetInstance(); const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); - const auto loader = Loader::GetLoader(v_file); + const auto loader = Loader::GetLoader(system, v_file, program_index); + if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { // Load per game settings - Config per_game_config(fmt::format("{:016X}.ini", title_id), false); + Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig); } + ConfigureVibration::SetAllVibrationDevices(); + + // Save configurations + UpdateUISettings(); + game_list->SaveInterfaceLayout(); + config->Save(); + Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { SelectAndSetCurrentUser(); } - if (!LoadROM(filename)) + if (!LoadROM(filename, program_index)) return; // Create and start the emulation thread @@ -1086,7 +1180,12 @@ void GMainWindow::BootGame(const QString& filename) { emit EmulationStarting(emu_thread.get()); emu_thread->start(); + // Register an ExecuteProgram callback such that Core can execute a sub-program + system.RegisterExecuteProgramCallback( + [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); }); + connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); + connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget, @@ -1108,17 +1207,21 @@ void GMainWindow::BootGame(const QString& filename) { multicore_status_button->setDisabled(true); renderer_status_button->setDisabled(true); - if (UISettings::values.hide_mouse) { + if (UISettings::values.hide_mouse || Settings::values.mouse_panning) { mouse_hide_timer.start(); - setMouseTracking(true); - ui.centralwidget->setMouseTracking(true); + render_window->installEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, true); } std::string title_name; std::string title_version; - const auto res = Core::System::GetInstance().GetGameName(title_name); + const auto res = system.GetGameName(title_name); - const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata(); + const auto metadata = [&system, title_id] { + const FileSys::PatchManager pm(title_id, system.GetFileSystemController(), + system.GetContentProvider()); + return pm.GetControlMetadata(); + }(); if (metadata.first != nullptr) { title_version = metadata.first->GetVersionString(); title_name = metadata.first->GetApplicationName(); @@ -1129,7 +1232,7 @@ void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); UpdateWindowTitle(title_name, title_version); - loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); + loading_screen->Prepare(system.GetAppLoader()); loading_screen->show(); emulation_running = true; @@ -1177,14 +1280,15 @@ void GMainWindow::ShutdownGame() { render_window->hide(); loading_screen->hide(); loading_screen->Clear(); - if (game_list->isEmpty()) + if (game_list->IsEmpty()) { game_list_placeholder->show(); - else + } else { game_list->show(); - game_list->setFilterFocus(); + } + game_list->SetFilterFocus(); - setMouseTracking(false); - ui.centralwidget->setMouseTracking(false); + render_window->removeEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, false); UpdateWindowTitle(); @@ -1196,9 +1300,7 @@ void GMainWindow::ShutdownGame() { emu_frametime_label->setVisible(false); async_status_button->setEnabled(true); multicore_status_button->setEnabled(true); -#ifdef HAS_VULKAN renderer_status_button->setEnabled(true); -#endif emulation_running = false; @@ -1247,16 +1349,18 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target const std::string& game_path) { std::string path; QString open_target; + auto& system = Core::System::GetInstance(); - const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] { - FileSys::PatchManager pm{program_id}; + const auto [user_save_size, device_save_size] = [this, &game_path, &program_id, &system] { + const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata().first; if (control != nullptr) { return std::make_pair(control->GetDefaultNormalSaveSize(), control->GetDeviceSaveDataSize()); } else { const auto file = Core::GetGameFileFromPath(vfs, game_path); - const auto loader = Loader::GetLoader(file); + const auto loader = Loader::GetLoader(system, file); FileSys::NACP nacp{}; loader->ReadControlData(nacp); @@ -1298,12 +1402,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); ASSERT(user_id); path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - FileSys::SaveDataSpaceId::NandUser, + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0); } else { // Device save data path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - FileSys::SaveDataSpaceId::NandUser, + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, program_id, {}, 0); } @@ -1483,7 +1587,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); for (const auto& entry : dlc_entries) { - if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); @@ -1558,7 +1662,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id) { const QString config_dir = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); const QString custom_config_file_path = - config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); + config_dir + QStringLiteral("custom") + QDir::separator() + + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); if (!QFile::exists(custom_config_file_path)) { QMessageBox::warning(this, tr("Error Removing Custom Configuration"), @@ -1582,7 +1687,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa "cancelled the operation.")); }; - const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); + auto& system = Core::System::GetInstance(); + const auto loader = Loader::GetLoader(system, vfs->OpenFile(game_path, FileSys::Mode::Read)); if (loader == nullptr) { failed(); return; @@ -1594,7 +1700,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto& installed = Core::System::GetInstance().GetContentProvider(); + const auto& installed = system.GetContentProvider(); const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); if (!romfs_title_id) { @@ -1609,7 +1715,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (*romfs_title_id == program_id) { const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); - FileSys::PatchManager pm{program_id}; + const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program); } else { romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); @@ -1726,7 +1832,8 @@ void GMainWindow::OnGameListShowList(bool show) { void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { u64 title_id{}; const auto v_file = Core::GetGameFileFromPath(vfs, file); - const auto loader = Loader::GetLoader(v_file); + const auto loader = Loader::GetLoader(Core::System::GetInstance(), v_file); + if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { QMessageBox::information(this, tr("Properties"), tr("The game properties could not be loaded.")); @@ -2047,17 +2154,19 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); qRegisterMetaType<std::string_view>("std::string_view"); + qRegisterMetaType<Service::AM::Applets::WebExitReason>("Service::AM::Applets::WebExitReason"); connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); @@ -2082,14 +2191,14 @@ void GMainWindow::OnPauseGame() { } void GMainWindow::OnStopGame() { - Core::System& system{Core::System::GetInstance()}; + auto& system{Core::System::GetInstance()}; if (system.GetExitLock() && !ConfirmForceLockedExit()) { return; } ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(system.IsPoweredOn()); UpdateStatusButtons(); } @@ -2097,6 +2206,11 @@ void GMainWindow::OnLoadComplete() { loading_screen->OnLoadComplete(); } +void GMainWindow::OnExecuteProgram(std::size_t program_index) { + ShutdownGame(); + BootGame(last_filename_booted, program_index); +} + void GMainWindow::ErrorDisplayDisplayError(QString body) { QMessageBox::critical(this, tr("Error Display"), body); emit ErrorDisplayFinished(); @@ -2196,7 +2310,7 @@ void GMainWindow::ToggleWindowMode() { } } -void GMainWindow::ResetWindowSize() { +void GMainWindow::ResetWindowSize720() { const auto aspect_ratio = Layout::EmulationAspectRatio( static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); @@ -2210,6 +2324,20 @@ void GMainWindow::ResetWindowSize() { } } +void GMainWindow::ResetWindowSize1080() { + const auto aspect_ratio = Layout::EmulationAspectRatio( + static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), + static_cast<float>(Layout::ScreenDocked::Height) / Layout::ScreenDocked::Width); + if (!ui.action_Single_Window_Mode->isChecked()) { + render_window->resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height); + } else { + resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height + menuBar()->height() + + (ui.action_Show_Status_Bar->isChecked() ? statusBar()->height() : 0)); + } +} + void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence; @@ -2224,6 +2352,7 @@ void GMainWindow::OnConfigure() { } configure_dialog.ApplyConfiguration(); + controller_dialog->refreshConfiguration(); InitializeHotkeys(); if (UISettings::values.theme != old_theme) { UpdateUITheme(); @@ -2240,13 +2369,13 @@ void GMainWindow::OnConfigure() { config->Save(); - if (UISettings::values.hide_mouse && emulation_running) { - setMouseTracking(true); - ui.centralwidget->setMouseTracking(true); + if ((UISettings::values.hide_mouse || Settings::values.mouse_panning) && emulation_running) { + render_window->installEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, true); mouse_hide_timer.start(); } else { - setMouseTracking(false); - ui.centralwidget->setMouseTracking(false); + render_window->removeEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, false); } UpdateStatusButtons(); @@ -2259,10 +2388,11 @@ void GMainWindow::OnConfigurePerGame() { void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { const auto v_file = Core::GetGameFileFromPath(vfs, file_name); + const auto& system = Core::System::GetInstance(); ConfigurePerGame dialog(this, title_id); dialog.LoadFromFile(v_file); - auto result = dialog.exec(); + const auto result = dialog.exec(); if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); @@ -2272,13 +2402,14 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } // Do not cause the global config to write local settings into the config file - Settings::RestoreGlobalState(); + const bool is_powered_on = system.IsPoweredOn(); + Settings::RestoreGlobalState(is_powered_on); - if (!Core::System::GetInstance().IsPoweredOn()) { + if (!is_powered_on) { config->Save(); } } else { - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(system.IsPoweredOn()); } } @@ -2338,11 +2469,11 @@ void GMainWindow::OnAbout() { } void GMainWindow::OnToggleFilterBar() { - game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); + game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked()); if (ui.action_Show_Filter_Bar->isChecked()) { - game_list->setFilterFocus(); + game_list->SetFilterFocus(); } else { - game_list->clearFilter(); + game_list->ClearFilter(); } } @@ -2359,6 +2490,11 @@ void GMainWindow::OnCaptureScreenshot() { .arg(title_id, 16, 16, QLatin1Char{'0'}) .arg(date); + if (!Common::FS::CreateDir(screenshot_path.toStdString())) { + OnStartGame(); + return; + } + #ifdef _WIN32 if (UISettings::values.enable_screenshot_save_as) { filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename, @@ -2373,6 +2509,29 @@ void GMainWindow::OnCaptureScreenshot() { OnStartGame(); } +// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant +void GMainWindow::MigrateConfigFiles() { + const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir); + const QDir config_dir = QDir(QString::fromStdString(config_dir_str)); + const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini"))); + + Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str)); + for (QStringList::const_iterator it = config_dir_list.constBegin(); + it != config_dir_list.constEnd(); ++it) { + const auto filename = it->toStdString(); + if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) { + continue; + } + const auto origin = fmt::format("{}{}", config_dir_str, filename); + const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename); + LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); + if (!Common::FS::Rename(origin, destination)) { + // Delete the old config file if one already exists in the new location. + Common::FS::Delete(origin); + } + } +} + void GMainWindow::UpdateWindowTitle(const std::string& title_name, const std::string& title_version) { const auto full_name = std::string(Common::g_build_fullname); @@ -2430,40 +2589,53 @@ void GMainWindow::UpdateStatusBar() { } void GMainWindow::UpdateStatusButtons() { - dock_status_button->setChecked(Settings::values.use_docked_mode); + dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); - Settings::values.use_asynchronous_gpu_emulation.SetValue( - Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); -#ifdef HAS_VULKAN renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); +} + +void GMainWindow::UpdateUISettings() { + if (!ui.action_Fullscreen->isChecked()) { + UISettings::values.geometry = saveGeometry(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); + } + UISettings::values.state = saveState(); +#if MICROPROFILE_ENABLED + UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); + UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif + UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); + UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); + UISettings::values.first_start = false; } void GMainWindow::HideMouseCursor() { - if (emu_thread == nullptr || UISettings::values.hide_mouse == false) { + if (emu_thread == nullptr || + (!UISettings::values.hide_mouse && !Settings::values.mouse_panning)) { mouse_hide_timer.stop(); ShowMouseCursor(); return; } - setCursor(QCursor(Qt::BlankCursor)); + render_window->setCursor(QCursor(Qt::BlankCursor)); } void GMainWindow::ShowMouseCursor() { - unsetCursor(); - if (emu_thread != nullptr && UISettings::values.hide_mouse) { + render_window->unsetCursor(); + if (emu_thread != nullptr && + (UISettings::values.hide_mouse || Settings::values.mouse_panning)) { mouse_hide_timer.start(); } } -void GMainWindow::mouseMoveEvent(QMouseEvent* event) { - ShowMouseCursor(); -} - -void GMainWindow::mousePressEvent(QMouseEvent* event) { - ShowMouseCursor(); +void GMainWindow::OnMouseActivity() { + if (!Settings::values.mouse_panning) { + ShowMouseCursor(); + } } void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) { @@ -2526,7 +2698,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det if (emu_thread) { ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn()); UpdateStatusButtons(); } } else { @@ -2569,8 +2741,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { const auto function = [this, &keys, &pdm] { keys.PopulateFromPartitionData(pdm); - Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); - keys.DeriveETicket(pdm); + + auto& system = Core::System::GetInstance(); + system.GetFileSystemController().CreateFactories(*vfs); + keys.DeriveETicket(pdm, system.GetContentProvider()); }; QString errors; @@ -2596,7 +2770,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { .arg(errors)); } - QProgressDialog prog; + QProgressDialog prog(this); prog.setRange(0, 0); prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " "system's performance.")); @@ -2627,7 +2801,7 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { - return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && + return FileSys::GetBaseTitleID(entry.title_id) == program_id && installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; }); @@ -2673,22 +2847,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - if (!ui.action_Fullscreen->isChecked()) { - UISettings::values.geometry = saveGeometry(); - UISettings::values.renderwindow_geometry = render_window->saveGeometry(); - } - UISettings::values.state = saveState(); -#if MICROPROFILE_ENABLED - UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); - UISettings::values.microprofile_visible = microProfileDialog->isVisible(); -#endif - UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); - UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); - UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); - UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); - UISettings::values.first_start = false; - + UpdateUISettings(); game_list->SaveInterfaceLayout(); hotkey_registry.SaveHotkeys(); @@ -2696,7 +2855,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { if (emu_thread != nullptr) { ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn()); UpdateStatusButtons(); } @@ -2793,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) { } void GMainWindow::UpdateUITheme() { - const QString default_icons = QStringLiteral(":/icons/default"); + const QString default_icons = QStringLiteral("default"); const QString& current_theme = UISettings::values.theme; const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second); QStringList theme_paths(default_theme_paths); @@ -2809,7 +2968,6 @@ void GMainWindow::UpdateUITheme() { qApp->setStyleSheet({}); setStyleSheet({}); } - theme_paths.append(default_icons); QIcon::setThemeName(default_icons); } else { const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss")); @@ -2821,10 +2979,7 @@ void GMainWindow::UpdateUITheme() { } else { LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); } - - const QString theme_name = QStringLiteral(":/icons/") + current_theme; - theme_paths.append({default_icons, theme_name}); - QIcon::setThemeName(theme_name); + QIcon::setThemeName(current_theme); } QIcon::setThemeSearchPaths(theme_paths); @@ -2864,7 +3019,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { UpdateWindowTitle(); if (emulation_running) - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); } void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { @@ -2889,6 +3044,8 @@ int main(int argc, char* argv[]) { MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); + Common::ConfigureNvidiaEnvironmentFlags(); + // Init settings params QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); QCoreApplication::setApplicationName(QStringLiteral("yuzu")); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0ce66a1ca..04d37d4ae 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -27,6 +27,7 @@ class GRenderWindow; class LoadingScreen; class MicroProfileDialog; class ProfilerWidget; +class ControllerDialog; class QLabel; class QPushButton; class QProgressDialog; @@ -37,6 +38,7 @@ enum class InstalledEntryType; class GameListPlaceholder; namespace Core::Frontend { +struct ControllerParameters; struct SoftwareKeyboardParameters; } // namespace Core::Frontend @@ -54,6 +56,10 @@ namespace InputCommon { class InputSubsystem; } +namespace Service::AM::Applets { +enum class WebExitReason : u32; +} + enum class EmulatedDirectoryTarget { NAND, SDMC, @@ -116,22 +122,29 @@ signals: void UpdateInstallProgress(); + void ControllerSelectorReconfigureFinished(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); + void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); - void WebBrowserUnpackRomFS(); - void WebBrowserFinishedBrowsing(); + void WebBrowserExtractOfflineRomFS(); + void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); public slots: void OnLoadComplete(); + void OnExecuteProgram(std::size_t program_index); + void ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); - void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); + void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); private: @@ -148,8 +161,8 @@ private: void PreventOSSleep(); void AllowOSSleep(); - bool LoadROM(const QString& filename); - void BootGame(const QString& filename); + bool LoadROM(const QString& filename, std::size_t program_index); + void BootGame(const QString& filename, std::size_t program_index = 0); void ShutdownGame(); void ShowTelemetryCallout(); @@ -230,11 +243,13 @@ private slots: void ShowFullscreen(); void HideFullscreen(); void ToggleWindowMode(); - void ResetWindowSize(); + void ResetWindowSize720(); + void ResetWindowSize1080(); void OnCaptureScreenshot(); void OnCoreError(Core::System::ResultStatus, std::string); void OnReinitializeKeys(ReinitializeKeyBehavior behavior); void OnLanguageChanged(const QString& locale); + void OnMouseActivity(); private: void RemoveBaseContent(u64 program_id, const QString& entry_type); @@ -245,10 +260,12 @@ private: std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); InstallResult InstallNSPXCI(const QString& filename); InstallResult InstallNCA(const QString& filename); + void MigrateConfigFiles(); void UpdateWindowTitle(const std::string& title_name = {}, const std::string& title_version = {}); void UpdateStatusBar(); void UpdateStatusButtons(); + void UpdateUISettings(); void HideMouseCursor(); void ShowMouseCursor(); void OpenURL(const QUrl& url); @@ -297,6 +314,7 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; WaitTreeWidget* waitTreeWidget; + ControllerDialog* controller_dialog; QAction* actions_recent_files[max_recent_files_item]; @@ -310,10 +328,14 @@ private: // Install progress dialog QProgressDialog* install_progress; + // Last game booted, used for multi-process apps + QString last_filename_booted; + + // Disables the web applet for the rest of the emulated session + bool disable_web_applet{}; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; }; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 87ea985d8..048870687 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -14,8 +14,8 @@ <string>yuzu</string> </property> <property name="windowIcon"> - <iconset> - <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset> + <iconset resource="yuzu.qrc"> + <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset> </property> <property name="tabShape"> <enum>QTabWidget::Rounded</enum> @@ -25,16 +25,7 @@ </property> <widget class="QWidget" name="centralwidget"> <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> + <property name="margin"> <number>0</number> </property> </layout> @@ -45,7 +36,7 @@ <x>0</x> <y>0</y> <width>1280</width> - <height>21</height> + <height>26</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -54,7 +45,7 @@ </property> <widget class="QMenu" name="menu_recent_files"> <property name="title"> - <string>Recent Files</string> + <string>&Recent Files</string> </property> </widget> <addaction name="action_Install_File_NAND"/> @@ -89,7 +80,7 @@ </property> <widget class="QMenu" name="menu_View_Debugging"> <property name="title"> - <string>Debugging</string> + <string>&Debugging</string> </property> </widget> <addaction name="action_Fullscreen"/> @@ -97,13 +88,14 @@ <addaction name="action_Display_Dock_Widget_Headers"/> <addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Status_Bar"/> - <addaction name="action_Reset_Window_Size"/> + <addaction name="action_Reset_Window_Size_720"/> + <addaction name="action_Reset_Window_Size_1080"/> <addaction name="separator"/> <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Tools"> <property name="title"> - <string>Tools</string> + <string>&Tools</string> </property> <addaction name="action_Rederive"/> <addaction name="separator"/> @@ -131,17 +123,17 @@ <bool>true</bool> </property> <property name="text"> - <string>Install Files to NAND...</string> + <string>&Install Files to NAND...</string> </property> </action> <action name="action_Load_File"> <property name="text"> - <string>Load File...</string> + <string>L&oad File...</string> </property> </action> <action name="action_Load_Folder"> <property name="text"> - <string>Load Folder...</string> + <string>Load &Folder...</string> </property> </action> <action name="action_Exit"> @@ -175,12 +167,12 @@ </action> <action name="action_Rederive"> <property name="text"> - <string>Reinitialize keys...</string> + <string>&Reinitialize keys...</string> </property> </action> <action name="action_About"> <property name="text"> - <string>About yuzu</string> + <string>&About yuzu</string> </property> </action> <action name="action_Single_Window_Mode"> @@ -188,12 +180,12 @@ <bool>true</bool> </property> <property name="text"> - <string>Single Window Mode</string> + <string>Single &Window Mode</string> </property> </action> <action name="action_Configure"> <property name="text"> - <string>Configure...</string> + <string>Con&figure...</string> </property> </action> <action name="action_Display_Dock_Widget_Headers"> @@ -201,7 +193,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Display Dock Widget Headers</string> + <string>Display D&ock Widget Headers</string> </property> </action> <action name="action_Show_Filter_Bar"> @@ -209,7 +201,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Show Filter Bar</string> + <string>Show &Filter Bar</string> </property> </action> <action name="action_Show_Status_Bar"> @@ -217,12 +209,26 @@ <bool>true</bool> </property> <property name="text"> + <string>Show &Status Bar</string> + </property> + <property name="iconText"> <string>Show Status Bar</string> </property> </action> - <action name="action_Reset_Window_Size"> + <action name="action_Reset_Window_Size_720"> + <property name="text"> + <string>Reset Window Size to &720p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 720p</string> + </property> + </action> + <action name="action_Reset_Window_Size_1080"> <property name="text"> - <string>Reset Window Size</string> + <string>Reset Window Size to &1080p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 1080p</string> </property> </action> <action name="action_Fullscreen"> @@ -230,7 +236,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Fullscreen</string> + <string>F&ullscreen</string> </property> </action> <action name="action_Restart"> @@ -238,7 +244,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Restart</string> + <string>&Restart</string> </property> </action> <action name="action_Load_Amiibo"> @@ -246,7 +252,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Load Amiibo...</string> + <string>Load &Amiibo...</string> </property> </action> <action name="action_Report_Compatibility"> @@ -254,7 +260,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Report Compatibility</string> + <string>&Report Compatibility</string> </property> <property name="visible"> <bool>false</bool> @@ -262,22 +268,22 @@ </action> <action name="action_Open_Mods_Page"> <property name="text"> - <string>Open Mods Page</string> + <string>Open &Mods Page</string> </property> </action> <action name="action_Open_Quickstart_Guide"> <property name="text"> - <string>Open Quickstart Guide</string> + <string>Open &Quickstart Guide</string> </property> </action> <action name="action_Open_FAQ"> <property name="text"> - <string>FAQ</string> + <string>&FAQ</string> </property> </action> <action name="action_Open_yuzu_Folder"> <property name="text"> - <string>Open yuzu Folder</string> + <string>Open &yuzu Folder</string> </property> </action> <action name="action_Capture_Screenshot"> @@ -285,7 +291,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Capture Screenshot</string> + <string>&Capture Screenshot</string> </property> </action> <action name="action_Configure_Current_Game"> @@ -293,10 +299,12 @@ <bool>false</bool> </property> <property name="text"> - <string>Configure Current Game..</string> + <string>Configure C&urrent Game...</string> </property> </action> </widget> - <resources/> + <resources> + <include location="yuzu.qrc"/> + </resources> <connections/> </ui> diff --git a/src/yuzu/util/url_request_interceptor.cpp b/src/yuzu/util/url_request_interceptor.cpp new file mode 100644 index 000000000..b637e771e --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.cpp @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include "yuzu/util/url_request_interceptor.h" + +UrlRequestInterceptor::UrlRequestInterceptor(QObject* p) : QWebEngineUrlRequestInterceptor(p) {} + +UrlRequestInterceptor::~UrlRequestInterceptor() = default; + +void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + const auto resource_type = info.resourceType(); + + switch (resource_type) { + case QWebEngineUrlRequestInfo::ResourceTypeMainFrame: + requested_url = info.requestUrl(); + emit FrameChanged(); + break; + case QWebEngineUrlRequestInfo::ResourceTypeSubFrame: + case QWebEngineUrlRequestInfo::ResourceTypeXhr: + emit FrameChanged(); + break; + default: + break; + } +} + +QUrl UrlRequestInterceptor::GetRequestedURL() const { + return requested_url; +} + +#endif diff --git a/src/yuzu/util/url_request_interceptor.h b/src/yuzu/util/url_request_interceptor.h new file mode 100644 index 000000000..8a7f7499f --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.h @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include <QObject> +#include <QWebEngineUrlRequestInterceptor> + +class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor { + Q_OBJECT + +public: + explicit UrlRequestInterceptor(QObject* p = nullptr); + ~UrlRequestInterceptor() override; + + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + + QUrl GetRequestedURL() const; + +signals: + void FrameChanged(); + +private: + QUrl requested_url; +}; + +#endif diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc new file mode 100644 index 000000000..5733cac98 --- /dev/null +++ b/src/yuzu/yuzu.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/img"> + <file alias="yuzu.ico">../../dist/yuzu.ico</file> + </qresource> +</RCC> diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index a15719a0f..8461f8896 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -1,29 +1,30 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) +function(create_resource file output filename) + # Read hex data from file + file(READ ${file} filedata HEX) + # Convert hex data for C compatibility + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) + # Write data to output file + set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE) + file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n") +endfunction() + add_executable(yuzu-cmd config.cpp config.h default_ini.h - emu_window/emu_window_sdl2_gl.cpp - emu_window/emu_window_sdl2_gl.h emu_window/emu_window_sdl2.cpp emu_window/emu_window_sdl2.h emu_window/emu_window_sdl2_gl.cpp emu_window/emu_window_sdl2_gl.h + emu_window/emu_window_sdl2_vk.cpp + emu_window/emu_window_sdl2_vk.h resource.h yuzu.cpp yuzu.rc ) -if (ENABLE_VULKAN) - target_sources(yuzu-cmd PRIVATE - emu_window/emu_window_sdl2_vk.cpp - emu_window/emu_window_sdl2_vk.h) - - target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include) - target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN) -endif() - create_target_directory_groups(yuzu-cmd) target_link_libraries(yuzu-cmd PRIVATE common core input_common) @@ -33,13 +34,16 @@ if (MSVC) endif() target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) +create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") +target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) + +target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include) + if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") endif() if (MSVC) include(CopyYuzuSDLDeps) - include(CopyYuzuUnicornDeps) copy_yuzu_SDL_deps(yuzu-cmd) - copy_yuzu_unicorn_deps(yuzu-cmd) endif() diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index e9f1c6500..aa0a9f288 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -228,24 +228,24 @@ static const std::array<int, 8> keyboard_mods{ void Config::ReadValues() { // Controls - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { const auto group = fmt::format("ControlsP{}", p); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.players[p].buttons[i] = + Settings::values.players.GetValue()[p].buttons[i] = sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param); - if (Settings::values.players[p].buttons[i].empty()) - Settings::values.players[p].buttons[i] = default_param; + if (Settings::values.players.GetValue()[p].buttons[i].empty()) + Settings::values.players.GetValue()[p].buttons[i] = default_param; } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.players[p].analogs[i] = + Settings::values.players.GetValue()[p].analogs[i] = sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param); - if (Settings::values.players[p].analogs[i].empty()) - Settings::values.players[p].analogs[i] = default_param; + if (Settings::values.players.GetValue()[p].analogs[i].empty()) + Settings::values.players.GetValue()[p].analogs[i] = default_param; } } @@ -288,24 +288,22 @@ void Config::ReadValues() { Settings::values.debug_pad_analogs[i] = default_param; } - Settings::values.vibration_enabled = - sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); + Settings::values.vibration_enabled.SetValue( + sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true)); + Settings::values.enable_accurate_vibrations.SetValue( + sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false)); + Settings::values.motion_enabled.SetValue( + sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true)); Settings::values.touchscreen.enabled = sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); - Settings::values.touchscreen.device = - sdl2_config->Get("ControlsGeneral", "touch_device", "engine:emu_window"); - Settings::values.touchscreen.finger = - sdl2_config->GetInteger("ControlsGeneral", "touch_finger", 0); Settings::values.touchscreen.rotation_angle = sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0); Settings::values.touchscreen.diameter_x = sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); Settings::values.touchscreen.diameter_y = sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); - Settings::values.udp_input_address = - sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); - Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger( - "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); + Settings::values.udp_input_servers = + sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_SRV); std::transform(keyboard_keys.begin(), keyboard_keys.end(), Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); @@ -341,8 +339,8 @@ void Config::ReadValues() { Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", ""); // System - Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); - const auto size = sdl2_config->GetInteger("System", "users_size", 0); + Settings::values.use_docked_mode.SetValue( + sdl2_config->GetBoolean("System", "use_docked_mode", true)); Settings::values.current_user = std::clamp<int>( sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); @@ -369,7 +367,7 @@ void Config::ReadValues() { // Core Settings::values.use_multi_core.SetValue( - sdl2_config->GetBoolean("Core", "use_multi_core", false)); + sdl2_config->GetBoolean("Core", "use_multi_core", true)); // Renderer const int renderer_backend = sdl2_config->GetInteger( @@ -390,14 +388,14 @@ void Config::ReadValues() { static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100))); Settings::values.use_disk_shader_cache.SetValue( sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false)); - const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); + const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 1); Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level)); Settings::values.use_asynchronous_gpu_emulation.SetValue( - sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false)); + sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true)); Settings::values.use_vsync.SetValue( static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); Settings::values.use_assembly_shaders.SetValue( - sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false)); + sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true)); Settings::values.use_asynchronous_shaders.SetValue( sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); Settings::values.use_asynchronous_shaders.SetValue( @@ -427,9 +425,6 @@ void Config::ReadValues() { // Debugging Settings::values.record_frame_times = sdl2_config->GetBoolean("Debugging", "record_frame_times", false); - Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); - Settings::values.gdbstub_port = - static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index aa9e40380..3ee0e037d 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -65,6 +65,14 @@ button_screenshot= lstick= rstick= +# Whether to enable or disable vibration +# 0: Disabled, 1 (default): Enabled +vibration_enabled= + +# Whether to enable or disable accurate vibrations +# 0 (default): Disabled, 1: Enabled +enable_accurate_vibrations= + # for motion input, the following devices are available: # - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: # - "update_period": update period in milliseconds (default to 100) @@ -94,7 +102,7 @@ udp_pad_index= [Core] # Whether to use multi-core for CPU emulation -# 0 (default): Disabled, 1: Enabled +# 0: Disabled, 1 (default): Enabled use_multi_core= [Cpu] @@ -163,7 +171,7 @@ max_anisotropy = use_vsync = # Whether to use OpenGL assembly shaders or not. NV_gpu_program5 is required. -# 0 (default): Off, 1: On +# 0: Off, 1 (default): On use_assembly_shaders = # Whether to allow asynchronous shader building. @@ -266,7 +274,7 @@ gamecard_path = [System] # Whether the system is docked -# 1: Yes, 0 (default): No +# 1 (default): Yes, 0: No use_docked_mode = # Allow the use of NFC in games @@ -310,9 +318,6 @@ log_filter = *:Trace [Debugging] # Record frame time data, can be found in the log directory. Boolean value record_frame_times = -# Port for listening to GDB connections. -use_gdbstub=false -gdbstub_port=24689 # Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them dump_exefs=false # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index a804d5185..7e391ab89 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -9,13 +9,13 @@ #include "core/perf_stats.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/mouse/mouse_input.h" #include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/yuzu_icon.h" -EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem_) - : system{system}, input_subsystem{input_subsystem_} { +EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_) + : input_subsystem{input_subsystem_} { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); @@ -30,22 +30,23 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { } void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { - TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - input_subsystem->GetMotionEmu()->Tilt(x, y); + TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0); + + input_subsystem->GetMouse()->MouseMove(x, y, 0, 0); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { if (button == SDL_BUTTON_LEFT) { if (state == SDL_PRESSED) { - TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0); } else { - TouchReleased(); + TouchReleased(0); } } else if (button == SDL_BUTTON_RIGHT) { if (state == SDL_PRESSED) { - input_subsystem->GetMotionEmu()->BeginTilt(x, y); + input_subsystem->GetMouse()->PressButton(x, y, button); } else { - input_subsystem->GetMotionEmu()->EndTilt(); + input_subsystem->GetMouse()->ReleaseButton(button); } } } @@ -67,16 +68,16 @@ void EmuWindow_SDL2::OnFingerDown(float x, float y) { // 3DS does const auto [px, py] = TouchToPixelPos(x, y); - TouchPressed(px, py); + TouchPressed(px, py, 0); } void EmuWindow_SDL2::OnFingerMotion(float x, float y) { const auto [px, py] = TouchToPixelPos(x, y); - TouchMoved(px, py); + TouchMoved(px, py, 0); } void EmuWindow_SDL2::OnFingerUp() { - TouchReleased(); + TouchReleased(0); } void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { @@ -122,62 +123,64 @@ void EmuWindow_SDL2::Fullscreen() { SDL_MaximizeWindow(render_window); } -void EmuWindow_SDL2::PollEvents() { +void EmuWindow_SDL2::WaitEvent() { + // Called on main thread SDL_Event event; - // SDL_PollEvent returns 0 when there are no more events in the event queue - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_SIZE_CHANGED: - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_MAXIMIZED: - case SDL_WINDOWEVENT_RESTORED: - OnResize(); - break; - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_EXPOSED: - is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED; - OnResize(); - break; - case SDL_WINDOWEVENT_CLOSE: - is_open = false; - break; - } - break; - case SDL_KEYDOWN: - case SDL_KEYUP: - OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state); - break; - case SDL_MOUSEMOTION: - // ignore if it came from touch - if (event.button.which != SDL_TOUCH_MOUSEID) - OnMouseMotion(event.motion.x, event.motion.y); - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - // ignore if it came from touch - if (event.button.which != SDL_TOUCH_MOUSEID) { - OnMouseButton(event.button.button, event.button.state, event.button.x, - event.button.y); - } - break; - case SDL_FINGERDOWN: - OnFingerDown(event.tfinger.x, event.tfinger.y); - break; - case SDL_FINGERMOTION: - OnFingerMotion(event.tfinger.x, event.tfinger.y); + if (!SDL_WaitEvent(&event)) { + LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", SDL_GetError()); + exit(1); + } + + switch (event.type) { + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + OnResize(); break; - case SDL_FINGERUP: - OnFingerUp(); + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_EXPOSED: + is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED; + OnResize(); break; - case SDL_QUIT: + case SDL_WINDOWEVENT_CLOSE: is_open = false; break; - default: - break; } + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state); + break; + case SDL_MOUSEMOTION: + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) + OnMouseMotion(event.motion.x, event.motion.y); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) { + OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y); + } + break; + case SDL_FINGERDOWN: + OnFingerDown(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERMOTION: + OnFingerMotion(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERUP: + OnFingerUp(); + break; + case SDL_QUIT: + is_open = false; + break; + default: + break; } const u32 current_time = SDL_GetTicks(); @@ -192,6 +195,22 @@ void EmuWindow_SDL2::PollEvents() { } } +void EmuWindow_SDL2::SetWindowIcon() { + SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); + if (yuzu_icon_stream == nullptr) { + LOG_WARNING(Frontend, "Failed to create yuzu icon stream."); + return; + } + SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); + if (window_icon == nullptr) { + LOG_WARNING(Frontend, "Failed to read BMP from stream."); + return; + } + // The icon is attached to the window pointer + SDL_SetWindowIcon(render_window, window_icon); + SDL_FreeSurface(window_icon); +} + void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 82750ffec..51a12a6a9 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -20,45 +20,44 @@ class InputSubsystem; class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem); + explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem); ~EmuWindow_SDL2(); - /// Polls window events - void PollEvents() override; - /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; /// Returns if window is shown (not minimized) bool IsShown() const override; - /// Presents the next frame - virtual void Present() = 0; + /// Wait for the next event on the main thread. + void WaitEvent(); + + // Sets the window icon from yuzu.bmp + void SetWindowIcon(); protected: - /// Called by PollEvents when a key is pressed or released. + /// Called by WaitEvent when a key is pressed or released. void OnKeyEvent(int key, u8 state); - /// Called by PollEvents when the mouse moves. + /// Called by WaitEvent when the mouse moves. void OnMouseMotion(s32 x, s32 y); - /// Called by PollEvents when a mouse button is pressed or released + /// Called by WaitEvent when a mouse button is pressed or released void OnMouseButton(u32 button, u8 state, s32 x, s32 y); /// Translates pixel position (0..1) to pixel positions std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const; - /// Called by PollEvents when a finger starts touching the touchscreen + /// Called by WaitEvent when a finger starts touching the touchscreen void OnFingerDown(float x, float y); - /// Called by PollEvents when a finger moves while touching the touchscreen + /// Called by WaitEvent when a finger moves while touching the touchscreen void OnFingerMotion(float x, float y); - /// Called by PollEvents when a finger stops touching the touchscreen + /// Called by WaitEvent when a finger stops touching the touchscreen void OnFingerUp(); - /// Called by PollEvents when any event that may cause the window to be resized occurs + /// Called by WaitEvent when any event that may cause the window to be resized occurs void OnResize(); /// Called when user passes the fullscreen parameter flag @@ -67,9 +66,6 @@ protected: /// Called when a configuration change affects the minimal size of the window void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override; - /// Instance of the system, used to access renderer for the presentation thread - Core::System& system; - /// Is the window still open? bool is_open = true; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 881b67a76..a02485c14 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -17,7 +17,6 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" #include "video_core/renderer_base.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" @@ -60,38 +59,25 @@ private: bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { std::vector<std::string_view> unsupported_ext; - if (!GLAD_GL_ARB_buffer_storage) - unsupported_ext.push_back("ARB_buffer_storage"); - if (!GLAD_GL_ARB_direct_state_access) - unsupported_ext.push_back("ARB_direct_state_access"); - if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) - unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); - if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) - unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); - if (!GLAD_GL_ARB_multi_bind) - unsupported_ext.push_back("ARB_multi_bind"); - if (!GLAD_GL_ARB_clip_control) - unsupported_ext.push_back("ARB_clip_control"); - // Extensions required to support some texture formats. - if (!GLAD_GL_EXT_texture_compression_s3tc) + if (!GLAD_GL_EXT_texture_compression_s3tc) { unsupported_ext.push_back("EXT_texture_compression_s3tc"); - if (!GLAD_GL_ARB_texture_compression_rgtc) + } + if (!GLAD_GL_ARB_texture_compression_rgtc) { unsupported_ext.push_back("ARB_texture_compression_rgtc"); - if (!GLAD_GL_ARB_depth_buffer_float) - unsupported_ext.push_back("ARB_depth_buffer_float"); + } - for (const auto& extension : unsupported_ext) + for (const auto& extension : unsupported_ext) { LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension); + } return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem) - : EmuWindow_SDL2{system, fullscreen, input_subsystem} { +EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen) + : EmuWindow_SDL2{input_subsystem} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -121,6 +107,8 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen, dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + SetWindowIcon(); + if (fullscreen) { Fullscreen(); } @@ -163,13 +151,3 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { return std::make_unique<SDLGLContext>(); } - -void EmuWindow_SDL2_GL::Present() { - SDL_GL_MakeCurrent(render_window, window_context); - SDL_GL_SetSwapInterval(Settings::values.use_vsync.GetValue() ? 1 : 0); - while (IsOpen()) { - system.Renderer().TryPresent(100); - SDL_GL_SwapWindow(render_window); - } - SDL_GL_MakeCurrent(render_window, nullptr); -} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h index 732a64edd..dba5c293c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h @@ -14,12 +14,9 @@ class InputSubsystem; class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem); + explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen); ~EmuWindow_SDL2_GL(); - void Present() override; - std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; private: diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp index 53491f86e..6f9b00461 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -19,9 +19,8 @@ #include <SDL.h> #include <SDL_syswm.h> -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem) - : EmuWindow_SDL2{system, fullscreen, input_subsystem} { +EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem) + : EmuWindow_SDL2{input_subsystem} { const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); render_window = @@ -36,6 +35,8 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, std::exit(EXIT_FAILURE); } + SetWindowIcon(); + switch (wm.subsystem) { #ifdef SDL_VIDEO_DRIVER_WINDOWS case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: @@ -74,7 +75,3 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const { return std::make_unique<DummyContext>(); } - -void EmuWindow_SDL2_VK::Present() { - // TODO (bunnei): ImplementMe -} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h index f99704d4c..bdfdc3c6f 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h @@ -19,11 +19,8 @@ class InputSubsystem; class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, - InputCommon::InputSubsystem* input_subsystem); - ~EmuWindow_SDL2_VK(); - - void Present() override; + explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem); + ~EmuWindow_SDL2_VK() override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; }; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 4f00c804d..982c41785 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -17,6 +17,7 @@ #include "common/logging/filter.h" #include "common/logging/log.h" #include "common/microprofile.h" +#include "common/nvidia_flags.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" @@ -25,7 +26,7 @@ #include "core/crypto/key_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/vfs_real.h" -#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -35,9 +36,7 @@ #include "yuzu_cmd/config.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" -#ifdef HAS_VULKAN #include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" -#endif #ifdef _WIN32 // windows.h needs to be included before shellapi.h @@ -64,7 +63,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] <filename>\n" - "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" "-f, --fullscreen Start in fullscreen mode\n" "-h, --help Display this help and exit\n" "-v, --version Output version information and exit\n" @@ -96,12 +94,8 @@ int main(int argc, char** argv) { Config config; int option_index = 0; - bool use_gdbstub = Settings::values.use_gdbstub; - u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); InitializeLogging(); - - char* endarg; #ifdef _WIN32 int argc_w; auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); @@ -116,26 +110,17 @@ int main(int argc, char** argv) { bool fullscreen = false; static struct option long_options[] = { - {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'}, - {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, - {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0}, + {"fullscreen", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {"program", optional_argument, 0, 'p'}, + {0, 0, 0, 0}, }; while (optind < argc) { int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index); if (arg != -1) { switch (static_cast<char>(arg)) { - case 'g': - errno = 0; - gdb_port = strtoul(optarg, &endarg, 0); - use_gdbstub = true; - if (endarg == optarg) - errno = EINVAL; - if (errno != 0) { - perror("--gdbport"); - exit(1); - } - break; case 'f': fullscreen = true; LOG_INFO(Frontend, "Starting in fullscreen mode..."); @@ -168,32 +153,27 @@ int main(int argc, char** argv) { MicroProfileOnThreadCreate("EmuThread"); SCOPE_EXIT({ MicroProfileShutdown(); }); + Common::ConfigureNvidiaEnvironmentFlags(); + if (filepath.empty()) { LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); return -1; } - // Apply the command line arguments - Settings::values.gdbstub_port = gdb_port; - Settings::values.use_gdbstub = use_gdbstub; - Settings::Apply(); - - Core::System& system{Core::System::GetInstance()}; + auto& system{Core::System::GetInstance()}; InputCommon::InputSubsystem input_subsystem; + // Apply the command line arguments + Settings::Apply(system); + std::unique_ptr<EmuWindow_SDL2> emu_window; switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: - emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, &input_subsystem); + emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, fullscreen); break; case Settings::RendererBackend::Vulkan: -#ifdef HAS_VULKAN - emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, &input_subsystem); + emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem); break; -#else - LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); - return 1; -#endif } system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); @@ -223,7 +203,7 @@ int main(int argc, char** argv) { const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); const u16 error_id = static_cast<u16>(load_result) - loader_id; LOG_CRITICAL(Frontend, - "While attempting to load the ROM requested, an error occured. Please " + "While attempting to load the ROM requested, an error occurred. Please " "refer to the yuzu wiki for more information or the yuzu discord for " "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", loader_id, error_id, static_cast<Loader::ResultStatus>(error_id)); @@ -235,16 +215,15 @@ int main(int argc, char** argv) { // Core is loaded, start the GPU (makes the GPU contexts current to this thread) system.GPU().Start(); - system.Renderer().Rasterizer().LoadDiskResources(); + system.Renderer().ReadRasterizer()->LoadDiskResources( + system.CurrentProcess()->GetTitleID(), false, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); - std::thread render_thread([&emu_window] { emu_window->Present(); }); - system.Run(); + void(system.Run()); while (emu_window->IsOpen()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + emu_window->WaitEvent(); } - system.Pause(); - render_thread.join(); - + void(system.Pause()); system.Shutdown(); detached_tasks.WaitForAllTasks(); diff --git a/src/yuzu_tester/CMakeLists.txt b/src/yuzu_tester/CMakeLists.txt deleted file mode 100644 index 06c2ee011..000000000 --- a/src/yuzu_tester/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) - -add_executable(yuzu-tester - config.cpp - config.h - default_ini.h - emu_window/emu_window_sdl2_hide.cpp - emu_window/emu_window_sdl2_hide.h - resource.h - service/yuzutest.cpp - service/yuzutest.h - yuzu.cpp - yuzu.rc -) - -create_target_directory_groups(yuzu-tester) - -target_link_libraries(yuzu-tester PRIVATE common core input_common) -target_link_libraries(yuzu-tester PRIVATE inih glad) -if (MSVC) - target_link_libraries(yuzu-tester PRIVATE getopt) -endif() -target_link_libraries(yuzu-tester PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) - -if(UNIX AND NOT APPLE) - install(TARGETS yuzu-tester RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") -endif() - -if (MSVC) - include(CopyYuzuSDLDeps) - include(CopyYuzuUnicornDeps) - copy_yuzu_SDL_deps(yuzu-tester) - copy_yuzu_unicorn_deps(yuzu-tester) -endif() diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp deleted file mode 100644 index aaf59129a..000000000 --- a/src/yuzu_tester/config.cpp +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <sstream> -#include <SDL.h> -#include <inih/cpp/INIReader.h> -#include "common/file_util.h" -#include "common/logging/log.h" -#include "common/param_package.h" -#include "core/hle/service/acc/profile_manager.h" -#include "core/settings.h" -#include "input_common/main.h" -#include "yuzu_tester/config.h" -#include "yuzu_tester/default_ini.h" - -namespace FS = Common::FS; - -Config::Config() { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini"; - sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); - - Reload(); -} - -Config::~Config() = default; - -bool Config::LoadINI(const std::string& default_contents, bool retry) { - const char* location = this->sdl2_config_loc.c_str(); - if (sdl2_config->ParseError() < 0) { - if (retry) { - LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); - FS::CreateFullPath(location); - FS::WriteStringToFile(true, default_contents, location); - sdl2_config = std::make_unique<INIReader>(location); // Reopen file - - return LoadINI(default_contents, false); - } - LOG_ERROR(Config, "Failed."); - return false; - } - LOG_INFO(Config, "Successfully loaded {}", location); - return true; -} - -void Config::ReadValues() { - // Controls - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - Settings::values.players[p].buttons[i] = ""; - } - - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - Settings::values.players[p].analogs[i] = ""; - } - } - - Settings::values.mouse_enabled = false; - for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - Settings::values.mouse_buttons[i] = ""; - } - - Settings::values.motion_device = ""; - - Settings::values.keyboard_enabled = false; - - Settings::values.debug_pad_enabled = false; - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - Settings::values.debug_pad_buttons[i] = ""; - } - - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - Settings::values.debug_pad_analogs[i] = ""; - } - - Settings::values.vibration_enabled = true; - Settings::values.touchscreen.enabled = ""; - Settings::values.touchscreen.device = ""; - Settings::values.touchscreen.finger = 0; - Settings::values.touchscreen.rotation_angle = 0; - Settings::values.touchscreen.diameter_x = 15; - Settings::values.touchscreen.diameter_y = 15; - - Settings::values.use_docked_mode = - sdl2_config->GetBoolean("Controls", "use_docked_mode", false); - - // Data Storage - Settings::values.use_virtual_sd = - sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); - FS::GetUserPath(Common::FS::UserPath::NANDDir, - sdl2_config->Get("Data Storage", "nand_directory", - Common::FS::GetUserPath(Common::FS::UserPath::NANDDir))); - FS::GetUserPath(Common::FS::UserPath::SDMCDir, - sdl2_config->Get("Data Storage", "sdmc_directory", - Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir))); - - // System - Settings::values.current_user = std::clamp<int>( - sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); - - const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); - if (rng_seed_enabled) { - Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0)); - } else { - Settings::values.rng_seed.SetValue(std::nullopt); - } - - const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); - if (custom_rtc_enabled) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0))); - } else { - Settings::values.custom_rtc.SetValue(std::nullopt); - } - - // Core - Settings::values.use_multi_core.SetValue( - sdl2_config->GetBoolean("Core", "use_multi_core", false)); - - // Renderer - Settings::values.aspect_ratio.SetValue( - static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0))); - Settings::values.max_anisotropy.SetValue( - static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0))); - Settings::values.use_frame_limit.SetValue(false); - Settings::values.frame_limit.SetValue(100); - Settings::values.use_disk_shader_cache.SetValue( - sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false)); - const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); - Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level)); - Settings::values.use_asynchronous_gpu_emulation.SetValue( - sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false)); - Settings::values.use_fast_gpu_time.SetValue( - sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); - - Settings::values.bg_red.SetValue( - static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0))); - Settings::values.bg_green.SetValue( - static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0))); - Settings::values.bg_blue.SetValue( - static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0))); - - // Audio - Settings::values.sink_id = "null"; - Settings::values.enable_audio_stretching.SetValue(false); - Settings::values.audio_device_id = "auto"; - Settings::values.volume.SetValue(0); - - Settings::values.language_index.SetValue( - sdl2_config->GetInteger("System", "language_index", 1)); - - // Miscellaneous - Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); - Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); - - // Debugging - Settings::values.use_gdbstub = false; - Settings::values.program_args = ""; - Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); - Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); - - const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); - std::stringstream ss(title_list); - std::string line; - while (std::getline(ss, line, '|')) { - const auto title_id = std::stoul(line, nullptr, 16); - const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); - - std::stringstream inner_ss(disabled_list); - std::string inner_line; - std::vector<std::string> out; - while (std::getline(inner_ss, inner_line, '|')) { - out.push_back(inner_line); - } - - Settings::values.disabled_addons.insert_or_assign(title_id, out); - } - - // Web Service - Settings::values.enable_telemetry = - sdl2_config->GetBoolean("WebService", "enable_telemetry", true); - Settings::values.web_api_url = - sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); - Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); - Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); -} - -void Config::Reload() { - LoadINI(DefaultINI::sdl2_config_file); - ReadValues(); -} diff --git a/src/yuzu_tester/config.h b/src/yuzu_tester/config.h deleted file mode 100644 index 3b68e5bc9..000000000 --- a/src/yuzu_tester/config.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <string> - -class INIReader; - -class Config { - std::unique_ptr<INIReader> sdl2_config; - std::string sdl2_config_loc; - - bool LoadINI(const std::string& default_contents = "", bool retry = true); - void ReadValues(); - -public: - Config(); - ~Config(); - - void Reload(); -}; diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h deleted file mode 100644 index 3eb64e9d7..000000000 --- a/src/yuzu_tester/default_ini.h +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace DefaultINI { - -const char* sdl2_config_file = R"( -[Core] -# Whether to use multi-core for CPU emulation -# 0 (default): Disabled, 1: Enabled -use_multi_core= - -[Cpu] -# Enable inline page tables optimization (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_page_tables = - -# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps) -# 0: Disabled, 1 (default): Enabled -cpuopt_block_linking = - -# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns) -# 0: Disabled, 1 (default): Enabled -cpuopt_return_stack_buffer = - -# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture) -# 0: Disabled, 1 (default): Enabled -cpuopt_fast_dispatcher = - -# Enable context elimination CPU Optimization (reduce host memory use for guest context) -# 0: Disabled, 1 (default): Enabled -cpuopt_context_elimination = - -# Enable constant propagation CPU optimization (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_const_prop = - -# Enable miscellaneous CPU optimizations (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_misc_ir = - -# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access) -# 0: Disabled, 1 (default): Enabled -cpuopt_reduce_misalign_checks = - -[Renderer] -# Whether to use software or hardware rendering. -# 0: Software, 1 (default): Hardware -use_hw_renderer = - -# Whether to use the Just-In-Time (JIT) compiler for shader emulation -# 0: Interpreter (slow), 1 (default): JIT (fast) -use_shader_jit = - -# Aspect ratio -# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window -aspect_ratio = - -# Anisotropic filtering -# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x -max_anisotropy = - -# Whether to enable V-Sync (caps the framerate at 60FPS) or not. -# 0 (default): Off, 1: On -use_vsync = - -# Whether to use disk based shader cache -# 0 (default): Off, 1 : On -use_disk_shader_cache = - -# Whether to use accurate GPU emulation -# 0 (default): Off (fast), 1 : On (slow) -use_accurate_gpu_emulation = - -# Whether to use asynchronous GPU emulation -# 0 : Off (slow), 1 (default): On (fast) -use_asynchronous_gpu_emulation = - -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0.0-1.0. Defaults to 1.0 for all. -bg_red = -bg_blue = -bg_green = - -[Layout] -# Layout for the screen inside the render window. -# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen -layout_option = - -# Toggle custom layout (using the settings below) on or off. -# 0 (default): Off, 1: On -custom_layout = - -# Screen placement when using Custom layout option -# 0x, 0y is the top left corner of the render window. -custom_top_left = -custom_top_top = -custom_top_right = -custom_top_bottom = -custom_bottom_left = -custom_bottom_top = -custom_bottom_right = -custom_bottom_bottom = - -# Swaps the prominent screen with the other screen. -# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. -# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent -swap_screen = - -[Data Storage] -# Whether to create a virtual SD card. -# 1 (default): Yes, 0: No -use_virtual_sd = - -[System] -# Whether the system is docked -# 1: Yes, 0 (default): No -use_docked_mode = - -# Allow the use of NFC in games -# 1 (default): Yes, 0 : No -enable_nfc = - -# Sets the seed for the RNG generator built into the switch -# rng_seed will be ignored and randomly generated if rng_seed_enabled is false -rng_seed_enabled = -rng_seed = - -# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service -# This will auto-increment, with the time set being the time the game is started -# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used -custom_rtc_enabled = -custom_rtc = - -# Sets the account username, max length is 32 characters -# yuzu (default) -username = yuzu - -# Sets the systems language index -# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, -# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, -# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese -language_index = - -# The system region that yuzu will use during emulation -# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan -region_value = - -[Miscellaneous] -# A filter which removes logs below a certain logging level. -# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical -log_filter = *:Trace - -[Debugging] -# Arguments to be passed to argv/argc in the emulated program. It is preferable to use the testing service datastring -program_args= -# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them -dump_exefs=false -# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them -dump_nso=false - -[WebService] -# Whether or not to enable telemetry -# 0: No, 1 (default): Yes -enable_telemetry = -# URL for Web API -web_api_url = https://api.yuzu-emu.org -# Username and token for yuzu Web Service -# See https://profile.yuzu-emu.org/ for more info -yuzu_username = -yuzu_token = - -[AddOns] -# Used to disable add-ons -# List of title IDs of games that will have add-ons disabled (separated by '|'): -title_ids = -# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') -# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey -)"; -} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp deleted file mode 100644 index 78f75fb38..000000000 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cstdlib> -#include <string> - -#include <fmt/format.h> - -#define SDL_MAIN_HANDLED -#include <SDL.h> - -#include <glad/glad.h> - -#include "common/logging/log.h" -#include "common/scm_rev.h" -#include "core/settings.h" -#include "input_common/main.h" -#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" - -bool EmuWindow_SDL2_Hide::SupportsRequiredGLExtensions() { - std::vector<std::string> unsupported_ext; - - if (!GLAD_GL_ARB_direct_state_access) - unsupported_ext.push_back("ARB_direct_state_access"); - if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) - unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); - if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) - unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); - if (!GLAD_GL_ARB_multi_bind) - unsupported_ext.push_back("ARB_multi_bind"); - - // Extensions required to support some texture formats. - if (!GLAD_GL_EXT_texture_compression_s3tc) - unsupported_ext.push_back("EXT_texture_compression_s3tc"); - if (!GLAD_GL_ARB_texture_compression_rgtc) - unsupported_ext.push_back("ARB_texture_compression_rgtc"); - if (!GLAD_GL_ARB_depth_buffer_float) - unsupported_ext.push_back("ARB_depth_buffer_float"); - - for (const std::string& ext : unsupported_ext) - LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext); - - return unsupported_ext.empty(); -} - -EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() { - // Initialize the window - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); - exit(1); - } - - input_subsystem->Initialize(); - - SDL_SetMainReady(); - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - - std::string window_title = fmt::format("yuzu-tester {} | {}-{}", Common::g_build_fullname, - Common::g_scm_branch, Common::g_scm_desc); - render_window = SDL_CreateWindow(window_title.c_str(), - SDL_WINDOWPOS_UNDEFINED, // x position - SDL_WINDOWPOS_UNDEFINED, // y position - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | - SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); - - if (render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); - exit(1); - } - - gl_context = SDL_GL_CreateContext(render_window); - - if (gl_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError()); - exit(1); - } - - if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { - LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); - exit(1); - } - - if (!SupportsRequiredGLExtensions()) { - LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting..."); - exit(1); - } - - SDL_PumpEvents(); - SDL_GL_SetSwapInterval(false); - LOG_INFO(Frontend, "yuzu-tester Version: {} | {}-{}", Common::g_build_fullname, - Common::g_scm_branch, Common::g_scm_desc); - Settings::LogSettings(); -} - -EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { - input_subsystem->Shutdown(); - SDL_GL_DeleteContext(gl_context); - SDL_Quit(); -} - -void EmuWindow_SDL2_Hide::PollEvents() {} - -bool EmuWindow_SDL2_Hide::IsShown() const { - return false; -} - -class SDLGLContext : public Core::Frontend::GraphicsContext { -public: - explicit SDLGLContext() { - // create a hidden window to make the shared context against - window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, - SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); - context = SDL_GL_CreateContext(window); - } - - ~SDLGLContext() { - DoneCurrent(); - SDL_GL_DeleteContext(context); - SDL_DestroyWindow(window); - } - - void MakeCurrent() override { - SDL_GL_MakeCurrent(window, context); - } - - void DoneCurrent() override { - SDL_GL_MakeCurrent(window, nullptr); - } - -private: - SDL_Window* window; - SDL_GLContext context; -}; - -std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_Hide::CreateSharedContext() const { - return std::make_unique<SDLGLContext>(); -} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h deleted file mode 100644 index a553b4b95..000000000 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/emu_window.h" - -struct SDL_Window; - -namespace InputCommon { -class InputSubsystem; -} - -class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow { -public: - explicit EmuWindow_SDL2_Hide(); - ~EmuWindow_SDL2_Hide(); - - /// Polls window events - void PollEvents() override; - - /// Whether the screen is being shown or not. - bool IsShown() const override; - - std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; - -private: - /// Whether the GPU and driver supports the OpenGL extension required - bool SupportsRequiredGLExtensions(); - - std::unique_ptr<InputCommon::InputSubsystem> input_subsystem; - - /// Internal SDL2 render window - SDL_Window* render_window; - - using SDL_GLContext = void*; - /// The OpenGL context associated with the window - SDL_GLContext gl_context; -}; diff --git a/src/yuzu_tester/resource.h b/src/yuzu_tester/resource.h deleted file mode 100644 index df8e459e4..000000000 --- a/src/yuzu_tester/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by pcafe.rc -// -#define IDI_ICON3 103 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 105 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/src/yuzu_tester/service/yuzutest.cpp b/src/yuzu_tester/service/yuzutest.cpp deleted file mode 100644 index 2d3f6e3a7..000000000 --- a/src/yuzu_tester/service/yuzutest.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include "common/string_util.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/service/service.h" -#include "core/hle/service/sm/sm.h" -#include "yuzu_tester/service/yuzutest.h" - -namespace Service::Yuzu { - -constexpr u64 SERVICE_VERSION = 0x00000002; - -class YuzuTest final : public ServiceFramework<YuzuTest> { -public: - explicit YuzuTest(std::string data, - std::function<void(std::vector<TestResult>)> finish_callback) - : ServiceFramework{"yuzutest"}, data(std::move(data)), - finish_callback(std::move(finish_callback)) { - static const FunctionInfo functions[] = { - {0, &YuzuTest::Initialize, "Initialize"}, - {1, &YuzuTest::GetServiceVersion, "GetServiceVersion"}, - {2, &YuzuTest::GetData, "GetData"}, - {10, &YuzuTest::StartIndividual, "StartIndividual"}, - {20, &YuzuTest::FinishIndividual, "FinishIndividual"}, - {100, &YuzuTest::ExitProgram, "ExitProgram"}, - }; - - RegisterHandlers(functions); - } - -private: - void Initialize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Frontend, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } - - void GetServiceVersion(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Frontend, "called"); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push(SERVICE_VERSION); - } - - void GetData(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Frontend, "called"); - const auto size = ctx.GetWriteBufferSize(); - const auto write_size = std::min(size, data.size()); - ctx.WriteBuffer(data.data(), write_size); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(static_cast<u32>(write_size)); - } - - void StartIndividual(Kernel::HLERequestContext& ctx) { - const auto name_raw = ctx.ReadBuffer(); - - const auto name = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(name_raw.data()), name_raw.size()); - - LOG_DEBUG(Frontend, "called, name={}", name); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } - - void FinishIndividual(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - const auto code = rp.PopRaw<u32>(); - - const auto result_data_raw = ctx.ReadBuffer(); - const auto test_name_raw = ctx.ReadBuffer(1); - - const auto data = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(result_data_raw.data()), result_data_raw.size()); - const auto test_name = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(test_name_raw.data()), test_name_raw.size()); - - LOG_INFO(Frontend, "called, result_code={:08X}, data={}, name={}", code, data, test_name); - - results.push_back({code, data, test_name}); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } - - void ExitProgram(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Frontend, "called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - finish_callback(std::move(results)); - } - - std::string data; - - std::vector<TestResult> results; - std::function<void(std::vector<TestResult>)> finish_callback; -}; - -void InstallInterfaces(SM::ServiceManager& sm, std::string data, - std::function<void(std::vector<TestResult>)> finish_callback) { - std::make_shared<YuzuTest>(data, finish_callback)->InstallAsService(sm); -} - -} // namespace Service::Yuzu diff --git a/src/yuzu_tester/service/yuzutest.h b/src/yuzu_tester/service/yuzutest.h deleted file mode 100644 index eca129c8c..000000000 --- a/src/yuzu_tester/service/yuzutest.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <functional> -#include <string> - -namespace Service::SM { -class ServiceManager; -} - -namespace Service::Yuzu { - -struct TestResult { - u32 code; - std::string data; - std::string name; -}; - -void InstallInterfaces(SM::ServiceManager& sm, std::string data, - std::function<void(std::vector<TestResult>)> finish_callback); - -} // namespace Service::Yuzu diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp deleted file mode 100644 index 7acf0caad..000000000 --- a/src/yuzu_tester/yuzu.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <chrono> -#include <iostream> -#include <memory> -#include <string> -#include <thread> - -#include <fmt/ostream.h> - -#include "common/common_paths.h" -#include "common/detached_tasks.h" -#include "common/file_util.h" -#include "common/logging/backend.h" -#include "common/logging/filter.h" -#include "common/logging/log.h" -#include "common/microprofile.h" -#include "common/scm_rev.h" -#include "common/scope_exit.h" -#include "common/string_util.h" -#include "common/telemetry.h" -#include "core/core.h" -#include "core/crypto/key_manager.h" -#include "core/file_sys/registered_cache.h" -#include "core/file_sys/vfs_real.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/loader.h" -#include "core/settings.h" -#include "core/telemetry_session.h" -#include "video_core/renderer_base.h" -#include "yuzu_tester/config.h" -#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" -#include "yuzu_tester/service/yuzutest.h" - -#ifdef _WIN32 -// windows.h needs to be included before shellapi.h -#include <windows.h> - -#include <shellapi.h> -#endif - -#undef _UNICODE -#include <getopt.h> -#ifndef _MSC_VER -#include <unistd.h> -#endif - -#ifdef _WIN32 -extern "C" { -// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable -// graphics -__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -} -#endif - -static void PrintHelp(const char* argv0) { - std::cout << "Usage: " << argv0 - << " [options] <filename>\n" - "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n" - "-d, --datastring Pass following string as data to test service command #2\n" - "-l, --log Log to console in addition to file (will log to file only " - "by default)\n"; -} - -static void PrintVersion() { - std::cout << "yuzu [Test Utility] " << Common::g_scm_branch << " " << Common::g_scm_desc - << std::endl; -} - -static void InitializeLogging(bool console) { - Log::Filter log_filter(Log::Level::Debug); - log_filter.ParseFilterString(Settings::values.log_filter); - Log::SetGlobalFilter(log_filter); - - if (console) - Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); - - const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir); - Common::FS::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); -#ifdef _WIN32 - Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); -#endif -} - -/// Application entry point -int main(int argc, char** argv) { - Common::DetachedTasks detached_tasks; - Config config; - - int option_index = 0; - -#ifdef _WIN32 - int argc_w; - auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); - - if (argv_w == nullptr) { - std::cout << "Failed to get command line arguments" << std::endl; - return -1; - } -#endif - std::string filepath; - - static struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, - {"datastring", optional_argument, 0, 'd'}, - {"log", no_argument, 0, 'l'}, - {0, 0, 0, 0}, - }; - - bool console_log = false; - std::string datastring; - - while (optind < argc) { - int arg = getopt_long(argc, argv, "hvdl::", long_options, &option_index); - if (arg != -1) { - switch (static_cast<char>(arg)) { - case 'h': - PrintHelp(argv[0]); - return 0; - case 'v': - PrintVersion(); - return 0; - case 'd': - datastring = argv[optind]; - ++optind; - break; - case 'l': - console_log = true; - break; - } - } else { -#ifdef _WIN32 - filepath = Common::UTF16ToUTF8(argv_w[optind]); -#else - filepath = argv[optind]; -#endif - optind++; - } - } - - InitializeLogging(console_log); - -#ifdef _WIN32 - LocalFree(argv_w); -#endif - - MicroProfileOnThreadCreate("EmuThread"); - SCOPE_EXIT({ MicroProfileShutdown(); }); - - if (filepath.empty()) { - LOG_CRITICAL(Frontend, "Failed to load application: No application specified"); - std::cout << "Failed to load application: No application specified" << std::endl; - PrintHelp(argv[0]); - return -1; - } - - Settings::values.use_gdbstub = false; - Settings::Apply(); - - std::unique_ptr<EmuWindow_SDL2_Hide> emu_window{std::make_unique<EmuWindow_SDL2_Hide>()}; - - bool finished = false; - int return_value = 0; - const auto callback = [&finished, - &return_value](std::vector<Service::Yuzu::TestResult> results) { - finished = true; - return_value = 0; - - // Find the minimum length needed to fully enclose all test names (and the header field) in - // the fmt::format column by first finding the maximum size of any test name and comparing - // that to 9, the string length of 'Test Name' - const auto needed_length_name = - std::max<u64>(std::max_element(results.begin(), results.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.name.size() < rhs.name.size(); - }) - ->name.size(), - 9ull); - - std::size_t passed = 0; - std::size_t failed = 0; - - std::cout << fmt::format("Result [Res Code] | {:<{}} | Extra Data", "Test Name", - needed_length_name) - << std::endl; - - for (const auto& res : results) { - const auto main_res = res.code == 0 ? "PASSED" : "FAILED"; - if (res.code == 0) - ++passed; - else - ++failed; - std::cout << fmt::format("{} [{:08X}] | {:<{}} | {}", main_res, res.code, res.name, - needed_length_name, res.data) - << std::endl; - } - - std::cout << std::endl - << fmt::format("{:4d} Passed | {:4d} Failed | {:4d} Total | {:2.2f} Passed Ratio", - passed, failed, passed + failed, - static_cast<float>(passed) / (passed + failed)) - << std::endl - << (failed == 0 ? "PASSED" : "FAILED") << std::endl; - - if (failed > 0) - return_value = -1; - }; - - Core::System& system{Core::System::GetInstance()}; - system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); - system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); - - SCOPE_EXIT({ system.Shutdown(); }); - - const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; - - switch (load_result) { - case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); - return -1; - case Core::System::ResultStatus::ErrorLoader: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); - return -1; - case Core::System::ResultStatus::ErrorNotInitialized: - LOG_CRITICAL(Frontend, "CPUCore not initialized"); - return -1; - case Core::System::ResultStatus::ErrorVideoCore: - LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); - return -1; - case Core::System::ResultStatus::Success: - break; // Expected case - default: - if (static_cast<u32>(load_result) > - static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { - const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); - const u16 error_id = static_cast<u16>(load_result) - loader_id; - LOG_CRITICAL(Frontend, - "While attempting to load the ROM requested, an error occured. Please " - "refer to the yuzu wiki for more information or the yuzu discord for " - "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", - loader_id, error_id, static_cast<Loader::ResultStatus>(error_id)); - } - } - - Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback); - - system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", - "SDLHideTester"); - - system.GPU().Start(); - system.Renderer().Rasterizer().LoadDiskResources(); - - system.Run(); - while (!finished) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - system.Pause(); - - detached_tasks.WaitForAllTasks(); - return return_value; -} diff --git a/src/yuzu_tester/yuzu.rc b/src/yuzu_tester/yuzu.rc deleted file mode 100644 index 0cde75e2f..000000000 --- a/src/yuzu_tester/yuzu.rc +++ /dev/null @@ -1,17 +0,0 @@ -#include "winresrc.h" -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -YUZU_ICON ICON "../../dist/yuzu.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// RT_MANIFEST -// - -0 RT_MANIFEST "../../dist/yuzu.manifest" |