diff options
Diffstat (limited to 'src')
560 files changed, 34889 insertions, 13300 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e977e8a8..71efbb40d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,12 @@ else() -Wno-unused-parameter ) + # TODO: Remove when we update to a GCC compiler that enables this + # by default (i.e. GCC 10 or newer). + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + add_compile_options(-fconcepts) + endif() + if (ARCHITECTURE_x86_64) add_compile_options("-mcx16") endif() diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5ef38a337..6a7075f73 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -12,22 +12,48 @@ add_library(audio_core STATIC buffer.h codec.cpp codec.h + command_generator.cpp + command_generator.h common.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=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=reorder + -Werror=sign-compare + -Werror=unused-variable + ) +endif() + target_link_libraries(audio_core PUBLIC common core) target_link_libraries(audio_core PRIVATE SoundTouch) diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 49ab9d3e1..689a54508 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -197,4 +197,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]); + 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_renderer.cpp b/src/audio_core/audio_renderer.cpp index d64452617..a7e851bb8 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -2,95 +2,46 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "audio_core/algorithm/interpolate.h" +#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 { - -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; - } - - const VoiceOutStatus& GetOutStatus() const { - return out_status; - } - - const VoiceInfo& GetInfo() const { - return info; - } - - VoiceInfo& GetInfo() { - return info; - } - - 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 EffectInStatus& GetInfo() const { - return info; - } - - EffectInStatus& GetInfo() { - return info; - } - - void UpdateState(Core::Memory::Memory& memory); - -private: - EffectOutStatus out_status{}; - EffectInStatus info{}; -}; - AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, - AudioRendererParameter params, + AudioCommon::AudioRendererParameter params, std::shared_ptr<Kernel::WritableEvent> buffer_event, 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}, buffer_event{buffer_event}, + 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), + temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) { 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), + [=]() { buffer_event->Signal(); }); audio_out->StartStream(stream); QueueMixedBuffer(0); QueueMixedBuffer(1); QueueMixedBuffer(2); + QueueMixedBuffer(3); } AudioRenderer::~AudioRenderer() = default; @@ -111,355 +62,200 @@ 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); - - if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) { - LOG_ERROR(Audio, "Failed to update behavior info input parameters"); - return 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); - } - - 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); - } - - // 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; - } - } - - // 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); - } - } - - for (auto& effect : effects) { - effect.UpdateState(memory); - } - - // Release previous buffers and queue next ones for playback - ReleaseAndQueueBuffers(); - - // Copy output header - UpdateDataHeader response_data{worker_params}; - if (behavior_info.IsElapsedFrameCountSupported()) { - response_data.render_info = sizeof(RendererInfo); - response_data.total_size += sizeof(RendererInfo); - } - - 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); - } +static constexpr s16 ClampToS16(s32 value) { + return static_cast<s16>(std::clamp(value, -32768, 32767)); +} - 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); - } +ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, + std::vector<u8>& output_params) { - // 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}; + InfoUpdater info_updater{input_params, output_params, behavior_info}; - 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.UpdateBehaviorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update behavior info input parameters"); + 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)); + if (!info_updater.UpdateMemoryPools(memory_pool_info)) { + LOG_ERROR(Audio, "Failed to update memory pool parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - 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 (!info_updater.UpdateVoiceChannelResources(voice_context)) { + LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - if (is_refresh_pending) { - RefreshBuffer(memory, voice_resources); + if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { + LOG_ERROR(Audio, "Failed to update voice parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - 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; + // 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; } - 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 (behavior_info.IsSplitterSupported()) { + if (!info_updater.UpdateSplitterInfo(splitter_context)) { + LOG_ERROR(Audio, "Failed to update splitter parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } } - return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; -} + auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, + splitter_context, effect_context); -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 (mix_result.IsError()) { + LOG_ERROR(Audio, "Failed to update mix parameters"); + return mix_result; } - 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; + // TODO(ogniK): Sinks + if (!info_updater.UpdateSinks(sink_context)) { + LOG_ERROR(Audio, "Failed to update sink parameters"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - 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); - 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; + // TODO(ogniK): Performance buffer + if (!info_updater.UpdatePerformanceBuffer()) { + LOG_ERROR(Audio, "Failed to update performance buffer parameters"); + 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; + if (!info_updater.UpdateErrorInfo(behavior_info)) { + LOG_ERROR(Audio, "Failed to update error info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - 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); + if (behavior_info.IsElapsedFrameCountSupported()) { + if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { + LOG_ERROR(Audio, "Failed to update renderer info"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count); - break; } + // TODO(ogniK): Statistics - // 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); + if (!info_updater.WriteOutputHeader()) { + LOG_ERROR(Audio, "Failed to write output header"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } - is_refresh_pending = false; -} + // TODO(ogniK): Check when all sections are implemented -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"); - } + if (!info_updater.CheckConsumedSize()) { + LOG_ERROR(Audio, "Audio buffers were not consumed!"); + return AudioCommon::Audren::ERR_INVALID_PARAMETERS; } -} -static constexpr s16 ClampToS16(s32 value) { - return static_cast<s16>(std::clamp(value, -32768, 32767)); + ReleaseAndQueueBuffers(); + + 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]); + buffer[i * stream_channel_count + 0] = sample; + if (stream_channel_count > 1) { + buffer[i * stream_channel_count + 1] = sample; + } + if (stream_channel_count == 6) { + buffer[i * stream_channel_count + 2] = sample; + buffer[i * stream_channel_count + 4] = sample; + buffer[i * stream_channel_count + 5] = sample; + } + } 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] = l_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; + + buffer[i * stream_channel_count + 2] = + ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2); + + 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) { + buffer[i * stream_channel_count + 0] = fc_sample; + } else if (stream_channel_count == 2) { + buffer[i * stream_channel_count + 0] = + static_cast<s16>(0.3694f * static_cast<float>(fl_sample) + + 0.2612f * static_cast<float>(fc_sample) + + 0.3694f * static_cast<float>(bl_sample)); + buffer[i * stream_channel_count + 1] = + static_cast<s16>(0.3694f * static_cast<float>(fr_sample) + + 0.2612f * static_cast<float>(fc_sample) + + 0.3694f * static_cast<float>(br_sample)); + } else if (stream_channel_count == 6) { + 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() { diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index f0b691a86..2fd93e058 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -9,12 +9,18 @@ #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 { @@ -30,220 +36,25 @@ class Memory; } namespace AudioCore { +using DSPStateHolder = std::array<VoiceState*, 6>; 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, + AudioCommon::AudioRendererParameter params, std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number); ~AudioRenderer(); - ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params); + ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, + std::vector<u8>& output_params); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); u32 GetSampleRate() const; @@ -252,19 +63,23 @@ public: Stream::State GetStreamState() const; private: - class EffectState; - class VoiceState; BehaviorInfo behavior_info{}; - AudioRendererParameter worker_params; + AudioCommon::AudioRendererParameter worker_params; std::shared_ptr<Kernel::WritableEvent> buffer_event; + 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{}; + std::vector<s32> temp_mix_buffer{}; }; } // namespace AudioCore 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..512a4ebe3 100644 --- a/src/audio_core/behavior_info.h +++ b/src/audio_core/behavior_info.h @@ -14,53 +14,59 @@ namespace AudioCore { class BehaviorInfo { public: + struct ErrorInfo { + u32_le result{}; + INSERT_PADDING_WORDS(1); + u64_le result_info{}; + }; + static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); + + struct InParams { + u32_le revision{}; + u32_le padding{}; + u64_le flags{}; + }; + static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size"); + + struct OutParams { + std::array<ErrorInfo, 10> errors{}; + u32_le error_count{}; + INSERT_PADDING_BYTES(12); + }; + static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); + explicit BehaviorInfo(); ~BehaviorInfo(); - bool 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); + u32_le GetUserRevision() const; + u32_le GetProcessRevision() const; bool IsAdpcmLoopContextBugFixed() const; bool IsSplitterSupported() const; bool IsLongSizePreDelaySupported() const; - bool IsAudioRenererProcessingTimeLimit80PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit75PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; + bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; + bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; + bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; bool IsElapsedFrameCountSupported() const; bool IsMemoryPoolForceMappingEnabled() const; + bool IsFlushVoiceWaveBuffersSupported() const; + bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; + bool IsVoicePitchAndSrcSkippedSupported() const; + bool IsMixInParameterDirtyOnlyUpdateSupported() const; + bool IsSplitterBugFixed() const; + void CopyErrorInfo(OutParams& dst); private: u32_le process_revision{}; u32_le user_revision{}; u64_le flags{}; - - struct ErrorInfo { - u32_le result{}; - INSERT_PADDING_WORDS(1); - u64_le result_info{}; - }; - 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{}; - u64_le flags{}; - }; - static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size"); - - struct OutParams { - std::array<ErrorInfo, 10> errors{}; - u32_le error_count{}; - INSERT_PADDING_BYTES(12); - }; - static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); }; } // namespace AudioCore 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..bba40d13d --- /dev/null +++ b/src/audio_core/command_generator.cpp @@ -0,0 +1,978 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#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; + +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; + } +} + +} // 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, + s32 mix_buffer_count, 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 AudioCore::CommandGenerator::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) { + 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) { + if (!enabled) { + return; + } + const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams(); + const auto channel_count = params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + // TODO(ogniK): Actually implement reverb + 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::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::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, 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 -= 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..53e57748b --- /dev/null +++ b/src/audio_core/command_generator.h @@ -0,0 +1,102 @@ +// 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; +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(); + + s32* GetChannelMixBuffer(s32 channel); + const s32* GetChannelMixBuffer(s32 channel) const; + s32* GetMixBuffer(std::size_t index); + const s32* GetMixBuffer(std::size_t index) const; + std::size_t GetMixChannelBufferOffset(s32 channel) const; + + 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); + 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); + + // 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..7b4a1e9e8 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -3,18 +3,36 @@ // 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 = 6; +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; static constexpr u32 VersionFromRevision(u32_le rev) { // "REV7" -> 7 @@ -45,4 +63,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 41bf5cd4d..eb82791f6 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream { public: CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, + : 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; + 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) { @@ -78,7 +88,7 @@ public: const s16 surround_left{samples[i + 4]}; const s16 surround_right{samples[i + 5]}; // Not used in the ATSC reference implementation - [[maybe_unused]] const s16 low_frequency_effects { samples[i + 3] }; + [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; constexpr s32 clev{707}; // center mixing level coefficient constexpr s32 slev{707}; // surround mixing level coefficient @@ -182,8 +192,8 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, 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); + auto* impl = static_cast<CubebSinkStream*>(user_data); + auto* buffer = static_cast<u8*>(output_buffer); if (!impl) { return {}; @@ -193,6 +203,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 +218,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), diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp new file mode 100644 index 000000000..4d9cdf524 --- /dev/null +++ b/src/audio_core/effect_context.cpp @@ -0,0 +1,299 @@ +// 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::EffectBase(EffectType::Invalid) {} +EffectStubbed::~EffectStubbed() = default; + +void EffectStubbed::Update(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; +} + +EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {} +EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; + +void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { + auto& internal_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 = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channel_count)) { + internal_params.channel_count = internal_params.max_channels; + } + enabled = in_params.is_enabled; + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectI3dl2Reverb::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {} +EffectBiquadFilter::~EffectBiquadFilter() = default; + +void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { + auto& internal_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; + internal_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::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::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& internal_params = GetParams(); + if (!ValidChannelCountForEffect(delay_params->max_channels)) { + return; + } + + const auto last_status = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *delay_params; + if (!ValidChannelCountForEffect(delay_params->channels)) { + internal_params.channels = internal_params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_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::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::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& internal_params = GetParams(); + if (!ValidChannelCountForEffect(reverb_params->max_channels)) { + return; + } + + const auto last_status = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channels)) { + internal_params.channels = internal_params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_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..2c4ce53ef --- /dev/null +++ b/src/audio_core/effect_context.h @@ -0,0 +1,321 @@ +// 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 "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; + UsageState GetUsage() const; + EffectType GetType() const; + bool IsEnabled() const; + s32 GetMixID() const; + s32 GetProcessingOrder() const; + +protected: + UsageState usage{UsageState::Invalid}; + EffectType effect_type{}; + s32 mix_id{}; + s32 processing_order{}; + bool enabled = false; +}; + +template <typename T> +class EffectGeneric : public EffectBase { +public: + explicit EffectGeneric(EffectType effect_type) : EffectBase(effect_type) {} + + T& GetParams() { + return internal_params; + } + + const I3dl2ReverbParams& 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; +}; + +class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { +public: + explicit EffectI3dl2Reverb(); + ~EffectI3dl2Reverb() override; + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +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; + VAddr GetSendInfo() const; + VAddr GetSendBuffer() const; + VAddr GetRecvInfo() const; + 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(); + + std::size_t GetCount() const; + EffectBase* GetInfo(std::size_t i); + EffectBase* RetargetEffect(std::size_t i, EffectType effect); + 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..2940e53a9 --- /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, + std::vector<ServerMemoryPoolInfo>& memory_pool_info, + 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& in_params = voice_in[i]; + const auto channel_count = static_cast<std::size_t>(in_params.channel_count); + // Skip if it's not currently in use + if (!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>(in_params.id) < voice_count); + + // Grab our current voice info + auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(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(in_params.voice_channel_resource_ids[channel]); + } + + if (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(in_params, behavior_info); + // TODO(ogniK): Handle mapping errors with behavior info based on in params response + + // Update our wave buffers + voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info); + voice_info.WriteOutStatus(voice_out[i], 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(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..06f9d770f --- /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..5a3453063 --- /dev/null +++ b/src/audio_core/memory_pool.cpp @@ -0,0 +1,62 @@ + +// 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 ServerMemoryPoolInfo::InParams& in_params, + ServerMemoryPoolInfo::OutParams& out_params) { + // Our state does not need to be changed + if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach && + in_params.state != ServerMemoryPoolInfo::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 == ServerMemoryPoolInfo::State::RequestAttach) { + cpu_address = in_params.address; + size = in_params.size; + used = true; + out_params.state = ServerMemoryPoolInfo::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 = ServerMemoryPoolInfo::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..8ac503f1c --- /dev/null +++ b/src/audio_core/memory_pool.h @@ -0,0 +1,53 @@ +// 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{}; + ServerMemoryPoolInfo::State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size"); + + struct OutParams { + ServerMemoryPoolInfo::State state{}; + INSERT_PADDING_WORDS(3); + }; + static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size"); + + bool Update(const ServerMemoryPoolInfo::InParams& in_params, + ServerMemoryPoolInfo::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..6a588eeb4 --- /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(); + + const ServerMixInfo::InParams& GetInParams() const; + ServerMixInfo::InParams& GetInParams(); + + bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, + BehaviorInfo& behavior_info, SplitterContext& splitter_context, + EffectContext& effect_context); + bool HasAnyConnection() const; + void Cleanup(); + void SetEffectCount(std::size_t count); + void ResetEffectProcessingOrder(); + 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); + + std::size_t GetCount() const; + ServerMixInfo& GetInfo(std::size_t i); + const ServerMixInfo& GetInfo(std::size_t i) const; + ServerMixInfo& GetSortedInfo(std::size_t i); + const ServerMixInfo& GetSortedInfo(std::size_t i) const; + ServerMixInfo& GetFinalMixInfo(); + const ServerMixInfo& GetFinalMixInfo() const; + EdgeMatrix& GetEdgeMatrix(); + 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..0882b411a --- /dev/null +++ b/src/audio_core/sink_context.cpp @@ -0,0 +1,31 @@ +// 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(SinkInfo::InParams& in) { + in_use = in.in_use; + use_count = in.device.input_count; + std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT); +} + +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; +} + +} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h new file mode 100644 index 000000000..d7aa72ba7 --- /dev/null +++ b/src/audio_core/sink_context.h @@ -0,0 +1,89 @@ +// 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 { + +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_UNION_PADDING_BYTES(5); + }; + static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28, + "SinkInfo::CircularBufferIn is in invalid size"); + + struct DeviceIn { + std::array<u8, 255> device_name; + INSERT_UNION_PADDING_BYTES(1); + s32_le input_count; + std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input; + INSERT_UNION_PADDING_BYTES(1); + bool down_matrix_enabled; + std::array<float_le, 4> down_matrix_coef; + }; + static_assert(sizeof(SinkInfo::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{}; + SinkInfo::DeviceIn device; + SinkInfo::CircularBufferIn circular_buffer; + }; + }; + static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); +}; + +class SinkContext { +public: + explicit SinkContext(std::size_t sink_count); + ~SinkContext(); + + std::size_t GetCount() const; + + void UpdateMainSink(SinkInfo::InParams& in); + bool InUse() const; + std::vector<u8> OutputBuffers() const; + +private: + bool in_use{false}; + s32 use_count{}; + std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{}; + std::size_t sink_count{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp new file mode 100644 index 000000000..f21b53147 --- /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); + 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 node_count = edge_matrix.GetNodeCount(); + for (s32 j = 0; j < static_cast<s32>(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..ea6239fdb --- /dev/null +++ b/src/audio_core/splitter_context.h @@ -0,0 +1,221 @@ +// 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(NodeStates::State state, std::size_t i); + NodeStates::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{}; + NodeStates::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(SplitterInfo::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(SplitterInfo::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(SplitterInfo::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 aab3e979a..4bbb1e0c4 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 { @@ -36,9 +35,10 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo 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](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(cycles_late); }); + release_event = + Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) { + ReleaseActiveBuffer(ns_late); + }); } void Stream::Play() { @@ -59,11 +59,9 @@ Stream::State Stream::GetState() const { return state; } -s64 Stream::GetBufferReleaseNS(const Buffer& buffer) const { +std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const { const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; - const auto ns = - std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate); - return ns.count(); + return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate); } static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { @@ -80,7 +78,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { } } -void Stream::PlayNextBuffer(s64 cycles_late) { +void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) { if (!IsPlaying()) { // Ensure we are in playing state before playing the next buffer sink_stream.Flush(); @@ -105,17 +103,14 @@ void Stream::PlayNextBuffer(s64 cycles_late) { sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); - core_timing.ScheduleEvent( - GetBufferReleaseNS(*active_buffer) - - (Settings::values.enable_audio_stretching.GetValue() ? 0 : cycles_late), - release_event, {}); + core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {}); } -void Stream::ReleaseActiveBuffer(s64 cycles_late) { +void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { ASSERT(active_buffer); released_buffers.push(std::move(active_buffer)); release_callback(); - PlayNextBuffer(cycles_late); + PlayNextBuffer(ns_late); } bool Stream::QueueBuffer(BufferPtr&& buffer) { diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 524376257..6437b8591 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -4,6 +4,7 @@ #pragma once +#include <chrono> #include <functional> #include <memory> #include <string> @@ -90,16 +91,13 @@ public: private: /// Plays the next queued buffer in the audio stream, starting playback if necessary - void PlayNextBuffer(s64 cycles_late = 0); + void PlayNextBuffer(std::chrono::nanoseconds ns_late = {}); /// Releases the actively playing buffer, signalling that it has been completed - void ReleaseActiveBuffer(s64 cycles_late = 0); + void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); /// Gets the number of core cycles when the specified buffer will be released - s64 GetBufferReleaseNS(const Buffer& buffer) const; - - /// Gets the number of core cycles when the specified buffer will be released - s64 GetBufferReleaseNSHostTiming(const Buffer& buffer) const; + 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..863ac9267 --- /dev/null +++ b/src/audio_core/voice_context.cpp @@ -0,0 +1,526 @@ +// 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()) { + in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_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, 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..59d3d7dfb --- /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(VoiceChannelResource::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(VoiceInfo::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(VoiceInfo::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: + 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 d120c8d3d..0fb5d9708 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -110,6 +110,7 @@ add_library(common STATIC common_funcs.h common_paths.h common_types.h + concepts.h dynamic_library.cpp dynamic_library.h fiber.cpp @@ -171,7 +172,6 @@ add_library(common STATIC virtual_buffer.h wall_clock.cpp wall_clock.h - web_result.h zstd_compression.cpp zstd_compression.h ) @@ -192,4 +192,9 @@ 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/algorithm.h b/src/common/algorithm.h index e21b1373c..4804a3421 100644 --- a/src/common/algorithm.h +++ b/src/common/algorithm.h @@ -15,7 +15,8 @@ namespace Common { template <class ForwardIt, class T, class Compare = std::less<>> -ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, Compare comp = {}) { +[[nodiscard]] ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, + Compare comp = {}) { // Note: BOTH type T and the type after ForwardIt is dereferenced // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. // This is stricter than lower_bound requirement (see above) diff --git a/src/common/alignment.h b/src/common/alignment.h index b37044bb6..5040043de 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -3,13 +3,13 @@ #pragma once #include <cstddef> -#include <memory> +#include <new> #include <type_traits> namespace Common { template <typename T> -constexpr T AlignUp(T value, std::size_t size) { +[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); auto mod{static_cast<T>(value % size)}; value -= mod; @@ -17,31 +17,31 @@ constexpr T AlignUp(T value, std::size_t size) { } template <typename T> -constexpr T AlignDown(T value, std::size_t size) { +[[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); } template <typename T> -constexpr T AlignBits(T value, std::size_t align) { +[[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); } template <typename T> -constexpr bool Is4KBAligned(T value) { +[[nodiscard]] constexpr bool Is4KBAligned(T value) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return (value & 0xFFF) == 0; } template <typename T> -constexpr bool IsWordAligned(T value) { +[[nodiscard]] constexpr bool IsWordAligned(T value) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return (value & 0b11) == 0; } template <typename T> -constexpr bool IsAligned(T value, std::size_t alignment) { +[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) { using U = typename std::make_unsigned<T>::type; const U mask = static_cast<U>(alignment - 1); return (value & mask) == 0; @@ -54,66 +54,28 @@ public: using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using pointer = T*; - using const_pointer = const T*; - - using reference = T&; - using const_reference = const T&; - using propagate_on_container_copy_assignment = std::true_type; using propagate_on_container_move_assignment = std::true_type; using propagate_on_container_swap = std::true_type; using is_always_equal = std::true_type; -public: constexpr AlignmentAllocator() noexcept = default; template <typename T2> constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {} - pointer address(reference r) noexcept { - return std::addressof(r); - } - - const_pointer address(const_reference r) const noexcept { - return std::addressof(r); - } - - pointer allocate(size_type n) { - return static_cast<pointer>(::operator new (n, std::align_val_t{Align})); - } - - void deallocate(pointer p, size_type) { - ::operator delete (p, std::align_val_t{Align}); + [[nodiscard]] T* allocate(size_type n) { + return static_cast<T*>(::operator new (n * sizeof(T), std::align_val_t{Align})); } - void construct(pointer p, const value_type& wert) { - new (p) value_type(wert); - } - - void destroy(pointer p) { - p->~value_type(); - } - - size_type max_size() const noexcept { - return size_type(-1) / sizeof(value_type); + void deallocate(T* p, size_type n) { + ::operator delete (p, n * sizeof(T), std::align_val_t{Align}); } template <typename T2> struct rebind { using other = AlignmentAllocator<T2, Align>; }; - - bool operator!=(const AlignmentAllocator<T, Align>& other) const noexcept { - return !(*this == other); - } - - // Returns true if and only if storage allocated from *this - // can be deallocated from other, and vice versa. - // Always returns true for stateless allocators. - bool operator==(const AlignmentAllocator<T, Align>& other) const noexcept { - return true; - } }; } // namespace Common diff --git a/src/common/assert.h b/src/common/assert.h index 5b67c5c52..06d7b5612 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -17,11 +17,12 @@ // enough for our purposes. template <typename Fn> #if defined(_MSC_VER) -__declspec(noinline, noreturn) +[[msvc::noinline, noreturn]] #elif defined(__GNUC__) - __attribute__((noinline, noreturn, cold)) +[[gnu::cold, gnu::noinline, noreturn]] #endif - static void assert_noinline_call(const Fn& fn) { +static void +assert_noinline_call(const Fn& fn) { fn(); Crash(); exit(1); // Keeps GCC's mouth shut about this actually returning diff --git a/src/common/atomic_ops.cpp b/src/common/atomic_ops.cpp index 1098e21ff..1612d0e67 100644 --- a/src/common/atomic_ops.cpp +++ b/src/common/atomic_ops.cpp @@ -14,50 +14,55 @@ namespace Common { #if _MSC_VER -bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) { - u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected); +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(u16 volatile* pointer, u16 value, u16 expected) { - u16 result = _InterlockedCompareExchange16((short*)pointer, value, 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(u32 volatile* pointer, u32 value, u32 expected) { - u32 result = _InterlockedCompareExchange((long*)pointer, value, 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(u64 volatile* pointer, u64 value, u64 expected) { - u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, 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(u64 volatile* pointer, u128 value, u128 expected) { - return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0], - (__int64*)expected.data()) != 0; +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(u8 volatile* pointer, u8 value, u8 expected) { +bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { return __sync_bool_compare_and_swap(pointer, expected, value); } -bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) { +bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { return __sync_bool_compare_and_swap(pointer, expected, value); } -bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) { +bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { return __sync_bool_compare_and_swap(pointer, expected, value); } -bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) { +bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { return __sync_bool_compare_and_swap(pointer, expected, value); } -bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) { +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)); diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h index e6181d521..b46888589 100644 --- a/src/common/atomic_ops.h +++ b/src/common/atomic_ops.h @@ -8,10 +8,10 @@ namespace Common { -bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected); -bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected); -bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected); -bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected); -bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected); +[[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); } // namespace Common diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 26ae6c7fc..0f0661172 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -36,13 +36,6 @@ #include "common/common_funcs.h" #include "common/swap.h" -// Inlining -#ifdef _WIN32 -#define FORCE_INLINE __forceinline -#else -#define FORCE_INLINE inline __attribute__((always_inline)) -#endif - /* * Abstract bitfield class * @@ -142,8 +135,8 @@ public: * containing several bitfields can be assembled by formatting each of their values and ORing * the results together. */ - static constexpr FORCE_INLINE StorageType FormatValue(const T& value) { - return ((StorageType)value << position) & mask; + [[nodiscard]] static constexpr StorageType FormatValue(const T& value) { + return (static_cast<StorageType>(value) << position) & mask; } /** @@ -151,7 +144,7 @@ public: * (such as Value() or operator T), but this can be used to extract a value from a bitfield * union in a constexpr context. */ - static constexpr FORCE_INLINE T ExtractValue(const StorageType& storage) { + [[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) { if constexpr (std::numeric_limits<UnderlyingType>::is_signed) { std::size_t shift = 8 * sizeof(T) - bits; return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >> @@ -175,7 +168,7 @@ public: constexpr BitField(BitField&&) noexcept = default; constexpr BitField& operator=(BitField&&) noexcept = default; - constexpr operator T() const { + [[nodiscard]] constexpr operator T() const { return Value(); } @@ -183,11 +176,11 @@ public: storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value)); } - constexpr T Value() const { + [[nodiscard]] constexpr T Value() const { return ExtractValue(storage); } - constexpr explicit operator bool() const { + [[nodiscard]] constexpr explicit operator bool() const { return Value() != 0; } diff --git a/src/common/bit_util.h b/src/common/bit_util.h index 6f7d5a947..29f59a9a3 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -17,12 +17,12 @@ namespace Common { /// Gets the size of a specified type T in bits. template <typename T> -constexpr std::size_t BitSize() { +[[nodiscard]] constexpr std::size_t BitSize() { return sizeof(T) * CHAR_BIT; } #ifdef _MSC_VER -inline u32 CountLeadingZeroes32(u32 value) { +[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) { unsigned long leading_zero = 0; if (_BitScanReverse(&leading_zero, value) != 0) { @@ -32,7 +32,7 @@ inline u32 CountLeadingZeroes32(u32 value) { return 32; } -inline u32 CountLeadingZeroes64(u64 value) { +[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) { unsigned long leading_zero = 0; if (_BitScanReverse64(&leading_zero, value) != 0) { @@ -42,7 +42,7 @@ inline u32 CountLeadingZeroes64(u64 value) { return 64; } #else -inline u32 CountLeadingZeroes32(u32 value) { +[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) { if (value == 0) { return 32; } @@ -50,7 +50,7 @@ inline u32 CountLeadingZeroes32(u32 value) { return static_cast<u32>(__builtin_clz(value)); } -inline u32 CountLeadingZeroes64(u64 value) { +[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) { if (value == 0) { return 64; } @@ -60,7 +60,7 @@ inline u32 CountLeadingZeroes64(u64 value) { #endif #ifdef _MSC_VER -inline u32 CountTrailingZeroes32(u32 value) { +[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) { unsigned long trailing_zero = 0; if (_BitScanForward(&trailing_zero, value) != 0) { @@ -70,7 +70,7 @@ inline u32 CountTrailingZeroes32(u32 value) { return 32; } -inline u32 CountTrailingZeroes64(u64 value) { +[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) { unsigned long trailing_zero = 0; if (_BitScanForward64(&trailing_zero, value) != 0) { @@ -80,7 +80,7 @@ inline u32 CountTrailingZeroes64(u64 value) { return 64; } #else -inline u32 CountTrailingZeroes32(u32 value) { +[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) { if (value == 0) { return 32; } @@ -88,7 +88,7 @@ inline u32 CountTrailingZeroes32(u32 value) { return static_cast<u32>(__builtin_ctz(value)); } -inline u32 CountTrailingZeroes64(u64 value) { +[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) { if (value == 0) { return 64; } @@ -99,13 +99,13 @@ inline u32 CountTrailingZeroes64(u64 value) { #ifdef _MSC_VER -inline u32 MostSignificantBit32(const u32 value) { +[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) { unsigned long result; _BitScanReverse(&result, value); return static_cast<u32>(result); } -inline u32 MostSignificantBit64(const u64 value) { +[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) { unsigned long result; _BitScanReverse64(&result, value); return static_cast<u32>(result); @@ -113,30 +113,30 @@ inline u32 MostSignificantBit64(const u64 value) { #else -inline u32 MostSignificantBit32(const u32 value) { +[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) { return 31U - static_cast<u32>(__builtin_clz(value)); } -inline u32 MostSignificantBit64(const u64 value) { +[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) { return 63U - static_cast<u32>(__builtin_clzll(value)); } #endif -inline u32 Log2Floor32(const u32 value) { +[[nodiscard]] inline u32 Log2Floor32(const u32 value) { return MostSignificantBit32(value); } -inline u32 Log2Ceil32(const u32 value) { +[[nodiscard]] inline u32 Log2Ceil32(const u32 value) { const u32 log2_f = Log2Floor32(value); return log2_f + ((value ^ (1U << log2_f)) != 0U); } -inline u32 Log2Floor64(const u64 value) { +[[nodiscard]] inline u32 Log2Floor64(const u64 value) { return MostSignificantBit64(value); } -inline u32 Log2Ceil64(const u64 value) { +[[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)); } diff --git a/src/common/cityhash.h b/src/common/cityhash.h index 4b94f8e18..a00804e01 100644 --- a/src/common/cityhash.h +++ b/src/common/cityhash.h @@ -61,42 +61,43 @@ #pragma once +#include <cstddef> +#include <cstdint> #include <utility> -#include <stdint.h> -#include <stdlib.h> // for std::size_t. namespace Common { -typedef std::pair<uint64_t, uint64_t> uint128; +using uint128 = std::pair<uint64_t, uint64_t>; -inline uint64_t Uint128Low64(const uint128& x) { +[[nodiscard]] inline uint64_t Uint128Low64(const uint128& x) { return x.first; } -inline uint64_t Uint128High64(const uint128& x) { +[[nodiscard]] inline uint64_t Uint128High64(const uint128& x) { return x.second; } // Hash function for a byte array. -uint64_t CityHash64(const char* buf, std::size_t len); +[[nodiscard]] uint64_t CityHash64(const char* buf, std::size_t len); // Hash function for a byte array. For convenience, a 64-bit seed is also // hashed into the result. -uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed); +[[nodiscard]] uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed); // Hash function for a byte array. For convenience, two seeds are also // hashed into the result. -uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, uint64_t seed1); +[[nodiscard]] uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, + uint64_t seed1); // Hash function for a byte array. -uint128 CityHash128(const char* s, std::size_t len); +[[nodiscard]] uint128 CityHash128(const char* s, std::size_t len); // Hash function for a byte array. For convenience, a 128-bit seed is also // hashed into the result. -uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed); +[[nodiscard]] uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed); // Hash 128 input bits down to 64 bits of output. // This is intended to be a reasonably good hash function. -inline uint64_t Hash128to64(const uint128& x) { +[[nodiscard]] inline uint64_t Hash128to64(const uint128& x) { // Murmur-inspired hashing. const uint64_t kMul = 0x9ddfea08eb382d69ULL; uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; diff --git a/src/common/color.h b/src/common/color.h index 3a2222077..bbcac858e 100644 --- a/src/common/color.h +++ b/src/common/color.h @@ -10,45 +10,45 @@ #include "common/swap.h" #include "common/vector_math.h" -namespace Color { +namespace Common::Color { /// Convert a 1-bit color component to 8 bit -constexpr u8 Convert1To8(u8 value) { +[[nodiscard]] constexpr u8 Convert1To8(u8 value) { return value * 255; } /// Convert a 4-bit color component to 8 bit -constexpr u8 Convert4To8(u8 value) { +[[nodiscard]] constexpr u8 Convert4To8(u8 value) { return (value << 4) | value; } /// Convert a 5-bit color component to 8 bit -constexpr u8 Convert5To8(u8 value) { +[[nodiscard]] constexpr u8 Convert5To8(u8 value) { return (value << 3) | (value >> 2); } /// Convert a 6-bit color component to 8 bit -constexpr u8 Convert6To8(u8 value) { +[[nodiscard]] constexpr u8 Convert6To8(u8 value) { return (value << 2) | (value >> 4); } /// Convert a 8-bit color component to 1 bit -constexpr u8 Convert8To1(u8 value) { +[[nodiscard]] constexpr u8 Convert8To1(u8 value) { return value >> 7; } /// Convert a 8-bit color component to 4 bit -constexpr u8 Convert8To4(u8 value) { +[[nodiscard]] constexpr u8 Convert8To4(u8 value) { return value >> 4; } /// Convert a 8-bit color component to 5 bit -constexpr u8 Convert8To5(u8 value) { +[[nodiscard]] constexpr u8 Convert8To5(u8 value) { return value >> 3; } /// Convert a 8-bit color component to 6 bit -constexpr u8 Convert8To6(u8 value) { +[[nodiscard]] constexpr u8 Convert8To6(u8 value) { return value >> 2; } @@ -57,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) { +[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) { return {bytes[3], bytes[2], bytes[1], bytes[0]}; } @@ -66,7 +66,7 @@ inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) { +[[nodiscard]] inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) { return {bytes[2], bytes[1], bytes[0], 255}; } @@ -75,7 +75,7 @@ inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRG8(const u8* bytes) { +[[nodiscard]] inline Common::Vec4<u8> DecodeRG8(const u8* bytes) { return {bytes[1], bytes[0], 0, 255}; } @@ -84,7 +84,7 @@ inline Common::Vec4<u8> DecodeRG8(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) { +[[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), @@ -96,7 +96,7 @@ inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) { +[[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), @@ -108,7 +108,7 @@ inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) { * @param bytes Pointer to encoded source color * @return Result color decoded as Common::Vec4<u8> */ -inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) { +[[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), @@ -120,7 +120,7 @@ inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) { * @param bytes Pointer to encoded source value * @return Depth value as an u32 */ -inline u32 DecodeD16(const u8* bytes) { +[[nodiscard]] inline u32 DecodeD16(const u8* bytes) { u16_le data; std::memcpy(&data, bytes, sizeof(data)); return data; @@ -131,7 +131,7 @@ inline u32 DecodeD16(const u8* bytes) { * @param bytes Pointer to encoded source value * @return Depth value as an u32 */ -inline u32 DecodeD24(const u8* bytes) { +[[nodiscard]] inline u32 DecodeD24(const u8* bytes) { return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; } @@ -140,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) { * @param bytes Pointer to encoded source values * @return Resulting values stored as a Common::Vec2 */ -inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) { +[[nodiscard]] inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) { return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]}; } @@ -268,4 +268,4 @@ inline void EncodeX24S8(u8 stencil, u8* bytes) { bytes[3] = stencil; } -} // namespace Color +} // namespace Common::Color diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 88cf5250a..367b6bf6e 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -53,43 +53,49 @@ __declspec(dllimport) void __stdcall DebugBreak(void); // Call directly after the command or use the error num. // This function might change the error code. // Defined in Misc.cpp. -std::string GetLastErrorMsg(); +[[nodiscard]] std::string GetLastErrorMsg(); #define DECLARE_ENUM_FLAG_OPERATORS(type) \ - constexpr type operator|(type a, type b) noexcept { \ + [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ 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>; \ 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; \ } \ - constexpr type operator~(type key) noexcept { \ + [[nodiscard]] constexpr type operator~(type key) noexcept { \ using T = std::underlying_type_t<type>; \ return static_cast<type>(~static_cast<T>(key)); \ } \ - constexpr bool True(type key) noexcept { \ + [[nodiscard]] constexpr bool True(type key) noexcept { \ using T = std::underlying_type_t<type>; \ return static_cast<T>(key) != 0; \ } \ - constexpr bool False(type key) noexcept { \ + [[nodiscard]] constexpr bool False(type key) noexcept { \ using T = std::underlying_type_t<type>; \ return static_cast<T>(key) == 0; \ } namespace Common { -constexpr u32 MakeMagic(char a, char b, char c, char d) { +[[nodiscard]] constexpr u32 MakeMagic(char a, char b, char c, char d) { return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24; } diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 076752d3b..3c593d5f6 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -35,6 +35,7 @@ #define KEYS_DIR "keys" #define LOAD_DIR "load" #define DUMP_DIR "dump" +#define SCREENSHOTS_DIR "screenshots" #define SHADER_DIR "shader" #define LOG_DIR "log" diff --git a/src/common/concepts.h b/src/common/concepts.h new file mode 100644 index 000000000..5bef3ad67 --- /dev/null +++ b/src/common/concepts.h @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> + +namespace Common { + +// Check if type is like an STL container +template <typename T> +concept IsSTLContainer = requires(T t) { + typename T::value_type; + typename T::iterator; + typename T::const_iterator; + // TODO(ogniK): Replace below is std::same_as<void> when MSVC supports it. + t.begin(); + t.end(); + t.cbegin(); + t.cend(); + t.data(); + t.size(); +}; + +// TODO: Replace with std::derived_from when the <concepts> header +// is available on all supported platforms. +template <typename Derived, typename Base> +concept DerivedFrom = requires { + std::is_base_of_v<Base, Derived>; + std::is_convertible_v<const volatile Derived*, const volatile Base*>; +}; + +} // namespace Common diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp index f268d6021..f2b4939df 100644 --- a/src/common/detached_tasks.cpp +++ b/src/common/detached_tasks.cpp @@ -34,8 +34,7 @@ void DetachedTasks::AddTask(std::function<void()> task) { std::unique_lock lock{instance->mutex}; --instance->count; std::notify_all_at_thread_exit(instance->cv, std::move(lock)); - }) - .detach(); + }).detach(); } } // namespace Common diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp index 7ab54e9e4..7f0a10521 100644 --- a/src/common/dynamic_library.cpp +++ b/src/common/dynamic_library.cpp @@ -21,7 +21,7 @@ namespace Common { DynamicLibrary::DynamicLibrary() = default; DynamicLibrary::DynamicLibrary(const char* filename) { - Open(filename); + void(Open(filename)); } DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h index 2a06372fd..3512da940 100644 --- a/src/common/dynamic_library.h +++ b/src/common/dynamic_library.h @@ -33,7 +33,7 @@ public: ~DynamicLibrary(); /// Returns the specified library name with the platform-specific suffix added. - static std::string GetUnprefixedFilename(const char* filename); + [[nodiscard]] static std::string GetUnprefixedFilename(const char* filename); /// Returns the specified library name in platform-specific format. /// Major/minor versions will not be included if set to -1. @@ -41,28 +41,29 @@ public: /// Windows: LIBNAME-MAJOR-MINOR.dll /// Linux: libLIBNAME.so.MAJOR.MINOR /// Mac: libLIBNAME.MAJOR.MINOR.dylib - static std::string GetVersionedFilename(const char* libname, int major = -1, int minor = -1); + [[nodiscard]] static std::string GetVersionedFilename(const char* libname, int major = -1, + int minor = -1); /// Returns true if a module is loaded, otherwise false. - bool IsOpen() const { + [[nodiscard]] bool IsOpen() const { return handle != nullptr; } /// Loads (or replaces) the handle with the specified library file name. /// Returns true if the library was loaded and can be used. - bool Open(const char* filename); + [[nodiscard]] bool Open(const char* filename); /// Unloads the library, any function pointers from this library are no longer valid. void Close(); /// Returns the address of the specified symbol (function or variable) as an untyped pointer. /// If the specified symbol does not exist in this library, nullptr is returned. - void* GetSymbolAddress(const char* name) const; + [[nodiscard]] void* GetSymbolAddress(const char* name) const; /// Obtains the address of the specified symbol, automatically casting to the correct type. /// Returns true if the symbol was found and assigned, otherwise false. template <typename T> - bool GetSymbol(const char* name, T* ptr) const { + [[nodiscard]] bool GetSymbol(const char* name, T* ptr) const { *ptr = reinterpret_cast<T>(GetSymbolAddress(name)); return *ptr != nullptr; } diff --git a/src/common/fiber.h b/src/common/fiber.h index dafc1100e..89dde5e36 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -47,7 +47,7 @@ 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 std::shared_ptr<Fiber> ThreadToFiber(); + [[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber(); void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter); diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 45b750e1e..16c3713e0 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -74,7 +74,7 @@ // This namespace has various generic functions related to files and paths. // The code still needs a ton of cleanup. // REMEMBER: strdup considered harmful! -namespace FileUtil { +namespace Common::FS { // Remove any ending forward slashes from directory paths // Modifies argument. @@ -196,7 +196,7 @@ bool CreateFullPath(const std::string& fullPath) { int panicCounter = 100; LOG_TRACE(Common_Filesystem, "path {}", fullPath); - if (FileUtil::Exists(fullPath)) { + if (Exists(fullPath)) { LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath); return true; } @@ -212,7 +212,7 @@ bool CreateFullPath(const std::string& fullPath) { // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") std::string const subPath(fullPath.substr(0, position + 1)); - if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) { + if (!IsDirectory(subPath) && !CreateDir(subPath)) { LOG_ERROR(Common, "CreateFullPath: directory creation failed"); return false; } @@ -231,7 +231,7 @@ bool DeleteDir(const std::string& filename) { LOG_TRACE(Common_Filesystem, "directory {}", filename); // check if a directory - if (!FileUtil::IsDirectory(filename)) { + if (!IsDirectory(filename)) { LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); return false; } @@ -371,7 +371,7 @@ u64 GetSize(FILE* f) { bool CreateEmptyFile(const std::string& filename) { LOG_TRACE(Common_Filesystem, "{}", filename); - if (!FileUtil::IOFile(filename, "wb").IsOpen()) { + if (!IOFile(filename, "wb").IsOpen()) { LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); return false; } @@ -488,29 +488,34 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) return false; // Delete the outermost directory - FileUtil::DeleteDir(directory); + DeleteDir(directory); return true; } void CopyDir(const std::string& source_path, const std::string& dest_path) { #ifndef _WIN32 - if (source_path == dest_path) + if (source_path == dest_path) { return; - if (!FileUtil::Exists(source_path)) + } + if (!Exists(source_path)) { return; - if (!FileUtil::Exists(dest_path)) - FileUtil::CreateFullPath(dest_path); + } + if (!Exists(dest_path)) { + CreateFullPath(dest_path); + } DIR* dirp = opendir(source_path.c_str()); - if (!dirp) + if (!dirp) { return; + } while (struct dirent* result = readdir(dirp)) { const std::string virtualName(result->d_name); // check for "." and ".." if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || - ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) + ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) { continue; + } std::string source, dest; source = source_path + virtualName; @@ -518,11 +523,13 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) { if (IsDirectory(source)) { source += '/'; dest += '/'; - if (!FileUtil::Exists(dest)) - FileUtil::CreateFullPath(dest); + if (!Exists(dest)) { + CreateFullPath(dest); + } CopyDir(source, dest); - } else if (!FileUtil::Exists(dest)) - FileUtil::Copy(source, dest); + } else if (!Exists(dest)) { + Copy(source, dest); + } } closedir(dirp); #endif @@ -538,7 +545,7 @@ std::optional<std::string> GetCurrentDir() { if (!dir) { #endif LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); - return {}; + return std::nullopt; } #ifdef _WIN32 std::string strDir = Common::UTF16ToUTF8(dir); @@ -546,7 +553,7 @@ std::optional<std::string> GetCurrentDir() { std::string strDir = dir; #endif free(dir); - return strDir; + return std::move(strDir); } bool SetCurrentDir(const std::string& directory) { @@ -668,7 +675,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { if (user_path.empty()) { #ifdef _WIN32 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; - if (!FileUtil::IsDirectory(user_path)) { + if (!IsDirectory(user_path)) { user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; } else { LOG_INFO(Common_Filesystem, "Using the local user directory"); @@ -677,7 +684,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); #else - if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { + if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); @@ -695,6 +702,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); + paths.emplace(UserPath::ScreenshotsDir, user_path + SCREENSHOTS_DIR DIR_SEP); paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP); paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); @@ -703,7 +711,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { } if (!new_path.empty()) { - if (!FileUtil::IsDirectory(new_path)) { + if (!IsDirectory(new_path)) { LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path); return paths[path]; } else { @@ -901,10 +909,10 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se return std::string(RemoveTrailingSlash(path)); } -IOFile::IOFile() {} +IOFile::IOFile() = default; IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { - Open(filename, openmode, flags); + void(Open(filename, openmode, flags)); } IOFile::~IOFile() { @@ -945,17 +953,18 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags) } bool IOFile::Close() { - if (!IsOpen() || 0 != std::fclose(m_file)) + if (!IsOpen() || 0 != std::fclose(m_file)) { return false; + } m_file = nullptr; return true; } u64 IOFile::GetSize() const { - if (IsOpen()) - return FileUtil::GetSize(m_file); - + if (IsOpen()) { + return FS::GetSize(m_file); + } return 0; } @@ -964,9 +973,9 @@ bool IOFile::Seek(s64 off, int origin) const { } u64 IOFile::Tell() const { - if (IsOpen()) + if (IsOpen()) { return ftello(m_file); - + } return std::numeric_limits<u64>::max(); } @@ -1015,4 +1024,4 @@ bool IOFile::Resize(u64 size) { ; } -} // namespace FileUtil +} // namespace Common::FS diff --git a/src/common/file_util.h b/src/common/file_util.h index f7a0c33fa..8b587320f 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -19,7 +19,7 @@ #include "common/string_util.h" #endif -namespace FileUtil { +namespace Common::FS { // User paths for GetUserPath enum class UserPath { @@ -32,6 +32,7 @@ enum class UserPath { SDMCDir, LoadDir, DumpDir, + ScreenshotsDir, ShaderDir, SysDataDir, UserDir, @@ -47,19 +48,19 @@ struct FSTEntry { }; // Returns true if file filename exists -bool Exists(const std::string& filename); +[[nodiscard]] bool Exists(const std::string& filename); // Returns true if filename is a directory -bool IsDirectory(const std::string& filename); +[[nodiscard]] bool IsDirectory(const std::string& filename); // Returns the size of filename (64bit) -u64 GetSize(const std::string& filename); +[[nodiscard]] u64 GetSize(const std::string& filename); // Overloaded GetSize, accepts file descriptor -u64 GetSize(const int fd); +[[nodiscard]] u64 GetSize(int fd); // Overloaded GetSize, accepts FILE* -u64 GetSize(FILE* f); +[[nodiscard]] u64 GetSize(FILE* f); // Returns true if successful, or path already exists. bool CreateDir(const std::string& filename); @@ -119,7 +120,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); // Returns the current directory -std::optional<std::string> GetCurrentDir(); +[[nodiscard]] std::optional<std::string> GetCurrentDir(); // Create directory and copy contents (does not overwrite existing files) void CopyDir(const std::string& source_path, const std::string& dest_path); @@ -131,20 +132,20 @@ bool SetCurrentDir(const std::string& directory); // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); -std::string GetHactoolConfigurationPath(); +[[nodiscard]] std::string GetHactoolConfigurationPath(); -std::string GetNANDRegistrationDir(bool system = false); +[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false); // Returns the path to where the sys file are -std::string GetSysDirectory(); +[[nodiscard]] std::string GetSysDirectory(); #ifdef __APPLE__ -std::string GetBundleDirectory(); +[[nodiscard]] std::string GetBundleDirectory(); #endif #ifdef _WIN32 -const std::string& GetExeDirectory(); -std::string AppDataRoamingDirectory(); +[[nodiscard]] const std::string& GetExeDirectory(); +[[nodiscard]] std::string AppDataRoamingDirectory(); #endif std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str); @@ -163,38 +164,55 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam // Splits the path on '/' or '\' and put the components into a vector // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } -std::vector<std::string> SplitPathComponents(std::string_view filename); +[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); // Gets all of the text up to the last '/' or '\' in the path. -std::string_view GetParentPath(std::string_view path); +[[nodiscard]] std::string_view GetParentPath(std::string_view path); // Gets all of the text after the first '/' or '\' in the path. -std::string_view GetPathWithoutTop(std::string_view path); +[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); // Gets the filename of the path -std::string_view GetFilename(std::string_view path); +[[nodiscard]] std::string_view GetFilename(std::string_view path); // Gets the extension of the filename -std::string_view GetExtensionFromFilename(std::string_view name); +[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); // Removes the final '/' or '\' if one exists -std::string_view RemoveTrailingSlash(std::string_view path); +[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); // Creates a new vector containing indices [first, last) from the original. template <typename T> -std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, std::size_t last) { - if (first >= last) +[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, + std::size_t last) { + if (first >= last) { return {}; + } last = std::min<std::size_t>(last, vector.size()); return std::vector<T>(vector.begin() + first, vector.begin() + first + last); } -enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault }; +enum class DirectorySeparator { + ForwardSlash, + BackwardSlash, + PlatformDefault, +}; // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows -std::string SanitizePath(std::string_view path, - DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); +[[nodiscard]] std::string SanitizePath( + std::string_view path, + DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); + +// To deal with Windows being dumb at Unicode +template <typename T> +void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { +#ifdef _MSC_VER + fstream.open(Common::UTF8ToUTF16W(filename), openmode); +#else + fstream.open(filename, openmode); +#endif +} // simple wrapper for cstdlib file functions to // hopefully will make error checking easier @@ -214,7 +232,7 @@ public: void Swap(IOFile& other) noexcept; - bool Open(const std::string& filename, const char openmode[], int flags = 0); + [[nodiscard]] bool Open(const std::string& filename, const char openmode[], int flags = 0); bool Close(); template <typename T> @@ -255,13 +273,13 @@ public: return WriteArray(str.data(), str.length()); } - bool IsOpen() const { + [[nodiscard]] bool IsOpen() const { return nullptr != m_file; } bool Seek(s64 off, int origin) const; - u64 Tell() const; - u64 GetSize() const; + [[nodiscard]] u64 Tell() const; + [[nodiscard]] u64 GetSize() const; bool Resize(u64 size); bool Flush(); @@ -277,14 +295,4 @@ private: std::FILE* m_file = nullptr; }; -} // namespace FileUtil - -// To deal with Windows being dumb at unicode: -template <typename T> -void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { -#ifdef _MSC_VER - fstream.open(Common::UTF8ToUTF16W(filename), openmode); -#else - fstream.open(filename, openmode); -#endif -} +} // namespace Common::FS diff --git a/src/common/hash.h b/src/common/hash.h index b2538f3ea..298930702 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -5,36 +5,11 @@ #pragma once #include <cstddef> -#include <cstring> #include <utility> #include <boost/functional/hash.hpp> -#include "common/cityhash.h" -#include "common/common_types.h" namespace Common { -/** - * Computes a 64-bit hash over the specified block of data - * @param data Block of data to compute hash over - * @param len Length of data (in bytes) to compute hash over - * @returns 64-bit hash value that was computed over the data block - */ -static inline u64 ComputeHash64(const void* data, std::size_t len) { - return CityHash64(static_cast<const char*>(data), len); -} - -/** - * Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical - * that either the struct includes no padding, or that any padding is initialized to a known value - * by memsetting the struct to 0 before filling it in. - */ -template <typename T> -static inline u64 ComputeStructHash64(const T& data) { - static_assert(std::is_trivially_copyable_v<T>, - "Type passed to ComputeStructHash64 must be trivially copyable"); - return ComputeHash64(&data, sizeof(data)); -} - struct PairHash { template <class T1, class T2> std::size_t operator()(const std::pair<T1, T2>& pair) const noexcept { diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index c2f6cf0f6..74f52dd11 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -3,21 +3,9 @@ // Refer to the license.txt file included. #include "common/hex_util.h" -#include "common/logging/log.h" namespace Common { -u8 ToHexNibble(char c1) { - if (c1 >= 65 && c1 <= 70) - return c1 - 55; - if (c1 >= 97 && c1 <= 102) - return c1 - 87; - if (c1 >= 48 && c1 <= 57) - return c1 - 48; - LOG_ERROR(Common, "Invalid hex digit: 0x{:02X}", c1); - return 0; -} - std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { std::vector<u8> out(str.size() / 2); if (little_endian) { @@ -30,26 +18,4 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { return out; } -std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { - if (len != 32) { - LOG_ERROR(Common, - "Attempting to parse string to array that is not of correct size (expected=32, " - "actual={}).", - len); - return {}; - } - return HexStringToArray<16>(str); -} - -std::array<u8, 32> operator""_array32(const char* str, std::size_t len) { - if (len != 64) { - LOG_ERROR(Common, - "Attempting to parse string to array that is not of correct size (expected=64, " - "actual={}).", - len); - return {}; - } - return HexStringToArray<32>(str); -} - } // namespace Common diff --git a/src/common/hex_util.h b/src/common/hex_util.h index bb4736f96..120f1a5e6 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -14,25 +14,37 @@ namespace Common { -u8 ToHexNibble(char c1); +[[nodiscard]] constexpr u8 ToHexNibble(char c) { + if (c >= 65 && c <= 70) { + return c - 55; + } + + if (c >= 97 && c <= 102) { + return c - 87; + } + + return c - 48; +} -std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); +[[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); template <std::size_t Size, bool le = false> -std::array<u8, Size> HexStringToArray(std::string_view str) { +[[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) { std::array<u8, Size> out{}; if constexpr (le) { - for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) + for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) { out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } } else { - for (std::size_t i = 0; i < 2 * Size; i += 2) + for (std::size_t i = 0; i < 2 * Size; i += 2) { out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } } return out; } template <typename ContiguousContainer> -std::string HexToString(const ContiguousContainer& data, bool upper = true) { +[[nodiscard]] std::string HexToString(const ContiguousContainer& data, bool upper = true) { static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>, "Underlying type within the contiguous container must be u8."); @@ -48,7 +60,12 @@ std::string HexToString(const ContiguousContainer& data, bool upper = true) { return out; } -std::array<u8, 0x10> operator"" _array16(const char* str, std::size_t len); -std::array<u8, 0x20> operator"" _array32(const char* str, std::size_t len); +[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) { + return HexStringToArray<16>(data); +} + +[[nodiscard]] constexpr std::array<u8, 32> AsArray(const char (&data)[65]) { + return HexStringToArray<32>(data); +} } // namespace Common diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 04bc3128f..62cfde397 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -113,19 +113,19 @@ private: Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, std::string message) const { using std::chrono::duration_cast; + using std::chrono::microseconds; using std::chrono::steady_clock; - Entry entry; - entry.timestamp = - duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); - entry.log_class = log_class; - entry.log_level = log_level; - entry.filename = filename; - entry.line_num = line_nr; - entry.function = function; - entry.message = std::move(message); - - return entry; + return { + .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = filename, + .line_num = line_nr, + .function = function, + .message = std::move(message), + .final_entry = false, + }; } std::mutex writing_mutex; diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index fc338c70d..da1c2f185 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -21,19 +21,13 @@ class Filter; */ struct Entry { std::chrono::microseconds timestamp; - Class log_class; - Level log_level; - const char* filename; - unsigned int line_num; + Class log_class{}; + Level log_level{}; + const char* filename = nullptr; + unsigned int line_num = 0; std::string function; std::string message; bool final_entry = false; - - Entry() = default; - Entry(Entry&& o) = default; - - Entry& operator=(Entry&& o) = default; - Entry& operator=(const Entry& o) = default; }; /** @@ -100,7 +94,7 @@ public: void Write(const Entry& entry) override; private: - FileUtil::IOFile file; + Common::FS::IOFile file; std::size_t bytes_written; }; diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp index ade6759bb..25700015a 100644 --- a/src/common/lz4_compression.cpp +++ b/src/common/lz4_compression.cpp @@ -14,19 +14,19 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) { ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size"); const auto source_size_int = static_cast<int>(source_size); - const int max_compressed_size = LZ4_compressBound(source_size_int); + const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int)); std::vector<u8> compressed(max_compressed_size); - const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source), - reinterpret_cast<char*>(compressed.data()), - source_size_int, max_compressed_size); + const int compressed_size = LZ4_compress_default( + reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()), + source_size_int, static_cast<int>(max_compressed_size)); if (compressed_size <= 0) { // Compression failed return {}; } - compressed.resize(compressed_size); + compressed.resize(static_cast<std::size_t>(compressed_size)); return compressed; } @@ -38,19 +38,19 @@ std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX); const auto source_size_int = static_cast<int>(source_size); - const int max_compressed_size = LZ4_compressBound(source_size_int); + const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int)); std::vector<u8> compressed(max_compressed_size); const int compressed_size = LZ4_compress_HC( reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()), - source_size_int, max_compressed_size, compression_level); + source_size_int, static_cast<int>(max_compressed_size), compression_level); if (compressed_size <= 0) { // Compression failed return {}; } - compressed.resize(compressed_size); + compressed.resize(static_cast<std::size_t>(compressed_size)); return compressed; } diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h index 4c16f6e03..87a4be1b0 100644 --- a/src/common/lz4_compression.h +++ b/src/common/lz4_compression.h @@ -13,12 +13,12 @@ namespace Common::Compression { /** * Compresses a source memory region with LZ4 and returns the compressed data in a vector. * - * @param source the uncompressed source memory region. - * @param source_size the size in bytes of the uncompressed source memory region. + * @param source The uncompressed source memory region. + * @param source_size The size of the uncompressed source memory region. * * @return the compressed data. */ -std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size); +[[nodiscard]] std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size); /** * Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression @@ -26,23 +26,24 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size); * compression level has almost no impact on decompression speed. Data compressed with LZ4HC can * also be decompressed with the default LZ4 decompression. * - * @param source the uncompressed source memory region. - * @param source_size the size in bytes of the uncompressed source memory region. - * @param compression_level the used compression level. Should be between 3 and 12. + * @param source The uncompressed source memory region. + * @param source_size The size of the uncompressed source memory region. + * @param compression_level The used compression level. Should be between 3 and 12. * * @return the compressed data. */ -std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level); +[[nodiscard]] std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, + s32 compression_level); /** * Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level. * - * @param source the uncompressed source memory region. - * @param source_size the size in bytes of the uncompressed source memory region. + * @param source The uncompressed source memory region. + * @param source_size The size of the uncompressed source memory region * * @return the compressed data. */ -std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size); +[[nodiscard]] std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size); /** * Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector. @@ -52,6 +53,7 @@ std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size); * * @return the decompressed data. */ -std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size); +[[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, + std::size_t uncompressed_size); } // namespace Common::Compression
\ No newline at end of file diff --git a/src/common/math_util.h b/src/common/math_util.h index 83ef0201f..b35ad8507 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 { @@ -23,7 +23,7 @@ struct Rectangle { constexpr Rectangle(T left, T top, T right, T bottom) : left(left), top(top), right(right), bottom(bottom) {} - T GetWidth() const { + [[nodiscard]] T GetWidth() const { if constexpr (std::is_floating_point_v<T>) { return std::abs(right - left); } else { @@ -31,7 +31,7 @@ struct Rectangle { } } - T GetHeight() const { + [[nodiscard]] T GetHeight() const { if constexpr (std::is_floating_point_v<T>) { return std::abs(bottom - top); } else { @@ -39,21 +39,21 @@ struct Rectangle { } } - Rectangle<T> TranslateX(const T x) const { + [[nodiscard]] Rectangle<T> TranslateX(const T x) const { return Rectangle{left + x, top, right + x, bottom}; } - Rectangle<T> TranslateY(const T y) const { + [[nodiscard]] Rectangle<T> TranslateY(const T y) const { return Rectangle{left, top + y, right, bottom + y}; } - Rectangle<T> Scale(const float s) const { + [[nodiscard]] Rectangle<T> Scale(const float s) const { return Rectangle{left, top, static_cast<T>(left + GetWidth() * s), static_cast<T>(top + GetHeight() * s)}; } }; template <typename T> -Rectangle(T, T, T, T)->Rectangle<T>; +Rectangle(T, T, T, T) -> Rectangle<T>; } // namespace Common diff --git a/src/common/memory_detect.h b/src/common/memory_detect.h index a73c0f3f4..0f73751c8 100644 --- a/src/common/memory_detect.h +++ b/src/common/memory_detect.h @@ -17,6 +17,6 @@ struct MemoryInfo { * Gets the memory info of the host system * @return Reference to a MemoryInfo struct with the physical and swap memory sizes in bytes */ -const MemoryInfo& GetMemInfo(); +[[nodiscard]] const MemoryInfo& GetMemInfo(); } // namespace Common
\ No newline at end of file diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h index 50acfdbf2..4b305bf40 100644 --- a/src/common/multi_level_queue.h +++ b/src/common/multi_level_queue.h @@ -223,15 +223,15 @@ public: ListShiftForward(levels[priority], n); } - std::size_t depth() const { + [[nodiscard]] std::size_t depth() const { return Depth; } - std::size_t size(u32 priority) const { + [[nodiscard]] std::size_t size(u32 priority) const { return levels[priority].size(); } - std::size_t size() const { + [[nodiscard]] std::size_t size() const { u64 priorities = used_priorities; std::size_t size = 0; while (priorities != 0) { @@ -242,64 +242,64 @@ public: return size; } - bool empty() const { + [[nodiscard]] bool empty() const { return used_priorities == 0; } - bool empty(u32 priority) const { + [[nodiscard]] bool empty(u32 priority) const { return (used_priorities & (1ULL << priority)) == 0; } - u32 highest_priority_set(u32 max_priority = 0) const { + [[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)); } - u32 lowest_priority_set(u32 min_priority = Depth - 1) const { + [[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); } - const_iterator cbegin(u32 max_prio = 0) const { + [[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}; } - const_iterator begin(u32 max_prio = 0) const { + [[nodiscard]] const_iterator begin(u32 max_prio = 0) const { return cbegin(max_prio); } - iterator begin(u32 max_prio = 0) { + [[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}; } - const_iterator cend(u32 min_prio = Depth - 1) const { + [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const { return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1); } - const_iterator end(u32 min_prio = Depth - 1) const { + [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const { return cend(min_prio); } - iterator end(u32 min_prio = Depth - 1) { + [[nodiscard]] iterator end(u32 min_prio = Depth - 1) { return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1); } - T& front(u32 max_priority = 0) { + [[nodiscard]] T& front(u32 max_priority = 0) { const u32 priority = highest_priority_set(max_priority); return levels[priority == Depth ? 0 : priority].front(); } - const T& front(u32 max_priority = 0) const { + [[nodiscard]] const T& front(u32 max_priority = 0) const { const u32 priority = highest_priority_set(max_priority); return levels[priority == Depth ? 0 : priority].front(); } - T back(u32 min_priority = Depth - 1) { + [[nodiscard]] T& back(u32 min_priority = Depth - 1) { const u32 priority = lowest_priority_set(min_priority); // intended return levels[priority == Depth ? 63 : priority].back(); } - const T& back(u32 min_priority = Depth - 1) const { + [[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(); } @@ -329,7 +329,8 @@ private: in_list.splice(position, out_list, element); } - static const_list_iterator ListIterateTo(const std::list<T>& list, const T& 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; diff --git a/src/common/page_table.h b/src/common/page_table.h index 1e8bd3187..cf5eed780 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -36,11 +36,11 @@ struct SpecialRegion { MemoryHookPointer handler; - bool operator<(const SpecialRegion& other) const { + [[nodiscard]] bool operator<(const SpecialRegion& other) const { return std::tie(type, handler) < std::tie(other.type, other.handler); } - bool operator==(const SpecialRegion& other) const { + [[nodiscard]] bool operator==(const SpecialRegion& other) const { return std::tie(type, handler) == std::tie(other.type, other.handler); } }; diff --git a/src/common/param_package.h b/src/common/param_package.h index 6a0a9b656..c13e45479 100644 --- a/src/common/param_package.h +++ b/src/common/param_package.h @@ -19,19 +19,19 @@ public: explicit ParamPackage(const std::string& serialized); ParamPackage(std::initializer_list<DataType::value_type> list); ParamPackage(const ParamPackage& other) = default; - ParamPackage(ParamPackage&& other) = default; + ParamPackage(ParamPackage&& other) noexcept = default; ParamPackage& operator=(const ParamPackage& other) = default; ParamPackage& operator=(ParamPackage&& other) = default; - std::string Serialize() const; - std::string Get(const std::string& key, const std::string& default_value) const; - int Get(const std::string& key, int default_value) const; - float Get(const std::string& key, float default_value) const; + [[nodiscard]] std::string Serialize() const; + [[nodiscard]] std::string Get(const std::string& key, const std::string& default_value) const; + [[nodiscard]] int Get(const std::string& key, int default_value) const; + [[nodiscard]] float Get(const std::string& key, float default_value) const; void Set(const std::string& key, std::string value); void Set(const std::string& key, int value); void Set(const std::string& key, float value); - bool Has(const std::string& key) const; + [[nodiscard]] bool Has(const std::string& key) const; void Erase(const std::string& key); void Clear(); diff --git a/src/common/quaternion.h b/src/common/quaternion.h index 370198ae0..4d0871eb4 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -14,35 +14,66 @@ public: Vec3<T> xyz; T w{}; - Quaternion<decltype(-T{})> Inverse() const { + [[nodiscard]] Quaternion<decltype(-T{})> Inverse() const { return {-xyz, w}; } - Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const { + [[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const { return {xyz + other.xyz, w + other.w}; } - Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const { + [[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const { return {xyz - other.xyz, w - other.w}; } - Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const { + [[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*( + const Quaternion& other) const { return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), w * other.w - Dot(xyz, other.xyz)}; } - Quaternion<T> Normalized() const { + [[nodiscard]] Quaternion<T> Normalized() const { 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> -auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) { +[[nodiscard]] auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) { return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w); } -inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) { +[[nodiscard]] inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) { return {axis * std::sin(angle / 2), std::cos(angle / 2)}; } diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h index abe3b4dc2..138fa0131 100644 --- a/src/common/ring_buffer.h +++ b/src/common/ring_buffer.h @@ -91,12 +91,12 @@ public: } /// @returns Number of slots used - std::size_t Size() const { + [[nodiscard]] std::size_t Size() const { return m_write_index.load() - m_read_index.load(); } /// @returns Maximum size of ring buffer - constexpr std::size_t Capacity() const { + [[nodiscard]] constexpr std::size_t Capacity() const { return capacity; } diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h index 1df5528c4..4f946a258 100644 --- a/src/common/spin_lock.h +++ b/src/common/spin_lock.h @@ -17,7 +17,7 @@ class SpinLock { public: void lock(); void unlock(); - bool try_lock(); + [[nodiscard]] bool try_lock(); private: std::atomic_flag lck = ATOMIC_FLAG_INIT; diff --git a/src/common/string_util.h b/src/common/string_util.h index 583fd05e6..a32c07c06 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -12,19 +12,19 @@ namespace Common { /// Make a string lowercase -std::string ToLower(std::string str); +[[nodiscard]] std::string ToLower(std::string str); /// Make a string uppercase -std::string ToUpper(std::string str); +[[nodiscard]] std::string ToUpper(std::string str); -std::string StringFromBuffer(const std::vector<u8>& data); +[[nodiscard]] std::string StringFromBuffer(const std::vector<u8>& data); -std::string StripSpaces(const std::string& s); -std::string StripQuotes(const std::string& s); +[[nodiscard]] std::string StripSpaces(const std::string& s); +[[nodiscard]] std::string StripQuotes(const std::string& s); -std::string StringFromBool(bool value); +[[nodiscard]] std::string StringFromBool(bool value); -std::string TabsToSpaces(int tab_size, std::string in); +[[nodiscard]] std::string TabsToSpaces(int tab_size, std::string in); void SplitString(const std::string& str, char delim, std::vector<std::string>& output); @@ -34,14 +34,15 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, const std::string& _Filename); -std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest); +[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, + const std::string& dest); -std::string UTF16ToUTF8(const std::u16string& input); -std::u16string UTF8ToUTF16(const std::string& input); +[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input); +[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input); #ifdef _WIN32 -std::string UTF16ToUTF8(const std::wstring& input); -std::wstring UTF8ToUTF16W(const std::string& str); +[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input); +[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str); #endif @@ -50,7 +51,7 @@ std::wstring UTF8ToUTF16W(const std::string& str); * `other` for equality. */ template <typename InIt> -bool ComparePartialString(InIt begin, InIt end, const char* other) { +[[nodiscard]] bool ComparePartialString(InIt begin, InIt end, const char* other) { for (; begin != end && *other != '\0'; ++begin, ++other) { if (*begin != *other) { return false; @@ -64,26 +65,15 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { * Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't * NUL-terminated then the string ends at max_len characters. */ -std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); +[[nodiscard]] std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, + std::size_t max_len); /** * Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't * null-terminated, then the string ends at the greatest multiple of two less then or equal to * max_len_bytes. */ -std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, - std::size_t max_len); - -/** - * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's - * intended to be used to strip a system-specific build directory from the `__FILE__` macro, - * leaving only the path relative to the sources root. - * - * @param path The input file path as a null-terminated string - * @param root The name of the root source directory as a null-terminated string. Path up to and - * including the last occurrence of this name will be stripped - * @return A pointer to the same string passed as `path`, but starting at the trimmed portion - */ -const char* TrimSourcePath(const char* path, const char* root = "src"); +[[nodiscard]] std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, + std::size_t max_len); } // namespace Common diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp index 16d42facd..6241d08b3 100644 --- a/src/common/telemetry.cpp +++ b/src/common/telemetry.cpp @@ -12,7 +12,7 @@ #include "common/x64/cpu_detect.h" #endif -namespace Telemetry { +namespace Common::Telemetry { void FieldCollection::Accept(VisitorInterface& visitor) const { for (const auto& field : fields) { @@ -88,4 +88,4 @@ void AppendOSInfo(FieldCollection& fc) { #endif } -} // namespace Telemetry +} // namespace Common::Telemetry diff --git a/src/common/telemetry.h b/src/common/telemetry.h index 854a73fae..a50c5d1de 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -10,7 +10,7 @@ #include <string> #include "common/common_types.h" -namespace Telemetry { +namespace Common::Telemetry { /// Field type, used for grouping fields together in the final submitted telemetry log enum class FieldType : u8 { @@ -63,30 +63,30 @@ public: void Accept(VisitorInterface& visitor) const override; - const std::string& GetName() const override { + [[nodiscard]] const std::string& GetName() const override { return name; } /** * Returns the type of the field. */ - FieldType GetType() const { + [[nodiscard]] FieldType GetType() const { return type; } /** * Returns the value of the field. */ - const T& GetValue() const { + [[nodiscard]] const T& GetValue() const { return value; } - bool operator==(const Field& other) const { + [[nodiscard]] bool operator==(const Field& other) const { return (type == other.type) && (name == other.name) && (value == other.value); } - bool operator!=(const Field& other) const { - return !(*this == other); + [[nodiscard]] bool operator!=(const Field& other) const { + return !operator==(other); } private: @@ -196,4 +196,4 @@ void AppendCPUInfo(FieldCollection& fc); /// such as platform name, etc. void AppendOSInfo(FieldCollection& fc); -} // namespace Telemetry +} // namespace Common::Telemetry 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_queue_list.h b/src/common/thread_queue_list.h index 791f99a8c..def9e5d8d 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -18,14 +18,14 @@ struct ThreadQueueList { using Priority = unsigned int; // Number of priority levels. (Valid levels are [0..NUM_QUEUES).) - static const Priority NUM_QUEUES = N; + static constexpr Priority NUM_QUEUES = N; ThreadQueueList() { first = nullptr; } // Only for debugging, returns priority level. - Priority contains(const T& uid) const { + [[nodiscard]] Priority contains(const T& uid) const { for (Priority i = 0; i < NUM_QUEUES; ++i) { const Queue& cur = queues[i]; if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) { @@ -36,7 +36,7 @@ struct ThreadQueueList { return -1; } - T get_first() const { + [[nodiscard]] T get_first() const { const Queue* cur = first; while (cur != nullptr) { if (!cur->data.empty()) { @@ -49,7 +49,7 @@ struct ThreadQueueList { } template <typename UnaryPredicate> - T get_first_filter(UnaryPredicate filter) const { + [[nodiscard]] T get_first_filter(UnaryPredicate filter) const { const Queue* cur = first; while (cur != nullptr) { if (!cur->data.empty()) { @@ -129,7 +129,7 @@ struct ThreadQueueList { first = nullptr; } - bool empty(Priority priority) const { + [[nodiscard]] bool empty(Priority priority) const { const Queue* cur = &queues[priority]; return cur->data.empty(); } diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 8268bbd5c..a4647314a 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -25,15 +25,15 @@ public: delete read_ptr; } - std::size_t Size() const { + [[nodiscard]] std::size_t Size() const { return size.load(); } - bool Empty() const { + [[nodiscard]] bool Empty() const { return Size() == 0; } - T& Front() const { + [[nodiscard]] T& Front() const { return read_ptr->current; } @@ -130,15 +130,15 @@ private: template <typename T> class MPSCQueue { public: - std::size_t Size() const { + [[nodiscard]] std::size_t Size() const { return spsc_queue.Size(); } - bool Empty() const { + [[nodiscard]] bool Empty() const { return spsc_queue.Empty(); } - T& Front() const { + [[nodiscard]] T& Front() const { return spsc_queue.Front(); } diff --git a/src/common/time_zone.h b/src/common/time_zone.h index 945daa09c..9f5939ca5 100644 --- a/src/common/time_zone.h +++ b/src/common/time_zone.h @@ -10,9 +10,9 @@ namespace Common::TimeZone { /// Gets the default timezone, i.e. "GMT" -std::string GetDefaultTimeZone(); +[[nodiscard]] std::string GetDefaultTimeZone(); /// Gets the offset of the current timezone (from the default), in seconds -std::chrono::seconds GetCurrentOffsetSeconds(); +[[nodiscard]] std::chrono::seconds GetCurrentOffsetSeconds(); } // namespace Common::TimeZone diff --git a/src/common/timer.h b/src/common/timer.h index 27b521baa..8894a143d 100644 --- a/src/common/timer.h +++ b/src/common/timer.h @@ -19,18 +19,18 @@ public: // The time difference is always returned in milliseconds, regardless of alternative internal // representation - std::chrono::milliseconds GetTimeDifference(); + [[nodiscard]] std::chrono::milliseconds GetTimeDifference(); void AddTimeDifference(); - static std::chrono::seconds GetTimeSinceJan1970(); - static std::chrono::seconds GetLocalTimeSinceJan1970(); - static double GetDoubleTime(); + [[nodiscard]] static std::chrono::seconds GetTimeSinceJan1970(); + [[nodiscard]] static std::chrono::seconds GetLocalTimeSinceJan1970(); + [[nodiscard]] static double GetDoubleTime(); - static std::string GetTimeFormatted(); - std::string GetTimeElapsedFormatted() const; - std::chrono::milliseconds GetTimeElapsed(); + [[nodiscard]] static std::string GetTimeFormatted(); + [[nodiscard]] std::string GetTimeElapsedFormatted() const; + [[nodiscard]] std::chrono::milliseconds GetTimeElapsed(); - static std::chrono::milliseconds GetTimeMs(); + [[nodiscard]] static std::chrono::milliseconds GetTimeMs(); private: std::chrono::milliseconds m_LastTime; diff --git a/src/common/uint128.h b/src/common/uint128.h index 503cd2d0c..969259ab6 100644 --- a/src/common/uint128.h +++ b/src/common/uint128.h @@ -10,13 +10,13 @@ namespace Common { // This function multiplies 2 u64 values and divides it by a u64 value. -u64 MultiplyAndDivide64(u64 a, u64 b, u64 d); +[[nodiscard]] u64 MultiplyAndDivide64(u64 a, u64 b, u64 d); // This function multiplies 2 u64 values and produces a u128 value; -u128 Multiply64Into128(u64 a, u64 b); +[[nodiscard]] u128 Multiply64Into128(u64 a, u64 b); // This function divides a u128 by a u32 value and produces two u64 values: // the result of division and the remainder -std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor); +[[nodiscard]] std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor); } // namespace Common diff --git a/src/common/uuid.h b/src/common/uuid.h index 4d3af8cec..4ab9a25f0 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -19,21 +19,21 @@ struct UUID { constexpr explicit UUID(const u128& id) : uuid{id} {} constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} - constexpr explicit operator bool() const { + [[nodiscard]] constexpr explicit operator bool() const { return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1]; } - constexpr bool operator==(const UUID& rhs) const { + [[nodiscard]] constexpr bool operator==(const UUID& rhs) const { // TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20 return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1]; } - constexpr bool operator!=(const UUID& rhs) const { + [[nodiscard]] constexpr bool operator!=(const UUID& rhs) const { return !operator==(rhs); } // TODO(ogniK): Properly generate uuids based on RFC-4122 - static UUID Generate(); + [[nodiscard]] static UUID Generate(); // Set the UUID to {0,0} to be considered an invalid user constexpr void Invalidate() { @@ -41,12 +41,12 @@ struct UUID { } // TODO(ogniK): Properly generate a Nintendo ID - constexpr u64 GetNintendoID() const { + [[nodiscard]] constexpr u64 GetNintendoID() const { return uuid[0]; } - std::string Format() const; - std::string FormatSwitch() const; + [[nodiscard]] std::string Format() const; + [[nodiscard]] std::string FormatSwitch() const; }; static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 429485329..2a0fcf541 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -52,15 +52,15 @@ public: constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {} template <typename T2> - constexpr Vec2<T2> Cast() const { + [[nodiscard]] constexpr Vec2<T2> Cast() const { return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y)); } - static constexpr Vec2 AssignToAll(const T& f) { + [[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) { return Vec2{f, f}; } - constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { + [[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { return {x + other.x, y + other.y}; } constexpr Vec2& operator+=(const Vec2& other) { @@ -68,7 +68,7 @@ public: y += other.y; return *this; } - constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const { + [[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const { return {x - other.x, y - other.y}; } constexpr Vec2& operator-=(const Vec2& other) { @@ -78,15 +78,15 @@ public: } template <typename U = T> - constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + [[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { return {-x, -y}; } - constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { + [[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { return {x * other.x, y * other.y}; } template <typename V> - constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { + [[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { return {x * f, y * f}; } @@ -97,7 +97,7 @@ public: } template <typename V> - constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const { + [[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const { return {x / f, y / f}; } @@ -107,18 +107,18 @@ public: return *this; } - constexpr T Length2() const { + [[nodiscard]] constexpr T Length2() const { return x * x + y * y; } // Only implemented for T=float - float Length() const; - float Normalize(); // returns the previous length, which is often useful + [[nodiscard]] float Length() const; + [[nodiscard]] float Normalize(); // returns the previous length, which is often useful - constexpr T& operator[](std::size_t i) { + [[nodiscard]] constexpr T& operator[](std::size_t i) { return *((&x) + i); } - constexpr const T& operator[](std::size_t i) const { + [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } @@ -128,46 +128,46 @@ public: } // Common aliases: UV (texel coordinates), ST (texture coordinates) - constexpr T& u() { + [[nodiscard]] constexpr T& u() { return x; } - constexpr T& v() { + [[nodiscard]] constexpr T& v() { return y; } - constexpr T& s() { + [[nodiscard]] constexpr T& s() { return x; } - constexpr T& t() { + [[nodiscard]] constexpr T& t() { return y; } - constexpr const T& u() const { + [[nodiscard]] constexpr const T& u() const { return x; } - constexpr const T& v() const { + [[nodiscard]] constexpr const T& v() const { return y; } - constexpr const T& s() const { + [[nodiscard]] constexpr const T& s() const { return x; } - constexpr const T& t() const { + [[nodiscard]] constexpr const T& t() const { return y; } // swizzlers - create a subvector of specific components - constexpr Vec2 yx() const { + [[nodiscard]] constexpr Vec2 yx() const { return Vec2(y, x); } - constexpr Vec2 vu() const { + [[nodiscard]] constexpr Vec2 vu() const { return Vec2(y, x); } - constexpr Vec2 ts() const { + [[nodiscard]] constexpr Vec2 ts() const { return Vec2(y, x); } }; template <typename T, typename V> -constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) { +[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) { return Vec2<T>(f * vec.x, f * vec.y); } @@ -196,15 +196,15 @@ public: constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} template <typename T2> - constexpr Vec3<T2> Cast() const { + [[nodiscard]] constexpr Vec3<T2> Cast() const { return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z)); } - static constexpr Vec3 AssignToAll(const T& f) { + [[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) { return Vec3(f, f, f); } - constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const { + [[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const { return {x + other.x, y + other.y, z + other.z}; } @@ -215,7 +215,7 @@ public: return *this; } - constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const { + [[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const { return {x - other.x, y - other.y, z - other.z}; } @@ -227,16 +227,16 @@ public: } template <typename U = T> - constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + [[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { return {-x, -y, -z}; } - constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { + [[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { return {x * other.x, y * other.y, z * other.z}; } template <typename V> - constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const { + [[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const { return {x * f, y * f, z * f}; } @@ -246,7 +246,7 @@ public: return *this; } template <typename V> - constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const { + [[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const { return {x / f, y / f, z / f}; } @@ -256,20 +256,20 @@ public: return *this; } - constexpr T Length2() const { + [[nodiscard]] constexpr T Length2() const { return x * x + y * y + z * z; } // Only implemented for T=float - float Length() const; - Vec3 Normalized() const; - float Normalize(); // returns the previous length, which is often useful + [[nodiscard]] float Length() const; + [[nodiscard]] Vec3 Normalized() const; + [[nodiscard]] float Normalize(); // returns the previous length, which is often useful - constexpr T& operator[](std::size_t i) { + [[nodiscard]] constexpr T& operator[](std::size_t i) { return *((&x) + i); } - constexpr const T& operator[](std::size_t i) const { + [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } @@ -280,63 +280,63 @@ public: } // Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates) - constexpr T& u() { + [[nodiscard]] constexpr T& u() { return x; } - constexpr T& v() { + [[nodiscard]] constexpr T& v() { return y; } - constexpr T& w() { + [[nodiscard]] constexpr T& w() { return z; } - constexpr T& r() { + [[nodiscard]] constexpr T& r() { return x; } - constexpr T& g() { + [[nodiscard]] constexpr T& g() { return y; } - constexpr T& b() { + [[nodiscard]] constexpr T& b() { return z; } - constexpr T& s() { + [[nodiscard]] constexpr T& s() { return x; } - constexpr T& t() { + [[nodiscard]] constexpr T& t() { return y; } - constexpr T& q() { + [[nodiscard]] constexpr T& q() { return z; } - constexpr const T& u() const { + [[nodiscard]] constexpr const T& u() const { return x; } - constexpr const T& v() const { + [[nodiscard]] constexpr const T& v() const { return y; } - constexpr const T& w() const { + [[nodiscard]] constexpr const T& w() const { return z; } - constexpr const T& r() const { + [[nodiscard]] constexpr const T& r() const { return x; } - constexpr const T& g() const { + [[nodiscard]] constexpr const T& g() const { return y; } - constexpr const T& b() const { + [[nodiscard]] constexpr const T& b() const { return z; } - constexpr const T& s() const { + [[nodiscard]] constexpr const T& s() const { return x; } - constexpr const T& t() const { + [[nodiscard]] constexpr const T& t() const { return y; } - constexpr const T& q() const { + [[nodiscard]] constexpr const T& q() const { return z; } @@ -345,7 +345,7 @@ public: // _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all // component names (x<->r) and permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - constexpr Vec2<T> name() const { \ + [[nodiscard]] constexpr Vec2<T> name() const { \ return Vec2<T>(a, b); \ } #define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \ @@ -366,7 +366,7 @@ public: }; template <typename T, typename V> -constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) { +[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) { return Vec3<T>(f * vec.x, f * vec.y, f * vec.z); } @@ -402,16 +402,16 @@ public: : x(x_), y(y_), z(z_), w(w_) {} template <typename T2> - constexpr Vec4<T2> Cast() const { + [[nodiscard]] constexpr Vec4<T2> Cast() const { return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z), static_cast<T2>(w)); } - static constexpr Vec4 AssignToAll(const T& f) { + [[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) { return Vec4(f, f, f, f); } - constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const { + [[nodiscard]] constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const { return {x + other.x, y + other.y, z + other.z, w + other.w}; } @@ -423,7 +423,7 @@ public: return *this; } - constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const { + [[nodiscard]] constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const { return {x - other.x, y - other.y, z - other.z, w - other.w}; } @@ -436,16 +436,16 @@ public: } template <typename U = T> - constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { + [[nodiscard]] constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { return {-x, -y, -z, -w}; } - constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { + [[nodiscard]] constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { return {x * other.x, y * other.y, z * other.z, w * other.w}; } template <typename V> - constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const { + [[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const { return {x * f, y * f, z * f, w * f}; } @@ -456,7 +456,7 @@ public: } template <typename V> - constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const { + [[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const { return {x / f, y / f, z / f, w / f}; } @@ -466,15 +466,15 @@ public: return *this; } - constexpr T Length2() const { + [[nodiscard]] constexpr T Length2() const { return x * x + y * y + z * z + w * w; } - constexpr T& operator[](std::size_t i) { + [[nodiscard]] constexpr T& operator[](std::size_t i) { return *((&x) + i); } - constexpr const T& operator[](std::size_t i) const { + [[nodiscard]] constexpr const T& operator[](std::size_t i) const { return *((&x) + i); } @@ -486,29 +486,29 @@ public: } // Common alias: RGBA (colors) - constexpr T& r() { + [[nodiscard]] constexpr T& r() { return x; } - constexpr T& g() { + [[nodiscard]] constexpr T& g() { return y; } - constexpr T& b() { + [[nodiscard]] constexpr T& b() { return z; } - constexpr T& a() { + [[nodiscard]] constexpr T& a() { return w; } - constexpr const T& r() const { + [[nodiscard]] constexpr const T& r() const { return x; } - constexpr const T& g() const { + [[nodiscard]] constexpr const T& g() const { return y; } - constexpr const T& b() const { + [[nodiscard]] constexpr const T& b() const { return z; } - constexpr const T& a() const { + [[nodiscard]] constexpr const T& a() const { return w; } @@ -520,7 +520,7 @@ public: // DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and // permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - constexpr Vec2<T> name() const { \ + [[nodiscard]] constexpr Vec2<T> name() const { \ return Vec2<T>(a, b); \ } #define DEFINE_SWIZZLER2_COMP1(a, a2) \ @@ -547,7 +547,7 @@ public: #undef _DEFINE_SWIZZLER2 #define _DEFINE_SWIZZLER3(a, b, c, name) \ - constexpr Vec3<T> name() const { \ + [[nodiscard]] constexpr Vec3<T> name() const { \ return Vec3<T>(a, b, c); \ } #define DEFINE_SWIZZLER3_COMP1(a, a2) \ @@ -581,7 +581,7 @@ public: }; template <typename T, typename V> -constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) { +[[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}; } @@ -593,39 +593,41 @@ constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b } template <typename T> -constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) { +[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } template <typename T> -constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) { +[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } template <typename T> -constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) { +[[nodiscard]] constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, + const Vec3<T>& b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } // linear interpolation via float: 0.0=begin, 1.0=end template <typename X> -constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, - const float t) { +[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, + const float t) { return begin * (1.f - t) + end * t; } // linear interpolation via int: 0=begin, base=end template <typename X, int base> -constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end, - const int t) { +[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, + const X& end, + const int t) { return (begin * (base - t) + end * t) / base; } // bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second // interpolation. template <typename X> -constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s, - const float t) { +[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, + const float s, const float t) { auto y0 = Lerp(x00, x01, s); auto y1 = Lerp(x10, x11, s); return Lerp(y0, y1, t); @@ -633,42 +635,42 @@ constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& // Utility vector factories template <typename T> -constexpr Vec2<T> MakeVec(const T& x, const T& y) { +[[nodiscard]] constexpr Vec2<T> MakeVec(const T& x, const T& y) { return Vec2<T>{x, y}; } template <typename T> -constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) { +[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) { return Vec3<T>{x, y, z}; } template <typename T> -constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) { return MakeVec(x, y, zw[0], zw[1]); } template <typename T> -constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) { +[[nodiscard]] constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) { return MakeVec(xy[0], xy[1], z); } template <typename T> -constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) { +[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) { return MakeVec(x, yz[0], yz[1]); } template <typename T> -constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) { return Vec4<T>{x, y, z, w}; } template <typename T> -constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) { return MakeVec(xy[0], xy[1], z, w); } template <typename T> -constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { return MakeVec(x, yz[0], yz[1], w); } @@ -676,17 +678,17 @@ constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) { // Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error // out soon enough due to misuse of the returned structure. template <typename T> -constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) { return MakeVec(xy[0], xy[1], zw[0], zw[1]); } template <typename T> -constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) { return MakeVec(xyz[0], xyz[1], xyz[2], w); } template <typename T> -constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) { +[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) { return MakeVec(x, yzw[0], yzw[1], yzw[2]); } diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp index b426f4747..b009cb500 100644 --- a/src/common/virtual_buffer.cpp +++ b/src/common/virtual_buffer.cpp @@ -5,16 +5,7 @@ #ifdef _WIN32 #include <windows.h> #else -#include <stdio.h> #include <sys/mman.h> -#include <sys/types.h> -#if defined __APPLE__ || defined __FreeBSD__ || defined __OpenBSD__ -#include <sys/sysctl.h> -#elif defined __HAIKU__ -#include <OS.h> -#else -#include <sys/sysinfo.h> -#endif #endif #include "common/assert.h" @@ -38,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) { return base; } -void FreeMemoryPages(void* base, std::size_t size) { +void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) { if (!base) { return; } diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h index da064e59e..125cb42f0 100644 --- a/src/common/virtual_buffer.h +++ b/src/common/virtual_buffer.h @@ -30,23 +30,23 @@ public: base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); } - constexpr const T& operator[](std::size_t index) const { + [[nodiscard]] constexpr const T& operator[](std::size_t index) const { return base_ptr[index]; } - constexpr T& operator[](std::size_t index) { + [[nodiscard]] constexpr T& operator[](std::size_t index) { return base_ptr[index]; } - constexpr T* data() { + [[nodiscard]] constexpr T* data() { return base_ptr; } - constexpr const T* data() const { + [[nodiscard]] constexpr const T* data() const { return base_ptr; } - constexpr std::size_t size() const { + [[nodiscard]] constexpr std::size_t size() const { return alloc_size / sizeof(T); } diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index 3afbdb898..7a20e95b7 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -15,7 +15,7 @@ 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) { diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 367d72134..bc7adfbf8 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.h @@ -13,25 +13,27 @@ namespace Common { class WallClock { public: + virtual ~WallClock() = default; + /// Returns current wall time in nanoseconds - virtual std::chrono::nanoseconds GetTimeNS() = 0; + [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; /// Returns current wall time in microseconds - virtual std::chrono::microseconds GetTimeUS() = 0; + [[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0; /// Returns current wall time in milliseconds - virtual std::chrono::milliseconds GetTimeMS() = 0; + [[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0; /// Returns current wall time in emulated clock cycles - virtual u64 GetClockCycles() = 0; + [[nodiscard]] virtual u64 GetClockCycles() = 0; /// Returns current wall time in emulated cpu cycles - virtual u64 GetCPUCycles() = 0; + [[nodiscard]] virtual u64 GetCPUCycles() = 0; virtual void Pause(bool is_paused) = 0; /// Tells if the wall clock, uses the host CPU's hardware clock - bool IsNative() const { + [[nodiscard]] bool IsNative() const { return is_native; } @@ -47,7 +49,7 @@ private: bool is_native; }; -std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency, - u32 emulated_clock_frequency); +[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency, + u32 emulated_clock_frequency); } // namespace Common diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h index 891a3bbfd..7c503df26 100644 --- a/src/common/x64/native_clock.h +++ b/src/common/x64/native_clock.h @@ -12,7 +12,7 @@ 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); diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index a5f5d4fc1..26e4bfda5 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -11,7 +11,7 @@ namespace Common::X64 { -inline std::size_t RegToIndex(const Xbyak::Reg& reg) { +constexpr std::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."); @@ -19,17 +19,17 @@ inline std::size_t RegToIndex(const Xbyak::Reg& reg) { return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); } -inline Xbyak::Reg64 IndexToReg64(std::size_t reg_index) { +constexpr Xbyak::Reg64 IndexToReg64(std::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(std::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(std::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 diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp index 978526492..5f45459da 100644 --- a/src/common/zstd_compression.cpp +++ b/src/common/zstd_compression.cpp @@ -5,7 +5,6 @@ #include <algorithm> #include <zstd.h> -#include "common/assert.h" #include "common/zstd_compression.h" namespace Common::Compression { diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h index e9de941c8..c26a30ab9 100644 --- a/src/common/zstd_compression.h +++ b/src/common/zstd_compression.h @@ -13,24 +13,25 @@ namespace Common::Compression { /** * Compresses a source memory region with Zstandard and returns the compressed data in a vector. * - * @param source the uncompressed source memory region. - * @param source_size the size in bytes of the uncompressed source memory region. - * @param compression_level the used compression level. Should be between 1 and 22. + * @param source The uncompressed source memory region. + * @param source_size The size of the uncompressed source memory region. + * @param compression_level The used compression level. Should be between 1 and 22. * * @return the compressed data. */ -std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level); +[[nodiscard]] std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, + s32 compression_level); /** * Compresses a source memory region with Zstandard with the default compression level and returns * the compressed data in a vector. * - * @param source the uncompressed source memory region. - * @param source_size the size in bytes of the uncompressed source memory region. + * @param source The uncompressed source memory region. + * @param source_size The size of the uncompressed source memory region. * * @return the compressed data. */ -std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size); +[[nodiscard]] std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size); /** * Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector. @@ -39,6 +40,6 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz * * @return the decompressed data. */ -std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed); +[[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed); } // namespace Common::Compression
\ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c42f95705..d0c405ec7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -126,6 +126,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 @@ -244,6 +246,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 @@ -491,6 +495,7 @@ add_library(core STATIC hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h + hle/service/sockets/blocking_worker.h hle/service/sockets/bsd.cpp hle/service/sockets/bsd.h hle/service/sockets/ethc.cpp @@ -501,6 +506,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 @@ -586,6 +593,9 @@ add_library(core STATIC memory/dmnt_cheat_vm.h memory.cpp memory.h + network/network.cpp + network/network.h + network/sockets.h perf_stats.cpp perf_stats.h reporter.cpp diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp index df0350881..9c8898700 100644 --- a/src/core/arm/cpu_interrupt_handler.cpp +++ b/src/core/arm/cpu_interrupt_handler.cpp @@ -7,9 +7,7 @@ namespace Core { -CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} { - interrupt_event = std::make_unique<Common::Event>(); -} +CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {} CPUInterruptHandler::~CPUInterruptHandler() = default; @@ -17,7 +15,7 @@ void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) { if (is_interrupted_) { interrupt_event->Set(); } - this->is_interrupted = is_interrupted_; + is_interrupted = is_interrupted_; } void CPUInterruptHandler::AwaitInterrupt() { diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h index 3d062d326..71e582f79 100644 --- a/src/core/arm/cpu_interrupt_handler.h +++ b/src/core/arm/cpu_interrupt_handler.h @@ -4,6 +4,7 @@ #pragma once +#include <atomic> #include <memory> namespace Common { @@ -32,8 +33,8 @@ public: void AwaitInterrupt(); private: - bool is_interrupted{}; std::unique_ptr<Common::Event> interrupt_event; + std::atomic_bool is_interrupted{false}; }; } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 443ca72eb..b5f28a86e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -143,7 +143,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& config.wall_clock_cntpct = uses_wall_clock; // Safe optimizations - if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) { + if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { if (!Settings::values.cpuopt_page_tables) { config.page_table = nullptr; } @@ -170,6 +170,17 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& } } + // Unsafe optimizations + if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { + config.unsafe_optimizations = true; + if (Settings::values.cpuopt_unsafe_unfuse_fma) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; + } + if (Settings::values.cpuopt_unsafe_reduce_fp_error) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; + } + } + return std::make_unique<Dynarmic::A32::Jit>(config); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index a63a04a25..ce9968724 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -195,7 +195,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& config.wall_clock_cntpct = uses_wall_clock; // Safe optimizations - if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) { + if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { if (!Settings::values.cpuopt_page_tables) { config.page_table = nullptr; } @@ -222,6 +222,17 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& } } + // Unsafe optimizations + if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { + config.unsafe_optimizations = true; + if (Settings::values.cpuopt_unsafe_unfuse_fma) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; + } + if (Settings::values.cpuopt_unsafe_reduce_fp_error) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; + } + } + return std::make_shared<Dynarmic::A64::Jit>(config); } 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/core.cpp b/src/core/core.cpp index 69a1aa0a5..81e8cc338 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -43,6 +43,7 @@ #include "core/loader/loader.h" #include "core/memory.h" #include "core/memory/cheat_engine.h" +#include "core/network/network.h" #include "core/perf_stats.h" #include "core/reporter.h" #include "core/settings.h" @@ -112,7 +113,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); } - if (FileUtil::IsDirectory(path)) + if (Common::FS::IsDirectory(path)) return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); return vfs->OpenFile(path, FileSys::Mode::Read); @@ -145,7 +146,7 @@ struct System::Impl { ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { LOG_DEBUG(HW_Memory, "initialized OK"); - device_memory = std::make_unique<Core::DeviceMemory>(system); + 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(); @@ -177,7 +178,7 @@ struct System::Impl { arp_manager.ResetAll(); telemetry_session = std::make_unique<Core::TelemetrySession>(); - service_manager = std::make_shared<Service::SM::ServiceManager>(); + service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); Service::Init(service_manager, system); GDBStub::DeferStart(); @@ -187,7 +188,6 @@ struct System::Impl { if (!gpu_core) { return ResultStatus::ErrorVideoCore; } - gpu_core->Renderer().Rasterizer().SetupDirtyFlags(); is_powered_on = true; exit_lock = false; @@ -221,7 +221,7 @@ struct System::Impl { telemetry_session->AddInitialInfo(*app_loader); 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)); Shutdown(); @@ -268,14 +268,14 @@ struct System::Impl { // Log last frame performance stats if game was loded if (perf_stats) { const auto perf_results = GetAndResetPerfStats(); - telemetry_session->AddField(Telemetry::FieldType::Performance, - "Shutdown_EmulationSpeed", + constexpr auto performance = Common::Telemetry::FieldType::Performance; + + telemetry_session->AddField(performance, "Shutdown_EmulationSpeed", perf_results.emulation_speed * 100.0); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate", - perf_results.game_fps); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", + telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps); + telemetry_session->AddField(performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", + telemetry_session->AddField(performance, "Mean_Frametime_MS", perf_stats->GetMeanFrametime()); } @@ -394,6 +394,9 @@ struct System::Impl { /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; + /// Network instance + Network::NetworkInstance network_instance; + ResultStatus status = ResultStatus::Success; std::string status_details = ""; @@ -626,11 +629,11 @@ 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; } diff --git a/src/core/core.h b/src/core/core.h index 5c6cfbffe..27efe30bb 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -120,7 +120,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; } @@ -316,9 +316,9 @@ public: Service::SM::ServiceManager& ServiceManager(); 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; + FileSys::VirtualFilesystem GetFilesystem() const; void RegisterCheatList(const std::vector<Memory::CheatEntry>& list, const std::array<u8, 0x20>& build_id, VAddr main_region_begin, diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a63e60461..e6c8461a5 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -7,14 +7,14 @@ #include <string> #include <tuple> -#include "common/assert.h" #include "common/microprofile.h" #include "core/core_timing.h" #include "core/core_timing_util.h" +#include "core/hardware_properties.h" namespace Core::Timing { -constexpr u64 MAX_SLICE_LENGTH = 4000; +constexpr s64 MAX_SLICE_LENGTH = 4000; std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) { return std::make_shared<EventType>(std::move(callback), std::move(name)); @@ -23,7 +23,7 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac struct CoreTiming::Event { u64 time; u64 fifo_order; - u64 userdata; + std::uintptr_t user_data; std::weak_ptr<EventType> type; // Sort by time, unless the times are the same, in which case sort by @@ -37,10 +37,8 @@ struct CoreTiming::Event { } }; -CoreTiming::CoreTiming() { - clock = - Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ); -} +CoreTiming::CoreTiming() + : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {} CoreTiming::~CoreTiming() = default; @@ -53,12 +51,12 @@ void CoreTiming::ThreadEntry(CoreTiming& instance) { instance.ThreadLoop(); } -void CoreTiming::Initialize(std::function<void(void)>&& on_thread_init_) { +void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) { on_thread_init = std::move(on_thread_init_); event_fifo_id = 0; shutting_down = false; ticks = 0; - const auto empty_timed_callback = [](u64, s64) {}; + const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {}; ev_lost = CreateEvent("_lost_event", empty_timed_callback); if (is_multicore) { timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this)); @@ -106,23 +104,25 @@ bool CoreTiming::HasPendingEvents() const { return !(wait_set && event_queue.empty()); } -void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type, - u64 userdata) { +void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, + const std::shared_ptr<EventType>& event_type, + std::uintptr_t user_data) { { std::scoped_lock scope{basic_lock}; - const u64 timeout = static_cast<u64>(GetGlobalTimeNs().count() + ns_into_future); + const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count()); - event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type}); + event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type}); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } event.Set(); } -void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) { +void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, + std::uintptr_t user_data) { std::scoped_lock scope{basic_lock}; const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { - return e.type.lock().get() == event_type.get() && e.userdata == userdata; + return e.type.lock().get() == event_type.get() && e.user_data == user_data; }); // Removing random items breaks the invariant so we have to re-establish it. @@ -134,7 +134,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u void CoreTiming::AddTicks(u64 ticks) { this->ticks += ticks; - downcount -= ticks; + downcount -= static_cast<s64>(ticks); } void CoreTiming::Idle() { @@ -195,8 +195,9 @@ std::optional<s64> CoreTiming::Advance() { event_queue.pop_back(); basic_lock.unlock(); - if (auto event_type{evt.type.lock()}) { - event_type->callback(evt.userdata, global_timer - evt.time); + if (const auto event_type{evt.type.lock()}) { + event_type->callback( + evt.user_data, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)}); } basic_lock.lock(); diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 72faaab64..b0b6036e4 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -17,14 +17,13 @@ #include "common/common_types.h" #include "common/spin_lock.h" #include "common/thread.h" -#include "common/threadsafe_queue.h" #include "common/wall_clock.h" -#include "core/hardware_properties.h" namespace Core::Timing { /// A callback that may be scheduled for a particular core timing event. -using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>; +using TimedCallback = + std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>; /// Contains the characteristics of a particular event. struct EventType { @@ -42,12 +41,12 @@ struct EventType { * in main CPU clock cycles. * * To schedule an event, you first have to register its type. This is where you pass in the - * callback. You then schedule events using the type id you get back. + * callback. You then schedule events using the type ID you get back. * - * The int cyclesLate that the callbacks get is how many cycles late it was. + * The s64 ns_late that the callbacks get is how many ns late it was. * So to schedule a new event on a regular basis: * inside callback: - * ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever") + * ScheduleEvent(period_in_ns - ns_late, callback, "whatever") */ class CoreTiming { public: @@ -62,7 +61,7 @@ public: /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. - void Initialize(std::function<void(void)>&& on_thread_init_); + void Initialize(std::function<void()>&& on_thread_init_); /// Tears down all timing related functionality. void Shutdown(); @@ -95,10 +94,10 @@ public: bool HasPendingEvents() const; /// Schedules an event in core timing - void ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type, - u64 userdata = 0); + void ScheduleEvent(std::chrono::nanoseconds ns_into_future, + const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0); - void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata); + void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data); /// We only permit one event of each type in the queue at a time. void RemoveEvent(const std::shared_ptr<EventType>& event_type); @@ -141,8 +140,6 @@ private: u64 global_timer = 0; - std::chrono::nanoseconds start_point; - // The queue is a min-heap using std::make_heap/push_heap/pop_heap. // We don't use std::priority_queue because we need to be able to serialize, unserialize and // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't @@ -161,7 +158,7 @@ private: std::atomic<bool> wait_set{}; std::atomic<bool> shutting_down{}; std::atomic<bool> has_started{}; - std::function<void(void)> on_thread_init{}; + std::function<void()> on_thread_init{}; bool is_multicore{}; diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp index aefc63663..8ce8e602e 100644 --- a/src/core/core_timing_util.cpp +++ b/src/core/core_timing_util.cpp @@ -8,6 +8,7 @@ #include <limits> #include "common/logging/log.h" #include "common/uint128.h" +#include "core/hardware_properties.h" namespace Core::Timing { diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h index 2ed979e14..e4a046bf9 100644 --- a/src/core/core_timing_util.h +++ b/src/core/core_timing_util.h @@ -6,7 +6,6 @@ #include <chrono> #include "common/common_types.h" -#include "core/hardware_properties.h" namespace Core::Timing { diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 32afcf3ae..688b99eba 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -41,9 +41,9 @@ void CpuManager::Shutdown() { running_mode = false; Pause(false); if (is_multicore) { - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - core_data[core].host_thread->join(); - core_data[core].host_thread.reset(); + for (auto& data : core_data) { + data.host_thread->join(); + data.host_thread.reset(); } } else { core_data[0].host_thread->join(); @@ -52,15 +52,15 @@ void CpuManager::Shutdown() { } std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() { - return std::function<void(void*)>(GuestThreadFunction); + return GuestThreadFunction; } std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() { - return std::function<void(void*)>(IdleThreadFunction); + return IdleThreadFunction; } std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() { - return std::function<void(void*)>(SuspendThreadFunction); + return SuspendThreadFunction; } void CpuManager::GuestThreadFunction(void* cpu_manager_) { @@ -166,25 +166,23 @@ void CpuManager::MultiCorePause(bool paused) { bool all_not_barrier = false; while (!all_not_barrier) { all_not_barrier = true; - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - all_not_barrier &= - !core_data[core].is_running.load() && core_data[core].initialized.load(); + for (const auto& data : core_data) { + all_not_barrier &= !data.is_running.load() && data.initialized.load(); } } - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - core_data[core].enter_barrier->Set(); + for (auto& data : core_data) { + data.enter_barrier->Set(); } if (paused_state.load()) { bool all_barrier = false; while (!all_barrier) { all_barrier = true; - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - all_barrier &= - core_data[core].is_paused.load() && core_data[core].initialized.load(); + for (const auto& data : core_data) { + all_barrier &= data.is_paused.load() && data.initialized.load(); } } - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - core_data[core].exit_barrier->Set(); + for (auto& data : core_data) { + data.exit_barrier->Set(); } } } else { @@ -192,9 +190,8 @@ void CpuManager::MultiCorePause(bool paused) { bool all_barrier = false; while (!all_barrier) { all_barrier = true; - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - all_barrier &= - core_data[core].is_paused.load() && core_data[core].initialized.load(); + for (const auto& data : core_data) { + all_barrier &= data.is_paused.load() && data.initialized.load(); } } /// Don't release the barrier @@ -331,7 +328,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"; } diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 4be76bb43..6a9734812 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <mbedtls/cipher.h> #include "common/assert.h" #include "common/logging/log.h" @@ -10,8 +11,10 @@ namespace Core::Crypto { namespace { -std::vector<u8> CalculateNintendoTweak(std::size_t sector_id) { - std::vector<u8> out(0x10); +using NintendoTweak = std::array<u8, 16>; + +NintendoTweak CalculateNintendoTweak(std::size_t sector_id) { + NintendoTweak out{}; for (std::size_t i = 0xF; i <= 0xF; --i) { out[i] = sector_id & 0xFF; sector_id >>= 8; @@ -64,13 +67,6 @@ AESCipher<Key, KeySize>::~AESCipher() { } template <typename Key, std::size_t KeySize> -void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) { - ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) || - mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0, - "Failed to set IV on mbedtls ciphers."); -} - -template <typename Key, std::size_t KeySize> void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const { auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context; @@ -120,10 +116,17 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* for (std::size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); - Transcode<u8, u8>(src + i, sector_size, dest + i, op); + Transcode(src + i, sector_size, dest + i, op); } } +template <typename Key, std::size_t KeySize> +void AESCipher<Key, KeySize>::SetIVImpl(const u8* data, std::size_t size) { + ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, data, size) || + mbedtls_cipher_set_iv(&ctx->decryption_context, data, size)) == 0, + "Failed to set IV on mbedtls ciphers."); +} + template class AESCipher<Key128>; template class AESCipher<Key256>; } // namespace Core::Crypto diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index edc4ab910..e2a304186 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -6,7 +6,6 @@ #include <memory> #include <type_traits> -#include <vector> #include "common/common_types.h" #include "core/file_sys/vfs.h" @@ -32,10 +31,12 @@ class AESCipher { public: AESCipher(Key key, Mode mode); - ~AESCipher(); - void SetIV(std::vector<u8> iv); + template <typename ContiguousContainer> + void SetIV(const ContiguousContainer& container) { + SetIVImpl(std::data(container), std::size(container)); + } template <typename Source, typename Dest> void Transcode(const Source* src, std::size_t size, Dest* dest, Op op) const { @@ -59,6 +60,8 @@ public: std::size_t sector_size, Op op); private: + void SetIVImpl(const u8* data, std::size_t size); + std::unique_ptr<CipherContext> ctx; }; } // namespace Core::Crypto diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 902841c77..5c84bb0a4 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.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/assert.h" #include "core/crypto/ctr_encryption_layer.h" @@ -10,8 +11,7 @@ namespace Core::Crypto { CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, std::size_t base_offset) - : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), - iv(16, 0) {} + : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR) {} std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const { if (length == 0) @@ -39,9 +39,8 @@ std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t o return read + Read(data + read, length - read, offset + read); } -void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) { - const auto length = std::min(iv_.size(), iv.size()); - iv.assign(iv_.cbegin(), iv_.cbegin() + length); +void CTREncryptionLayer::SetIV(const IVData& iv_) { + iv = iv_; } void CTREncryptionLayer::UpdateIV(std::size_t offset) const { diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h index a7bf810f4..a2429f001 100644 --- a/src/core/crypto/ctr_encryption_layer.h +++ b/src/core/crypto/ctr_encryption_layer.h @@ -4,7 +4,8 @@ #pragma once -#include <vector> +#include <array> + #include "core/crypto/aes_util.h" #include "core/crypto/encryption_layer.h" #include "core/crypto/key_manager.h" @@ -14,18 +15,20 @@ namespace Core::Crypto { // Sits on top of a VirtualFile and provides CTR-mode AES decription. class CTREncryptionLayer : public EncryptionLayer { public: + using IVData = std::array<u8, 16>; + CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, std::size_t base_offset); std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; - void SetIV(const std::vector<u8>& iv); + void SetIV(const IVData& iv); private: std::size_t base_offset; // Must be mutable as operations modify cipher contexts. mutable AESCipher<Key128> cipher; - mutable std::vector<u8> iv; + mutable IVData iv{}; void UpdateIV(std::size_t offset) const; }; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index f87fe0abc..65d246050 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" @@ -36,18 +35,86 @@ #include "core/settings.h" namespace Core::Crypto { +namespace { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; constexpr u64 FULL_TICKET_SIZE = 0x400; -using namespace Common; +using Common::AsArray; -const std::array<SHA256Hash, 2> eticket_source_hashes{ - "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source - "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source +// clang-format off +constexpr std::array eticket_source_hashes{ + AsArray("B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"), // eticket_rsa_kek_source + AsArray("E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"), // eticket_rsa_kekek_source }; +// clang-format on -const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ +constexpr std::array<std::pair<std::string_view, KeyIndex<S128KeyType>>, 30> s128_file_id{{ + {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, + {"eticket_rsa_kek_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}}, + {"eticket_rsa_kekek_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}}, + {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}}, + {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}}, + {"rsa_oaep_kek_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}}, + {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}}, + {"aes_kek_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}}, + {"aes_key_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}}, + {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}}, + {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}}, + {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}}, + {"key_area_key_application_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Application)}}, + {"key_area_key_ocean_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Ocean)}}, + {"key_area_key_system_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::System)}}, + {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}}, + {"keyblob_mac_key_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}}, + {"tsec_key", {S128KeyType::TSEC, 0, 0}}, + {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}}, + {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, + {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}}, + {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, + {"sd_card_kek", {S128KeyType::SDKek, 0, 0}}, +}}; + +auto Find128ByName(std::string_view name) { + return std::find_if(s128_file_id.begin(), s128_file_id.end(), + [&name](const auto& pair) { return pair.first == name; }); +} + +constexpr std::array<std::pair<std::string_view, KeyIndex<S256KeyType>>, 6> s256_file_id{{ + {"header_key", {S256KeyType::Header, 0, 0}}, + {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}}, + {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}}, + {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, + {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}}, + {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}}, +}}; + +auto Find256ByName(std::string_view name) { + return std::find_if(s256_file_id.begin(), s256_file_id.end(), + [&name](const auto& pair) { return pair.first == name; }); +} + +using KeyArray = std::array<std::pair<std::pair<S128KeyType, u64>, std::string_view>, 7>; +constexpr KeyArray KEYS_VARIABLE_LENGTH{{ {{S128KeyType::Master, 0}, "master_key_"}, {{S128KeyType::Package1, 0}, "package1_key_"}, {{S128KeyType::Package2, 0}, "package2_key_"}, @@ -55,14 +122,13 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, -}; +}}; -namespace { template <std::size_t Size> bool IsAllZeroArray(const std::array<u8, Size>& array) { return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); } -} // namespace +} // Anonymous namespace u64 GetSignatureTypeDataSize(SignatureType type) { switch (type) { @@ -94,13 +160,13 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { } SignatureType Ticket::GetSignatureType() const { - if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { return ticket->sig_type; } - if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) { return ticket->sig_type; } - if (auto ticket = std::get_if<ECDSATicket>(&data)) { + if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->sig_type; } @@ -108,13 +174,13 @@ SignatureType Ticket::GetSignatureType() const { } TicketData& Ticket::GetData() { - if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + if (auto* ticket = std::get_if<RSA4096Ticket>(&data)) { return ticket->data; } - if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + if (auto* ticket = std::get_if<RSA2048Ticket>(&data)) { return ticket->data; } - if (auto ticket = std::get_if<ECDSATicket>(&data)) { + if (auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->data; } @@ -122,13 +188,13 @@ TicketData& Ticket::GetData() { } const TicketData& Ticket::GetData() const { - if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { return ticket->data; } - if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) { return ticket->data; } - if (auto ticket = std::get_if<ECDSATicket>(&data)) { + if (const auto* ticket = std::get_if<ECDSATicket>(&data)) { return ticket->data; } @@ -231,8 +297,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { - if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) + if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { return {}; + } const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); @@ -259,27 +326,30 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) } std::optional<Key128> DeriveSDSeed() { - const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "/system/save/8000000000000043", - "rb+"); - if (!save_43.IsOpen()) - return {}; + const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + + "/system/save/8000000000000043", + "rb+"); + if (!save_43.IsOpen()) { + return std::nullopt; + } - const FileUtil::IOFile sd_private( - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); - if (!sd_private.IsOpen()) - return {}; + const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + + "/Nintendo/Contents/private", + "rb+"); + if (!sd_private.IsOpen()) { + return std::nullopt; + } std::array<u8, 0x10> private_seed{}; if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { - return {}; + return std::nullopt; } std::array<u8, 0x10> buffer{}; std::size_t offset = 0; for (; offset + 0x10 < save_43.GetSize(); ++offset) { if (!save_43.Seek(offset, SEEK_SET)) { - return {}; + return std::nullopt; } save_43.ReadBytes(buffer.data(), buffer.size()); @@ -289,23 +359,26 @@ std::optional<Key128> DeriveSDSeed() { } if (!save_43.Seek(offset + 0x10, SEEK_SET)) { - return {}; + return std::nullopt; } Key128 seed{}; if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { - return {}; + return std::nullopt; } return seed; } Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) { - if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) { return Loader::ResultStatus::ErrorMissingSDKEKSource; - if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) + } + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) { return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; - if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) + } + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) { return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; + } const auto sd_kek_source = keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)); @@ -318,14 +391,17 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); keys.SetKey(S128KeyType::SDKek, sd_kek); - if (!keys.HasKey(S128KeyType::SDSeed)) + if (!keys.HasKey(S128KeyType::SDSeed)) { return Loader::ResultStatus::ErrorMissingSDSeed; + } const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); - if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) { return Loader::ResultStatus::ErrorMissingSDSaveKeySource; - if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) + } + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) { return Loader::ResultStatus::ErrorMissingSDNCAKeySource; + } std::array<Key256, 2> sd_key_sources{ keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)), @@ -334,8 +410,9 @@ 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) + for (std::size_t i = 0; i < source.size(); ++i) { source[i] ^= sd_seed[i & 0xF]; + } } AESCipher<Key128> cipher(sd_kek, Mode::ECB); @@ -353,9 +430,10 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke return Loader::ResultStatus::Success; } -std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { - if (!ticket_save.IsOpen()) +std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { + if (!ticket_save.IsOpen()) { return {}; + } std::vector<u8> buffer(ticket_save.GetSize()); if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { @@ -415,7 +493,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { offset = i + 1; break; } else if (data[i] != 0x0) { - return {}; + return std::nullopt; } } @@ -425,16 +503,18 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { const auto issuer = ticket.GetData().issuer; - if (IsAllZeroArray(issuer)) - return {}; + if (IsAllZeroArray(issuer)) { + return std::nullopt; + } if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); } Key128 rights_id = ticket.GetData().rights_id; - if (rights_id == Key128{}) - return {}; + if (rights_id == Key128{}) { + return std::nullopt; + } if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { @@ -466,15 +546,17 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, std::array<u8, 0xDF> m_2; std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); - if (m_0 != 0) - return {}; + if (m_0 != 0) { + return std::nullopt; + } m_1 = m_1 ^ MGF1<0x20>(m_2); m_2 = m_2 ^ MGF1<0xDF>(m_1); const auto offset = FindTicketOffset(m_2); - if (!offset) - return {}; + if (!offset) { + return std::nullopt; + } ASSERT(*offset > 0); Key128 key_temp{}; @@ -485,8 +567,8 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, KeyManager::KeyManager() { // Initialize keys - const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); - const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); + const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); @@ -504,34 +586,39 @@ KeyManager::KeyManager() { } static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { - if (base.size() < begin + length) + if (base.size() < begin + length) { return false; + } return std::all_of(base.begin() + begin, base.begin() + begin + length, [](u8 c) { return std::isxdigit(c); }); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { std::ifstream file; - OpenFStream(file, filename, std::ios_base::in); - if (!file.is_open()) + Common::FS::OpenFStream(file, filename, std::ios_base::in); + if (!file.is_open()) { return; + } std::string line; while (std::getline(file, line)) { std::vector<std::string> out; std::stringstream stream(line); std::string item; - while (std::getline(stream, item, '=')) + while (std::getline(stream, item, '=')) { out.push_back(std::move(item)); + } - if (out.size() != 2) + if (out.size() != 2) { continue; + } out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); - if (out[0].compare(0, 1, "#") == 0) + if (out[0].compare(0, 1, "#") == 0) { continue; + } if (is_title_keys) { auto rights_id_raw = Common::HexStringToArray<16>(out[0]); @@ -541,24 +628,26 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; } else { out[0] = Common::ToLower(out[0]); - if (s128_file_id.find(out[0]) != s128_file_id.end()) { - const auto index = s128_file_id.at(out[0]); - Key128 key = Common::HexStringToArray<16>(out[1]); + if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) { + const auto& index = iter128->second; + const Key128 key = Common::HexStringToArray<16>(out[1]); s128_keys[{index.type, index.field1, index.field2}] = key; - } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { - const auto index = s256_file_id.at(out[0]); - Key256 key = Common::HexStringToArray<32>(out[1]); + } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) { + const auto& index = iter256->second; + const Key256 key = Common::HexStringToArray<32>(out[1]); s256_keys[{index.type, index.field1, index.field2}] = key; } else if (out[0].compare(0, 8, "keyblob_") == 0 && out[0].compare(0, 9, "keyblob_k") != 0) { - if (!ValidCryptoRevisionString(out[0], 8, 2)) + if (!ValidCryptoRevisionString(out[0], 8, 2)) { continue; + } const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { - if (!ValidCryptoRevisionString(out[0], 18, 2)) + if (!ValidCryptoRevisionString(out[0], 18, 2)) { continue; + } const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); @@ -566,8 +655,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { eticket_extended_kek = Common::HexStringToArray<576>(out[1]); } else { for (const auto& kv : KEYS_VARIABLE_LENGTH) { - if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) + if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { continue; + } if (out[0].compare(0, kv.second.size(), kv.second) == 0) { const auto index = std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); @@ -602,10 +692,11 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title) { - if (FileUtil::Exists(dir1 + DIR_SEP + filename)) + if (Common::FS::Exists(dir1 + DIR_SEP + filename)) { LoadFromFile(dir1 + DIR_SEP + filename, title); - else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) + } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) { LoadFromFile(dir2 + DIR_SEP + filename, title); + } } bool KeyManager::BaseDeriveNecessary() const { @@ -613,8 +704,9 @@ bool KeyManager::BaseDeriveNecessary() const { return !HasKey(key_type, index1, index2); }; - if (check_key_existence(S256KeyType::Header)) + if (check_key_existence(S256KeyType::Header)) { return true; + } for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) { if (check_key_existence(S128KeyType::Master, i) || @@ -639,14 +731,16 @@ bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { } Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { - if (!HasKey(id, field1, field2)) + if (!HasKey(id, field1, field2)) { return {}; + } return s128_keys.at({id, field1, field2}); } Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { - if (!HasKey(id, field1, field2)) + if (!HasKey(id, field1, field2)) { return {}; + } return s256_keys.at({id, field1, field2}); } @@ -668,7 +762,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { template <size_t Size> void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, const std::array<u8, Size>& key) { - const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); std::string filename = "title.keys_autogenerated"; if (category == KeyCategory::Standard) { filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; @@ -677,9 +771,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, } const auto path = yuzu_keys_dir + DIR_SEP + filename; - const auto add_info_text = !FileUtil::Exists(path); - FileUtil::CreateFullPath(path); - FileUtil::IOFile file{path, "a"}; + const auto add_info_text = !Common::FS::Exists(path); + Common::FS::CreateFullPath(path); + Common::FS::IOFile file{path, "a"}; if (!file.IsOpen()) { return; } @@ -712,8 +806,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { } const auto iter2 = std::find_if( - s128_file_id.begin(), s128_file_id.end(), - [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { + s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) { return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); }); @@ -723,9 +816,11 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { // Variable cases if (id == S128KeyType::KeyArea) { - static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}", - "key_area_key_ocean_{:02X}", - "key_area_key_system_{:02X}"}; + static constexpr std::array<const char*, 3> kak_names = { + "key_area_key_application_{:02X}", + "key_area_key_ocean_{:02X}", + "key_area_key_system_{:02X}", + }; WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key); } else if (id == S128KeyType::Master) { WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key); @@ -751,8 +846,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { return; } const auto iter = std::find_if( - s256_file_id.begin(), s256_file_id.end(), - [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { + s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) { return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); }); @@ -763,29 +857,31 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { } bool KeyManager::KeyFileExists(bool title) { - const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); - const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); + const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); if (title) { - return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || - FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); + return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || + Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); } if (Settings::values.use_dev_keys) { - return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || - FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); + return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || + Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); } - return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || - FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); + return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || + Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); } void KeyManager::DeriveSDSeedLazy() { - if (HasKey(S128KeyType::SDSeed)) + if (HasKey(S128KeyType::SDSeed)) { return; + } const auto res = DeriveSDSeed(); - if (res) + if (res) { SetKey(S128KeyType::SDSeed, *res); + } } static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { @@ -797,11 +893,13 @@ static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { } void KeyManager::DeriveBase() { - if (!BaseDeriveNecessary()) + if (!BaseDeriveNecessary()) { return; + } - if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) + if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) { return; + } const auto has_bis = [this](u64 id) { return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) && @@ -818,10 +916,11 @@ void KeyManager::DeriveBase() { static_cast<u64>(BISKeyType::Tweak)); }; - if (has_bis(2) && !has_bis(3)) + if (has_bis(2) && !has_bis(3)) { copy_bis(2, 3); - else if (has_bis(3) && !has_bis(2)) + } else if (has_bis(3) && !has_bis(2)) { copy_bis(3, 2); + } std::bitset<32> revisions(0xFFFFFFFF); for (size_t i = 0; i < revisions.size(); ++i) { @@ -831,15 +930,17 @@ void KeyManager::DeriveBase() { } } - if (!revisions.any()) + if (!revisions.any()) { return; + } const auto sbk = GetKey(S128KeyType::SecureBoot); const auto tsec = GetKey(S128KeyType::TSEC); for (size_t i = 0; i < revisions.size(); ++i) { - if (!revisions[i]) + if (!revisions[i]) { continue; + } // Derive keyblob key const auto key = DeriveKeyblobKey( @@ -848,16 +949,18 @@ void KeyManager::DeriveBase() { SetKey(S128KeyType::Keyblob, key, i); // Derive keyblob MAC key - if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) + if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) { continue; + } const auto mac_key = DeriveKeyblobMACKey( key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))); SetKey(S128KeyType::KeyblobMAC, mac_key, i); Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key); - if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) + if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) { continue; + } // Decrypt keyblob if (keyblobs[i] == std::array<u8, 0x90>{}) { @@ -881,16 +984,19 @@ void KeyManager::DeriveBase() { revisions.set(); for (size_t i = 0; i < revisions.size(); ++i) { - if (!HasKey(S128KeyType::Master, i)) + if (!HasKey(S128KeyType::Master, i)) { revisions.reset(i); + } } - if (!revisions.any()) + if (!revisions.any()) { return; + } for (size_t i = 0; i < revisions.size(); ++i) { - if (!revisions[i]) + if (!revisions[i]) { continue; + } // Derive general purpose keys DeriveGeneralPurposeKeys(i); @@ -915,21 +1021,24 @@ 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) + if (es == nullptr) { return; + } const auto exefs = es->GetExeFS(); - if (exefs == nullptr) + if (exefs == nullptr) { return; + } const auto main = exefs->GetFile("main"); - if (main == nullptr) + if (main == nullptr) { return; + } const auto bytes = main->ReadAllBytes(); @@ -939,16 +1048,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { const auto seed3 = data.GetRSAKekSeed3(); const auto mask0 = data.GetRSAKekMask0(); - if (eticket_kek != Key128{}) + if (eticket_kek != Key128{}) { SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek)); + } if (eticket_kekek != Key128{}) { SetKey(S128KeyType::Source, eticket_kekek, static_cast<size_t>(SourceKeyType::ETicketKekek)); } - if (seed3 != Key128{}) + if (seed3 != Key128{}) { SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); - if (mask0 != Key128{}) + } + if (mask0 != Key128{}) { SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0)); + } if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || mask0 == Key128{}) { return; @@ -974,8 +1086,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { AESCipher<Key128> es_kek(temp_kekek, Mode::ECB); es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt); - if (eticket_final == Key128{}) + if (eticket_final == Key128{}) { return; + } SetKey(S128KeyType::ETicketRSAKek, eticket_final); @@ -990,18 +1103,20 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { void KeyManager::PopulateTickets() { const auto rsa_key = GetETicketRSAKey(); - if (rsa_key == RSAKeyPair<2048>{}) + if (rsa_key == RSAKeyPair<2048>{}) { return; + } - if (!common_tickets.empty() && !personal_tickets.empty()) + if (!common_tickets.empty() && !personal_tickets.empty()) { return; + } - const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "/system/save/80000000000000e1", - "rb+"); - const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "/system/save/80000000000000e2", - "rb+"); + const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + + "/system/save/80000000000000e1", + "rb+"); + const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + + "/system/save/80000000000000e2", + "rb+"); const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); @@ -1011,8 +1126,10 @@ void KeyManager::PopulateTickets() { for (std::size_t i = 0; i < res.size(); ++i) { const auto common = i < idx; const auto pair = ParseTicket(res[i], rsa_key); - if (!pair) + if (!pair) { continue; + } + const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); @@ -1041,27 +1158,33 @@ void KeyManager::SynthesizeTickets() { } void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { - if (key == Key128{}) + if (key == Key128{}) { return; + } SetKey(id, key, field1, field2); } void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { - if (key == Key256{}) + if (key == Key256{}) { return; + } + SetKey(id, key, field1, field2); } void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { - if (!BaseDeriveNecessary()) + if (!BaseDeriveNecessary()) { return; + } - if (!data.HasBoot0()) + if (!data.HasBoot0()) { return; + } for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { - if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) + if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) { continue; + } encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), encrypted_keyblobs[i]); @@ -1083,8 +1206,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { static_cast<u64>(SourceKeyType::Keyblob), i); } - if (data.HasFuses()) + if (data.HasFuses()) { SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); + } DeriveBase(); @@ -1098,8 +1222,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { const auto masters = data.GetTZMasterKeys(latest_master); for (size_t i = 0; i < masters.size(); ++i) { - if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) + if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) { SetKey(S128KeyType::Master, masters[i], i); + } } DeriveBase(); @@ -1109,8 +1234,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { std::array<Key128, 0x20> package2_keys{}; for (size_t i = 0; i < package2_keys.size(); ++i) { - if (HasKey(S128KeyType::Package2, i)) + if (HasKey(S128KeyType::Package2, i)) { package2_keys[i] = GetKey(S128KeyType::Package2, i); + } } data.DecryptPackage2(package2_keys, Package2Type::NormalMain); @@ -1148,12 +1274,15 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { bool KeyManager::AddTicketCommon(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); - if (rsa_key == RSAKeyPair<2048>{}) + if (rsa_key == RSAKeyPair<2048>{}) { return false; + } const auto pair = ParseTicket(raw, rsa_key); - if (!pair) + if (!pair) { return false; + } + const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); @@ -1164,12 +1293,15 @@ bool KeyManager::AddTicketCommon(Ticket raw) { bool KeyManager::AddTicketPersonalized(Ticket raw) { const auto rsa_key = GetETicketRSAKey(); - if (rsa_key == RSAKeyPair<2048>{}) + if (rsa_key == RSAKeyPair<2048>{}) { return false; + } const auto pair = ParseTicket(raw, rsa_key); - if (!pair) + if (!pair) { return false; + } + const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); @@ -1177,58 +1309,4 @@ bool KeyManager::AddTicketPersonalized(Ticket raw) { SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); return true; } - -const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { - {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, - {"eticket_rsa_kek_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}}, - {"eticket_rsa_kekek_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}}, - {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}}, - {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}}, - {"rsa_oaep_kek_generation_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}}, - {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}}, - {"aes_kek_generation_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}}, - {"aes_key_generation_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}}, - {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}}, - {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}}, - {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}}, - {"key_area_key_application_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), - static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_ocean_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), - static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_system_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), - static_cast<u64>(KeyAreaKeyType::System)}}, - {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}}, - {"keyblob_mac_key_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}}, - {"tsec_key", {S128KeyType::TSEC, 0, 0}}, - {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}}, - {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, - {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}}, - {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}}, - {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}}, - {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}}, - {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}}, - {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}}, - {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}}, - {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}}, - {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, - {"sd_card_kek", {S128KeyType::SDKek, 0, 0}}, -}; - -const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { - {"header_key", {S256KeyType::Header, 0, 0}}, - {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}}, - {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}}, - {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, - {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}}, - {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}}, -}; } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 9269a73f2..0a7220286 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -10,17 +10,20 @@ #include <string> #include <variant> -#include <boost/container/flat_map.hpp> #include <fmt/format.h> #include "common/common_funcs.h" #include "common/common_types.h" #include "core/crypto/partition_data_manager.h" #include "core/file_sys/vfs_types.h" -namespace FileUtil { +namespace Common::FS { class IOFile; } +namespace FileSys { +class ContentProvider; +} + namespace Loader { enum class ResultStatus : u16; } @@ -253,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(); @@ -293,9 +296,6 @@ private: void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); - - static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id; - static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id; }; Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); @@ -308,7 +308,7 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo std::optional<Key128> DeriveSDSeed(); Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); -std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save); +std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority // (offset 0x140-0x144 is zero) diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index 7ed71ac3a..5f1c86a09 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -26,8 +26,9 @@ #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 namespace Common; +using Common::AsArray; namespace Core::Crypto { @@ -47,105 +48,123 @@ struct Package2Header { }; static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size."); -const std::array<SHA256Hash, 0x10> source_hashes{ - "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source - "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source - "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source - "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source - "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source - "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source - "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source - "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source - "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source - "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source - "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source - "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source - "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source - "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source - "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3 - "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0 +// clang-format off +constexpr std::array source_hashes{ + AsArray("B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"), // keyblob_mac_key_source + AsArray("7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"), // master_key_source + AsArray("21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"), // package2_key_source + AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // aes_kek_generation_source + AsArray("FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"), // aes_key_generation_source + AsArray("C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"), // titlekek_source + AsArray("04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"), // key_area_key_application_source + AsArray("FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"), // key_area_key_ocean_source + AsArray("1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"), // key_area_key_system_source + AsArray("6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"), // sd_card_kek_source + AsArray("D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"), // sd_card_save_key_source + AsArray("2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"), // sd_card_nca_key_source + AsArray("1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"), // header_kek_source + AsArray("8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"), // header_key_source + AsArray("D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"), // rsa_kek_seed3 + AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // rsa_kek_mask0 }; - -const std::array<SHA256Hash, 0x20> keyblob_source_hashes{ - "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00 - "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01 - "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02 - "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03 - "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04 - "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07 - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17 - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F +// clang-format on + +// clang-format off +constexpr std::array keyblob_source_hashes{ + AsArray("8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"), // keyblob_key_source_00 + AsArray("2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"), // keyblob_key_source_01 + AsArray("61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"), // keyblob_key_source_02 + AsArray("8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"), // keyblob_key_source_03 + AsArray("95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"), // keyblob_key_source_04 + AsArray("3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"), // keyblob_key_source_05 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_06 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_07 + + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_08 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_09 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0A + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0B + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0C + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0D + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0E + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0F + + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_10 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_11 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_12 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_13 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_14 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_15 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_16 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_17 + + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_18 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_19 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1A + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1B + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1C + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1D + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1E + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1F }; - -const std::array<SHA256Hash, 0x20> master_key_hashes{ - "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00 - "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01 - "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02 - "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03 - "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04 - "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07 - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17 - - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19 - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E - "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F +// clang-format on + +// clang-format off +constexpr std::array master_key_hashes{ + AsArray("0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"), // master_key_00 + AsArray("4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"), // master_key_01 + AsArray("79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"), // master_key_02 + AsArray("4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"), // master_key_03 + AsArray("75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"), // master_key_04 + AsArray("EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"), // master_key_05 + AsArray("9497E6779F5D840F2BBA1DE4E95BA1D6F21EFC94717D5AE5CA37D7EC5BD37A19"), // master_key_06 + AsArray("4EC96B8CB01B8DCE382149443430B2B6EBCB2983348AFA04A25E53609DABEDF6"), // master_key_07 + + AsArray("2998E2E23609BC2675FF062A2D64AF5B1B78DFF463B24119D64A1B64F01B2D51"), // master_key_08 + AsArray("9D486A98067C44B37CF173D3BF577891EB6081FF6B4A166347D9DBBF7025076B"), // master_key_09 + AsArray("4EC5A237A75A083A9C5F6CF615601522A7F822D06BD4BA32612C9CEBBB29BD45"), // master_key_0A + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0B + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0C + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0D + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0E + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0F + + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_10 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_11 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_12 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_13 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_14 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_15 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_16 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_17 + + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_18 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_19 + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1A + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1B + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1C + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1D + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1E + AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1F }; +// clang-format on + +static constexpr u8 CalculateMaxKeyblobSourceHash() { + const auto is_zero = [](const auto& data) { + // TODO: Replace with std::all_of whenever mingw decides to update their + // libraries to include the constexpr variant of it. + for (const auto element : data) { + if (element != 0) { + return false; + } + } + return true; + }; -static u8 CalculateMaxKeyblobSourceHash() { for (s8 i = 0x1F; i >= 0; --i) { - if (keyblob_source_hashes[i] != SHA256Hash{}) + if (!is_zero(keyblob_source_hashes[i])) { return static_cast<u8>(i + 1); + } } return 0; @@ -346,12 +365,11 @@ FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) con } static bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) { - const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end()); Package2Header temp = header; AESCipher<Key128> cipher(key, Mode::CTR); - cipher.SetIV(iv); - cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr, - Op::Decrypt); + cipher.SetIV(header.header_ctr); + cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - sizeof(Package2Header::signature), + &temp.header_ctr, Op::Decrypt); if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { header = temp; return true; @@ -388,7 +406,7 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa auto c = a->ReadAllBytes(); AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR); - cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()}); + cipher.SetIV(header.section_ctr[1]); cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c); diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp index 51097ced3..0c4b440ed 100644 --- a/src/core/device_memory.cpp +++ b/src/core/device_memory.cpp @@ -2,14 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/core.h" #include "core/device_memory.h" -#include "core/memory.h" namespace Core { -DeviceMemory::DeviceMemory(System& system) : buffer{DramMemoryMap::Size}, system{system} {} - +DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {} DeviceMemory::~DeviceMemory() = default; } // namespace Core diff --git a/src/core/device_memory.h b/src/core/device_memory.h index 9efa088d0..5b1ae28f3 100644 --- a/src/core/device_memory.h +++ b/src/core/device_memory.h @@ -4,14 +4,11 @@ #pragma once -#include "common/assert.h" -#include "common/common_funcs.h" +#include "common/common_types.h" #include "common/virtual_buffer.h" namespace Core { -class System; - namespace DramMemoryMap { enum : u64 { Base = 0x80000000ULL, @@ -26,7 +23,7 @@ enum : u64 { class DeviceMemory : NonCopyable { public: - explicit DeviceMemory(Core::System& system); + explicit DeviceMemory(); ~DeviceMemory(); template <typename T> @@ -45,7 +42,6 @@ public: private: Common::VirtualBuffer<u8> buffer; - Core::System& system; }; } // namespace Core diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 285277ef8..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( - FileUtil::GetUserPath(FileUtil::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..956da68f7 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" @@ -31,7 +31,8 @@ constexpr std::array partition_names{ XCI::XCI(VirtualFile file_) : 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; diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index e1b136426..2d0a0f285 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; } @@ -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/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 473245d5a..76af47ff9 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" @@ -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; @@ -441,18 +442,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 +477,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(); @@ -495,9 +496,10 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, starting_offset); - std::vector<u8> iv(16); - for (u8 i = 0; i < 8; ++i) - iv[i] = s_header.raw.section_ctr[0x8 - i - 1]; + Core::Crypto::CTREncryptionLayer::IVData iv{}; + for (std::size_t i = 0; i < 8; ++i) { + iv[i] = s_header.raw.section_ctr[8 - i - 1]; + } out->SetIV(iv); return std::static_pointer_cast<VfsFile>(out); } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index d25cbcf91..69292232a 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -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/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index a08a70efd..dd779310f 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")) { 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/mode.h b/src/core/file_sys/mode.h index c95205668..2b4f21073 100644 --- a/src/core/file_sys/mode.h +++ b/src/core/file_sys/mode.h @@ -4,6 +4,7 @@ #pragma once +#include "common/common_funcs.h" #include "common/common_types.h" namespace FileSys { @@ -11,13 +12,11 @@ namespace FileSys { enum class Mode : u32 { Read = 1, Write = 2, - ReadWrite = 3, + ReadWrite = Read | Write, Append = 4, - WriteAppend = 6, + WriteAppend = Write | Append, }; -inline u32 operator&(Mode lhs, Mode rhs) { - return static_cast<u32>(lhs) & static_cast<u32>(rhs); -} +DECLARE_ENUM_FLAG_OPERATORS(Mode) } // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 93d0df6b9..2d1476e3a 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 { 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 0090cc6c4..5990a2fd5 100644 --- a/src/core/file_sys/nca_patch.cpp +++ b/src/core/file_sys/nca_patch.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <cstddef> #include <cstring> @@ -11,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."); +} +} // Anonymous namespace BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, @@ -66,7 +110,7 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); // Calculate AES IV - std::vector<u8> iv(16); + std::array<u8, 16> iv{}; auto subsection_ctr = subsection.ctr; auto offset_iv = section_offset + base_offset; for (std::size_t i = 0; i < section_ctr.size(); ++i) @@ -109,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]; diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h index 8e64e8378..60c544f8e 100644 --- a/src/core/file_sys/nca_patch.h +++ b/src/core/file_sys/nca_patch.h @@ -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 c47ff863e..b9c09b456 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -27,6 +27,7 @@ #include "core/settings.h" namespace FileSys { +namespace { constexpr u64 SINGLE_BYTE_MODULUS = 0x100; constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; @@ -36,21 +37,29 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ "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,6 +75,43 @@ std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<Vfs #endif } +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) : title_id(title_id) {} PatchManager::~PatchManager() = default; @@ -246,7 +292,7 @@ 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); @@ -266,36 +312,8 @@ 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* const>(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 Core::System& system, const BuildID& build_id_) const { const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); @@ -315,14 +333,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)); } } @@ -436,21 +452,11 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content 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]; @@ -473,8 +479,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam 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"); @@ -563,40 +568,46 @@ std::optional<u32> PatchManager::GetGameVersion() const { return installed.GetEntryVersion(title_id); } -std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { +PatchManager::Metadata 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) + 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..1f28c6241 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -6,10 +6,11 @@ #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 { @@ -21,71 +22,62 @@ namespace FileSys { 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: + 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); ~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 Core::System& system, 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; }; 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 37351c561..da01002d5 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -257,8 +257,7 @@ std::vector<NcaID> PlaceholderCache::List() const { for (const auto& sdir : dir->GetSubdirectories()) { for (const auto& file : sdir->GetFiles()) { const auto name = file->GetName(); - if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' && - name[35] == 'a') { + if (name.length() == 36 && name.ends_with(".nca")) { out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32))); } } @@ -344,15 +343,18 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { - if (map.find(title_id) == map.end()) - return {}; - - const auto& cnmt = map.at(title_id); + const auto cmnt_iter = map.find(title_id); + if (cmnt_iter == map.cend()) { + return std::nullopt; + } - const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(), + const auto& cnmt = cmnt_iter->second; + const auto& content_records = cnmt.GetContentRecords(); + const auto iter = std::find_if(content_records.cbegin(), content_records.cend(), [type](const ContentRecord& rec) { return rec.type == type; }); - if (iter == cnmt.GetContentRecords().end()) - return {}; + if (iter == content_records.cend()) { + return std::nullopt; + } return std::make_optional(iter->nca_id); } @@ -467,14 +469,16 @@ VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType ty std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const { const auto meta_iter = meta.find(title_id); - if (meta_iter != meta.end()) + if (meta_iter != meta.cend()) { return meta_iter->second.GetTitleVersion(); + } const auto yuzu_meta_iter = yuzu_meta.find(title_id); - if (yuzu_meta_iter != yuzu_meta.end()) + if (yuzu_meta_iter != yuzu_meta.cend()) { return yuzu_meta_iter->second.GetTitleVersion(); + } - return {}; + return std::nullopt; } VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { @@ -547,56 +551,6 @@ InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_ex return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); } -bool RegisteredCache::RemoveExistingEntry(u64 title_id) { - const auto delete_nca = [this](const NcaID& id) { - const auto path = GetRelativePathFromNcaID(id, false, true, false); - - if (dir->GetFileRelative(path) == nullptr) { - return false; - } - - Core::Crypto::SHA256Hash hash{}; - mbedtls_sha256_ret(id.data(), id.size(), hash.data(), 0); - const auto dirname = fmt::format("000000{:02X}", hash[0]); - - const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); - - const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); - - return res; - }; - - // If an entry exists in the registered cache, remove it - if (HasEntry(title_id, ContentRecordType::Meta)) { - LOG_INFO(Loader, - "Previously installed entry (v{}) for title_id={:016X} detected! " - "Attempting to remove...", - GetEntryVersion(title_id).value_or(0), title_id); - // Get all the ncas associated with the current CNMT and delete them - const auto meta_old_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); - const auto program_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); - const auto data_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); - const auto control_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); - const auto html_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); - const auto legal_id = - GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); - - delete_nca(meta_old_id); - delete_nca(program_id); - delete_nca(data_id); - delete_nca(control_id); - delete_nca(html_id); - delete_nca(legal_id); - return true; - } - return false; -} - InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, const VfsCopyFunction& copy) { const auto ncas = nsp.GetNCAsCollapsed(); @@ -666,25 +620,25 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists, const VfsCopyFunction& copy) { - CNMTHeader header{ - nca.GetTitleId(), // Title ID - 0, // Ignore/Default title version - type, // Type - {}, // Padding - 0x10, // Default table offset - 1, // 1 Content Entry - 0, // No Meta Entries - {}, // Padding - {}, // Reserved 1 - 0, // Is committed - 0, // Required download system version - {}, // Reserved 2 + const CNMTHeader header{ + .title_id = nca.GetTitleId(), + .title_version = 0, + .type = type, + .reserved = {}, + .table_offset = 0x10, + .number_content_entries = 1, + .number_meta_entries = 0, + .attributes = 0, + .reserved2 = {}, + .is_committed = 0, + .required_download_system_version = 0, + .reserved3 = {}, }; - OptionalHeader opt_header{0, 0}; + const OptionalHeader opt_header{0, 0}; ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}}; const auto& data = nca.GetBaseFile()->ReadBytes(0x100000); mbedtls_sha256_ret(data.data(), data.size(), c_rec.hash.data(), 0); - memcpy(&c_rec.nca_id, &c_rec.hash, 16); + std::memcpy(&c_rec.nca_id, &c_rec.hash, 16); const CNMT new_cnmt(header, opt_header, {c_rec}, {}); if (!RawInstallYuzuMeta(new_cnmt)) { return InstallResult::ErrorMetaFailed; @@ -692,6 +646,57 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } +bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { + const auto delete_nca = [this](const NcaID& id) { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + const bool isFile = dir->GetFileRelative(path) != nullptr; + const bool isDir = dir->GetDirectoryRelative(path) != nullptr; + + if (isFile) { + return dir->DeleteFile(path); + } else if (isDir) { + return dir->DeleteSubdirectoryRecursive(path); + } + + return false; + }; + + // If an entry exists in the registered cache, remove it + if (HasEntry(title_id, ContentRecordType::Meta)) { + LOG_INFO(Loader, + "Previously installed entry (v{}) for title_id={:016X} detected! " + "Attempting to remove...", + GetEntryVersion(title_id).value_or(0), title_id); + + // Get all the ncas associated with the current CNMT and delete them + const auto meta_old_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); + const auto program_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); + const auto data_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); + const auto control_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); + const auto html_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); + const auto legal_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); + + const auto deleted_meta = delete_nca(meta_old_id); + const auto deleted_program = delete_nca(program_id); + const auto deleted_data = delete_nca(data_id); + const auto deleted_control = delete_nca(control_id); + const auto deleted_html = delete_nca(html_id); + const auto deleted_legal = delete_nca(legal_id); + + return deleted_meta && (deleted_meta || deleted_program || deleted_data || + deleted_control || deleted_html || deleted_legal); + } + + return false; +} + InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional<NcaID> override_id) { @@ -722,7 +727,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti LOG_WARNING(Loader, "Overwriting existing NCA..."); VirtualDir c_dir; { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } - c_dir->DeleteFile(FileUtil::GetFilename(path)); + c_dir->DeleteFile(Common::FS::GetFilename(path)); } auto out = dir->CreateFileRelative(path); diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 29cf0d40c..5b414b0f0 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -133,9 +133,9 @@ public: // Parsing function defines the conversion from raw file to NCA. If there are other steps // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom // parsing function. - explicit RegisteredCache(VirtualDir dir, - ContentProviderParsingFunction parsing_function = - [](const VirtualFile& file, const NcaID& id) { return file; }); + explicit RegisteredCache( + VirtualDir dir, ContentProviderParsingFunction parsing_function = + [](const VirtualFile& file, const NcaID& id) { return file; }); ~RegisteredCache() override; void Refresh() override; @@ -155,9 +155,6 @@ public: std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, std::optional<u64> title_id = {}) const override; - // Removes an existing entry based on title id - bool RemoveExistingEntry(u64 title_id); - // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure // there is a meta NCA and all of them are accessible. InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, @@ -172,6 +169,9 @@ public: InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); + // Removes an existing entry based on title id + bool RemoveExistingEntry(u64 title_id) const; + private: template <typename T> void IterateAllMetadata(std::vector<T>& out, 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..e967a254e 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -6,7 +6,6 @@ #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/content_archive.h" #include "core/file_sys/nca_metadata.h" @@ -19,7 +18,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!"); @@ -46,39 +47,38 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_titl ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const { - std::shared_ptr<NCA> res; - - 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)); - } - + 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..ec704dfa8 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,26 @@ 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> 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 adfd2c1a4..ba4efee3a 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -17,23 +17,23 @@ constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size"; namespace { -void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) { +void PrintSaveDataAttributeWarnings(SaveDataAttribute meta) { if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { if (meta.zero_1 != 0) { LOG_WARNING(Service_FS, - "Possibly incorrect SaveDataDescriptor, type is " + "Possibly incorrect SaveDataAttribute, type is " "SystemSaveData||SaveData but offset 0x28 is non-zero ({:016X}).", meta.zero_1); } if (meta.zero_2 != 0) { LOG_WARNING(Service_FS, - "Possibly incorrect SaveDataDescriptor, type is " + "Possibly incorrect SaveDataAttribute, type is " "SystemSaveData||SaveData but offset 0x30 is non-zero ({:016X}).", meta.zero_2); } if (meta.zero_3 != 0) { LOG_WARNING(Service_FS, - "Possibly incorrect SaveDataDescriptor, type is " + "Possibly incorrect SaveDataAttribute, type is " "SystemSaveData||SaveData but offset 0x38 is non-zero ({:016X}).", meta.zero_3); } @@ -41,33 +41,32 @@ void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) { if (meta.type == SaveDataType::SystemSaveData && meta.title_id != 0) { LOG_WARNING(Service_FS, - "Possibly incorrect SaveDataDescriptor, type is SystemSaveData but title_id is " + "Possibly incorrect SaveDataAttribute, type is SystemSaveData but title_id is " "non-zero ({:016X}).", meta.title_id); } if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) { LOG_WARNING(Service_FS, - "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is " + "Possibly incorrect SaveDataAttribute, type is DeviceSaveData but user_id is " "non-zero ({:016X}{:016X})", meta.user_id[1], meta.user_id[0]); } } -bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataDescriptor& desc) { - return desc.type == SaveDataType::CacheStorage || desc.type == SaveDataType::TemporaryStorage || +bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataAttribute& attr) { + return attr.type == SaveDataType::CacheStorage || attr.type == SaveDataType::TemporaryStorage || (space == SaveDataSpaceId::NandUser && ///< Normal Save Data -- Current Title & User - (desc.type == SaveDataType::SaveData || desc.type == SaveDataType::DeviceSaveData) && - desc.title_id == 0 && desc.save_id == 0); + (attr.type == SaveDataType::SaveData || attr.type == SaveDataType::DeviceSaveData) && + attr.title_id == 0 && attr.save_id == 0); } } // Anonymous namespace -std::string SaveDataDescriptor::DebugInfo() const { - return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, " - "save_id={:016X}, " +std::string SaveDataAttribute::DebugInfo() const { + return fmt::format("[title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, type={:02X}, " "rank={}, index={}]", - static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id, + title_id, user_id[1], user_id[0], save_id, static_cast<u8>(type), static_cast<u8>(rank), index); } @@ -80,8 +79,8 @@ SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save SaveDataFactory::~SaveDataFactory() = default; ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, - const SaveDataDescriptor& meta) const { - PrintSaveDataDescriptorWarnings(meta); + const SaveDataAttribute& meta) const { + PrintSaveDataAttributeWarnings(meta); const auto save_directory = GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); @@ -98,7 +97,7 @@ ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, } ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, - const SaveDataDescriptor& meta) const { + const SaveDataAttribute& meta) const { const auto save_directory = GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 991e57aa1..6625bbbd8 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -21,6 +21,7 @@ enum class SaveDataSpaceId : u8 { TemporaryStorage = 3, SdCardUser = 4, ProperSystem = 100, + SafeMode = 101, }; enum class SaveDataType : u8 { @@ -30,28 +31,50 @@ enum class SaveDataType : u8 { DeviceSaveData = 3, TemporaryStorage = 4, CacheStorage = 5, + SystemBcat = 6, }; enum class SaveDataRank : u8 { - Primary, - Secondary, + Primary = 0, + Secondary = 1, }; -struct SaveDataDescriptor { - u64_le title_id; +enum class SaveDataFlags : u32 { + None = (0 << 0), + KeepAfterResettingSystemSaveData = (1 << 0), + KeepAfterRefurbishment = (1 << 1), + KeepAfterResettingSystemSaveDataWithoutUserSaveData = (1 << 2), + NeedsSecureDelete = (1 << 3), +}; + +struct SaveDataAttribute { + u64 title_id; u128 user_id; - u64_le save_id; + u64 save_id; SaveDataType type; SaveDataRank rank; - u16_le index; + u16 index; INSERT_PADDING_BYTES(4); - u64_le zero_1; - u64_le zero_2; - u64_le zero_3; + u64 zero_1; + u64 zero_2; + u64 zero_3; std::string DebugInfo() const; }; -static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size."); +static_assert(sizeof(SaveDataAttribute) == 0x40, "SaveDataAttribute has incorrect size."); + +struct SaveDataExtraData { + SaveDataAttribute attr; + u64 owner_id; + s64 timestamp; + SaveDataFlags flags; + INSERT_PADDING_BYTES(4); + s64 available_size; + s64 journal_size; + s64 commit_id; + std::array<u8, 0x190> unused; +}; +static_assert(sizeof(SaveDataExtraData) == 0x200, "SaveDataExtraData has incorrect size."); struct SaveDataSize { u64 normal; @@ -64,8 +87,8 @@ public: explicit SaveDataFactory(VirtualDir dir); ~SaveDataFactory(); - ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; - ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; + ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; + ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const; VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; 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 a6637fa39..90641d23b 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -22,7 +22,7 @@ namespace FileSys { NSP::NSP(VirtualFile file_) : file(std::move(file_)), status{Loader::ResultStatus::Success}, - pfs(std::make_shared<PartitionFilesystem>(file)) { + pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} { if (pfs->GetStatus() != Loader::ResultStatus::Success) { status = pfs->GetStatus(); return; @@ -264,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)); @@ -283,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 6d54bd807..c70a11b5b 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; } @@ -74,7 +78,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/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp index 61bb67945..d65c7d234 100644 --- a/src/core/file_sys/system_archive/mii_model.cpp +++ b/src/core/file_sys/system_archive/mii_model.cpp @@ -27,18 +27,12 @@ VirtualDir MiiModel() { auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, "data"); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>( - MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>( - MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>( - MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>( - MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>( - MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); - out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>( - MiiModelData::SHAPE_MID, "ShapeMid.dat")); + out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); + out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); + out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); + out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); + out->AddFile(MakeArrayFile(MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); + out->AddFile(MakeArrayFile(MiiModelData::SHAPE_MID, "ShapeMid.dat")); return out; } diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp index f4443784d..100d3c5db 100644 --- a/src/core/file_sys/system_archive/ng_word.cpp +++ b/src/core/file_sys/system_archive/ng_word.cpp @@ -24,19 +24,18 @@ constexpr std::array<u8, 30> WORD_TXT{ } // namespace NgWord1Data VirtualDir NgWord1() { - std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES); + std::vector<VirtualFile> files; + files.reserve(NgWord1Data::NUMBER_WORD_TXT_FILES); for (std::size_t i = 0; i < files.size(); ++i) { - files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>( - NgWord1Data::WORD_TXT, fmt::format("{}.txt", i)); + files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, fmt::format("{}.txt", i))); } - files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>( - NgWord1Data::WORD_TXT, "common.txt")); - files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>( - NgWord1Data::VERSION_DAT, "version.dat")); + files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, "common.txt")); + files.push_back(MakeArrayFile(NgWord1Data::VERSION_DAT, "version.dat")); - return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data"); + return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{}, + "data"); } namespace NgWord2Data { @@ -55,27 +54,22 @@ constexpr std::array<u8, 0x2C> AC_NX_DATA{ } // namespace NgWord2Data VirtualDir NgWord2() { - std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3); + std::vector<VirtualFile> files; + files.reserve(NgWord2Data::NUMBER_AC_NX_FILES * 3); for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) { - files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i)); - files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i)); - files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i)); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i))); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i))); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i))); } - files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, "ac_common_b1_nx")); - files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, "ac_common_b2_nx")); - files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( - NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx")); - files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>( - NgWord2Data::VERSION_DAT, "version.dat")); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b1_nx")); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b2_nx")); + files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx")); + files.push_back(MakeArrayFile(NgWord2Data::VERSION_DAT, "version.dat")); - return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data"); + return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{}, + "data"); } } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp index 9806bd197..8fd005012 100644 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <vector> + #include "common/swap.h" #include "core/file_sys/system_archive/time_zone_binary.h" #include "core/file_sys/vfs_vector.h" @@ -615,43 +618,49 @@ static constexpr std::array<u8, 9633> LOCATION_NAMES{ 0x0a}; static VirtualFile GenerateDefaultTimeZoneFile() { - struct { + struct TimeZoneInfo { s64_be at; - INSERT_PADDING_BYTES(7); + std::array<u8, 7> padding1; std::array<char, 4> time_zone_chars; - INSERT_PADDING_BYTES(2); + std::array<u8, 2> padding2; std::array<char, 6> time_zone_name; - } time_zone_info{}; + }; - const VirtualFile file{std::make_shared<VectorVfsFile>( - std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(time_zone_info)), + VirtualFile file{std::make_shared<VectorVfsFile>( + std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(TimeZoneInfo)), "GMT")}; - Service::Time::TimeZone::TzifHeader header{}; - header.magic = 0x545a6966; - header.version = 0x32; - header.ttis_gmt_count = 0x1; - header.ttis_std_count = 0x1; - header.time_count = 0x1; - header.type_count = 0x1; - header.char_count = 0x4; + const Service::Time::TimeZone::TzifHeader header{ + .magic = 0x545a6966, + .version = 0x32, + .ttis_gmt_count = 1, + .ttis_std_count = 1, + .time_count = 1, + .type_count = 1, + .char_count = 4, + }; file->WriteObject(header, 0); - time_zone_info.at = 0xf8; - time_zone_info.time_zone_chars = {'G', 'M', 'T', '\0'}; - time_zone_info.time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'}; + const TimeZoneInfo time_zone_info{ + .at = 0xf8, + .padding1 = {}, + .time_zone_chars = {'G', 'M', 'T', '\0'}, + .padding2 = {}, + .time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'}, + }; file->WriteObject(time_zone_info, sizeof(Service::Time::TimeZone::TzifHeader)); return file; } VirtualDir TimeZoneBinary() { - const std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>( + std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>( std::vector<VirtualFile>{GenerateDefaultTimeZoneFile()}, std::vector<VirtualDir>{}, "zoneinfo")}; - const std::vector<VirtualFile> root_files{ - std::make_shared<ArrayVfsFile<LOCATION_NAMES.size()>>(LOCATION_NAMES, "binaryList.txt")}; - return std::make_shared<VectorVfsDirectory>(root_files, root_dirs, "data"); + std::vector<VirtualFile> root_files{MakeArrayFile(LOCATION_NAMES, "binaryList.txt")}; + + return std::make_shared<VectorVfsDirectory>(std::move(root_files), std::move(root_dirs), + "data"); } } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index e33327ef0..b2f026b6d 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -30,7 +30,7 @@ bool VfsFilesystem::IsWritable() const { } VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const { - const auto path = FileUtil::SanitizePath(path_); + const auto path = Common::FS::SanitizePath(path_); if (root->GetFileRelative(path) != nullptr) return VfsEntryType::File; if (root->GetDirectoryRelative(path) != nullptr) @@ -40,22 +40,22 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const { } VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_); + const auto path = Common::FS::SanitizePath(path_); return root->GetFileRelative(path); } VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_); + const auto path = Common::FS::SanitizePath(path_); return root->CreateFileRelative(path); } VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = FileUtil::SanitizePath(old_path_); - const auto new_path = FileUtil::SanitizePath(new_path_); + const auto old_path = Common::FS::SanitizePath(old_path_); + const auto new_path = Common::FS::SanitizePath(new_path_); // VfsDirectory impls are only required to implement copy across the current directory. - if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) { - if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path))) + if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) { + if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path))) return nullptr; return OpenFile(new_path, Mode::ReadWrite); } @@ -76,8 +76,8 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view } VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) { - const auto sanitized_old_path = FileUtil::SanitizePath(old_path); - const auto sanitized_new_path = FileUtil::SanitizePath(new_path); + const auto sanitized_old_path = Common::FS::SanitizePath(old_path); + const auto sanitized_new_path = Common::FS::SanitizePath(new_path); // Again, non-default impls are highly encouraged to provide a more optimized version of this. auto out = CopyFile(sanitized_old_path, sanitized_new_path); @@ -89,26 +89,26 @@ VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view } bool VfsFilesystem::DeleteFile(std::string_view path_) { - const auto path = FileUtil::SanitizePath(path_); - auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); + const auto path = Common::FS::SanitizePath(path_); + auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write); if (parent == nullptr) return false; - return parent->DeleteFile(FileUtil::GetFilename(path)); + return parent->DeleteFile(Common::FS::GetFilename(path)); } VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_); + const auto path = Common::FS::SanitizePath(path_); return root->GetDirectoryRelative(path); } VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_); + const auto path = Common::FS::SanitizePath(path_); return root->CreateDirectoryRelative(path); } VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = FileUtil::SanitizePath(old_path_); - const auto new_path = FileUtil::SanitizePath(new_path_); + const auto old_path = Common::FS::SanitizePath(old_path_); + const auto new_path = Common::FS::SanitizePath(new_path_); // Non-default impls are highly encouraged to provide a more optimized version of this. auto old_dir = OpenDirectory(old_path, Mode::Read); @@ -139,8 +139,8 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_ } VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) { - const auto sanitized_old_path = FileUtil::SanitizePath(old_path); - const auto sanitized_new_path = FileUtil::SanitizePath(new_path); + const auto sanitized_old_path = Common::FS::SanitizePath(old_path); + const auto sanitized_new_path = Common::FS::SanitizePath(new_path); // Non-default impls are highly encouraged to provide a more optimized version of this. auto out = CopyDirectory(sanitized_old_path, sanitized_new_path); @@ -152,28 +152,29 @@ VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_v } bool VfsFilesystem::DeleteDirectory(std::string_view path_) { - const auto path = FileUtil::SanitizePath(path_); - auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); + const auto path = Common::FS::SanitizePath(path_); + auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write); if (parent == nullptr) return false; - return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path)); + return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path)); } VfsFile::~VfsFile() = default; std::string VfsFile::GetExtension() const { - return std::string(FileUtil::GetExtensionFromFilename(GetName())); + return std::string(Common::FS::GetExtensionFromFilename(GetName())); } 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 { @@ -203,7 +204,7 @@ std::string VfsFile::GetFullPath() const { } std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const { - auto vec = FileUtil::SplitPathComponents(path); + auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); if (vec.empty()) { @@ -239,7 +240,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co } std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const { - auto vec = FileUtil::SplitPathComponents(path); + auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); if (vec.empty()) { @@ -301,7 +302,7 @@ std::size_t VfsDirectory::GetSize() const { } std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) { - auto vec = FileUtil::SplitPathComponents(path); + auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); if (vec.empty()) { @@ -320,7 +321,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) } } - return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path)); + return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path)); } std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) { @@ -332,7 +333,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) } std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) { - auto vec = FileUtil::SplitPathComponents(path); + auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); if (vec.empty()) { @@ -351,7 +352,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_ } } - return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path)); + return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path)); } std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp index d69952940..429d7bc8b 100644 --- a/src/core/file_sys/vfs_libzip.cpp +++ b/src/core/file_sys/vfs_libzip.cpp @@ -49,7 +49,7 @@ VirtualDir ExtractZIP(VirtualFile file) { if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size())) return nullptr; - const auto parts = FileUtil::SplitPathComponents(stat.name); + const auto parts = Common::FS::SplitPathComponents(stat.name); const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back()); std::shared_ptr<VectorVfsDirectory> dtrv = out; diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index c96f88488..7714d3de5 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -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_real.cpp b/src/core/file_sys/vfs_real.cpp index 96ce5957c..488687ba9 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -14,24 +14,28 @@ namespace FileSys { +namespace FS = Common::FS; + static std::string ModeFlagsToString(Mode mode) { std::string mode_str; // Calculate the correct open mode for the file. - if (mode & Mode::Read && mode & Mode::Write) { - if (mode & Mode::Append) + if (True(mode & Mode::Read) && True(mode & Mode::Write)) { + if (True(mode & Mode::Append)) { mode_str = "a+"; - else + } else { mode_str = "r+"; + } } else { - if (mode & Mode::Read) + if (True(mode & Mode::Read)) { mode_str = "r"; - else if (mode & Mode::Append) + } else if (True(mode & Mode::Append)) { mode_str = "a"; - else if (mode & Mode::Write) + } else if (True(mode & Mode::Write)) { mode_str = "w"; - else + } else { UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode)); + } } mode_str += "b"; @@ -55,78 +59,82 @@ bool RealVfsFilesystem::IsWritable() const { } VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(path)) + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + if (!FS::Exists(path)) { return VfsEntryType::None; - if (FileUtil::IsDirectory(path)) + } + if (FS::IsDirectory(path)) { return VfsEntryType::Directory; + } return VfsEntryType::File; } VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - if (cache.find(path) != cache.end()) { - auto weak = cache[path]; + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + + if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { + const auto& weak = weak_iter->second; + if (!weak.expired()) { return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); } } - if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0) - FileUtil::CreateEmptyFile(path); + if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { + FS::CreateEmptyFile(path); + } - auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str()); - cache[path] = backing; + auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str()); + cache.insert_or_assign(path, backing); // Cannot use make_shared as RealVfsFile constructor is private return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); } VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); - if (!FileUtil::Exists(path)) { - FileUtil::CreateFullPath(path_fwd); - if (!FileUtil::CreateEmptyFile(path)) + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); + if (!FS::Exists(path)) { + FS::CreateFullPath(path_fwd); + if (!FS::CreateEmptyFile(path)) { return nullptr; + } } return OpenFile(path, perms); } VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = - FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto new_path = - FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || - FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path)) + if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || + !FS::Copy(old_path, new_path)) { return nullptr; + } return OpenFile(new_path, Mode::ReadWrite); } VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = - FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto new_path = - FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); + const auto cached_file_iter = cache.find(old_path); - if (cache.find(old_path) != cache.end()) { - auto file = cache[old_path].lock(); + if (cached_file_iter != cache.cend()) { + auto file = cached_file_iter->second.lock(); - if (!cache[old_path].expired()) { + if (!cached_file_iter->second.expired()) { file->Close(); } - if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || - FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) { + if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || + !FS::Rename(old_path, new_path)) { return nullptr; } cache.erase(old_path); file->Open(new_path, "r+b"); - cache[new_path] = file; + cache.insert_or_assign(new_path, std::move(file)); } else { UNREACHABLE(); return nullptr; @@ -136,28 +144,33 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ } bool RealVfsFilesystem::DeleteFile(std::string_view path_) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - if (cache.find(path) != cache.end()) { - if (!cache[path].expired()) - cache[path].lock()->Close(); + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + const auto cached_iter = cache.find(path); + + if (cached_iter != cache.cend()) { + if (!cached_iter->second.expired()) { + cached_iter->second.lock()->Close(); + } cache.erase(path); } - return FileUtil::Delete(path); + + return FS::Delete(path); } VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); } VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); - if (!FileUtil::Exists(path)) { - FileUtil::CreateFullPath(path_fwd); - if (!FileUtil::CreateDir(path)) + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); + if (!FS::Exists(path)) { + FS::CreateFullPath(path_fwd); + if (!FS::CreateDir(path)) { return nullptr; + } } // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); @@ -165,67 +178,75 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = - FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto new_path = - FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || - !FileUtil::IsDirectory(old_path)) + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); + if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) { return nullptr; - FileUtil::CopyDir(old_path, new_path); + } + FS::CopyDir(old_path, new_path); return OpenDirectory(new_path, Mode::ReadWrite); } VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = - FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); - const auto new_path = - FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || - FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); + + if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || + !FS::Rename(old_path, new_path)) { return nullptr; + } for (auto& kv : cache) { - // Path in cache starts with old_path - if (kv.first.rfind(old_path, 0) == 0) { - const auto file_old_path = - FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault); - const auto file_new_path = - FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), - FileUtil::DirectorySeparator::PlatformDefault); - auto cached = cache[file_old_path]; - if (!cached.expired()) { - auto file = cached.lock(); - file->Open(file_new_path, "r+b"); - cache.erase(file_old_path); - cache[file_new_path] = file; - } + // If the path in the cache doesn't start with old_path, then bail on this file. + if (kv.first.rfind(old_path, 0) != 0) { + continue; + } + + const auto file_old_path = + FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); + auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), + FS::DirectorySeparator::PlatformDefault); + const auto& cached = cache[file_old_path]; + + if (cached.expired()) { + continue; } + + 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)); } return OpenDirectory(new_path, Mode::ReadWrite); } bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { - const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + for (auto& kv : cache) { - // Path in cache starts with old_path - if (kv.first.rfind(path, 0) == 0) { - if (!cache[kv.first].expired()) - cache[kv.first].lock()->Close(); - cache.erase(kv.first); + // If the path in the cache doesn't start with path, then bail on this file. + if (kv.first.rfind(path, 0) != 0) { + continue; + } + + const auto& entry = cache[kv.first]; + if (!entry.expired()) { + entry.lock()->Close(); } + + cache.erase(kv.first); } - return FileUtil::DeleteDirRecursively(path); + + return FS::DeleteDirRecursively(path); } -RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_, +RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, const std::string& path_, Mode perms_) - : base(base_), backing(std::move(backing_)), path(path_), - parent_path(FileUtil::GetParentPath(path_)), - path_components(FileUtil::SplitPathComponents(path_)), - parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), + : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), + path_components(FS::SplitPathComponents(path_)), + parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), perms(perms_) {} RealVfsFile::~RealVfsFile() = default; @@ -247,22 +268,24 @@ std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const { } bool RealVfsFile::IsWritable() const { - return (perms & Mode::WriteAppend) != 0; + return True(perms & Mode::WriteAppend); } bool RealVfsFile::IsReadable() const { - return (perms & Mode::ReadWrite) != 0; + return True(perms & Mode::ReadWrite); } std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { - if (!backing->Seek(offset, SEEK_SET)) + if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { return 0; + } return backing->ReadBytes(data, length); } std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { - if (!backing->Seek(offset, SEEK_SET)) + if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { return 0; + } return backing->WriteBytes(data, length); } @@ -279,16 +302,18 @@ bool RealVfsFile::Close() { template <> std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const { - if (perms == Mode::Append) + if (perms == Mode::Append) { return {}; + } std::vector<VirtualFile> out; - FileUtil::ForeachDirectoryEntry( + FS::ForeachDirectoryEntry( nullptr, path, [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { const std::string full_path = directory + DIR_SEP + filename; - if (!FileUtil::IsDirectory(full_path)) + if (!FS::IsDirectory(full_path)) { out.emplace_back(base.OpenFile(full_path, perms)); + } return true; }); @@ -297,16 +322,18 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>( template <> std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const { - if (perms == Mode::Append) + if (perms == Mode::Append) { return {}; + } std::vector<VirtualDir> out; - FileUtil::ForeachDirectoryEntry( + FS::ForeachDirectoryEntry( nullptr, path, [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { const std::string full_path = directory + DIR_SEP + filename; - if (FileUtil::IsDirectory(full_path)) + if (FS::IsDirectory(full_path)) { out.emplace_back(base.OpenDirectory(full_path, perms)); + } return true; }); @@ -314,28 +341,30 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi } RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) - : base(base_), path(FileUtil::RemoveTrailingSlash(path_)), - parent_path(FileUtil::GetParentPath(path)), - path_components(FileUtil::SplitPathComponents(path)), - parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), + : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), + path_components(FS::SplitPathComponents(path)), + parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), perms(perms_) { - if (!FileUtil::Exists(path) && perms & Mode::WriteAppend) - FileUtil::CreateDir(path); + if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { + FS::CreateDir(path); + } } RealVfsDirectory::~RealVfsDirectory() = default; std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { - const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); - if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path)) + const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); + if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { return nullptr; + } return base.OpenFile(full_path, perms); } std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { - const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); - if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path)) + const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); + if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { return nullptr; + } return base.OpenDirectory(full_path, perms); } @@ -348,17 +377,17 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view } std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) { - const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(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) { - const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); + const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); return base.CreateDirectory(full_path, perms); } bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { - auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name)); + const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name)); return base.DeleteDirectory(full_path); } @@ -371,11 +400,11 @@ std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() } bool RealVfsDirectory::IsWritable() const { - return (perms & Mode::WriteAppend) != 0; + return True(perms & Mode::WriteAppend); } bool RealVfsDirectory::IsReadable() const { - return (perms & Mode::ReadWrite) != 0; + return True(perms & Mode::ReadWrite); } std::string RealVfsDirectory::GetName() const { @@ -383,8 +412,9 @@ std::string RealVfsDirectory::GetName() const { } std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { - if (path_components.size() <= 1) + if (path_components.size() <= 1) { return nullptr; + } return base.OpenDirectory(parent_path, perms); } @@ -421,16 +451,17 @@ std::string RealVfsDirectory::GetFullPath() const { } std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const { - if (perms == Mode::Append) + if (perms == Mode::Append) { return {}; + } std::map<std::string, VfsEntryType, std::less<>> out; - FileUtil::ForeachDirectoryEntry( + FS::ForeachDirectoryEntry( nullptr, path, [&out](u64* entries_out, const std::string& directory, const std::string& filename) { const std::string full_path = directory + DIR_SEP + filename; - out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory - : VfsEntryType::File); + out.emplace(filename, + FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File); return true; }); diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index a0a857a31..0b537b22c 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -9,7 +9,7 @@ #include "core/file_sys/mode.h" #include "core/file_sys/vfs.h" -namespace FileUtil { +namespace Common::FS { class IOFile; } @@ -36,7 +36,7 @@ public: bool DeleteDirectory(std::string_view path) override; private: - boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache; + boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; }; // An implmentation of VfsFile that represents a file on the user's computer. @@ -58,13 +58,13 @@ public: bool Rename(std::string_view name) override; private: - RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing, + RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, const std::string& path, Mode perms = Mode::Read); bool Close(); RealVfsFilesystem& base; - std::shared_ptr<FileUtil::IOFile> backing; + std::shared_ptr<Common::FS::IOFile> backing; std::string path; std::string parent_path; std::vector<std::string> path_components; diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h index 9f5a90b1b..8b27c30fa 100644 --- a/src/core/file_sys/vfs_static.h +++ b/src/core/file_sys/vfs_static.h @@ -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.h b/src/core/file_sys/vfs_vector.h index ac36cb2ee..95d3da2f2 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -4,7 +4,11 @@ #pragma once +#include <array> #include <cstring> +#include <memory> +#include <string> +#include <vector> #include "core/file_sys/vfs.h" namespace FileSys { @@ -13,7 +17,8 @@ namespace FileSys { template <std::size_t size> class ArrayVfsFile : public VfsFile { public: - ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr) + 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 { @@ -61,6 +66,12 @@ private: VirtualDir parent; }; +template <std::size_t Size, typename... Args> +std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data, + Args&&... args) { + return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...); +} + // An implementation of VfsFile that is backed by a vector optionally supplied upon construction class VectorVfsFile : public VfsFile { public: diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 86e06ccb9..24c58e7ae 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,8 +44,10 @@ 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_)) { - std::string path = FileUtil::SanitizePath(file->GetFullPath()); +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 | std::regex_constants::icase); @@ -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], @@ -70,14 +74,18 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) NAX::~NAX() = default; Loader::ResultStatus NAX::Parse(std::string_view path) { - if (file->ReadObject(header.get()) != sizeof(NAXHeader)) + if (file == nullptr) { + return Loader::ResultStatus::ErrorNullFile; + } + if (file->ReadObject(header.get()) != sizeof(NAXHeader)) { return Loader::ResultStatus::ErrorBadNAXHeader; - - if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) + } + if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) { return Loader::ResultStatus::ErrorBadNAXHeader; - - if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) + } + if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) { return Loader::ResultStatus::ErrorIncorrectNAXFileSize; + } keys.DeriveSDSeedLazy(); std::array<Core::Crypto::Key256, 2> sd_keys{}; diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 563531bb6..c472e226e 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; @@ -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..c5d65f2d0 --- /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, + 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; + + 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) { + // 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..3e49cdbb9 --- /dev/null +++ b/src/core/frontend/applets/controller.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 <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{}; +}; + +class ControllerApplet { +public: + virtual ~ControllerApplet(); + + virtual void ReconfigureControllers(std::function<void()> callback, + 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, + ControllerParameters parameters) const override; + +private: + Service::SM::ServiceManager& service_manager; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 13aa14934..3e8780243 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -39,7 +39,7 @@ public: class Scoped { public: - explicit Scoped(GraphicsContext& context_) : context(context_) { + [[nodiscard]] explicit Scoped(GraphicsContext& context_) : context(context_) { context.MakeCurrent(); } ~Scoped() { @@ -52,7 +52,7 @@ public: /// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value /// ends - Scoped Acquire() { + [[nodiscard]] Scoped Acquire() { return Scoped{*this}; } }; 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..277b70e53 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -33,6 +33,9 @@ public: virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { return {}; } + virtual bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const { + return {}; + } }; /// An abstract class template for a factory that can create input devices. @@ -119,11 +122,11 @@ 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 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,8 +136,22 @@ 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<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; +using MotionDevice = InputDevice<MotionStatus>; /** * A touch device is an input device that returns a tuple of two floats and a bool. The floats are diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp index c629d9fa1..645f26e91 100644 --- a/src/core/hardware_interrupt_manager.cpp +++ b/src/core/hardware_interrupt_manager.cpp @@ -11,19 +11,20 @@ namespace Core::Hardware { InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) { - gpu_interrupt_event = Core::Timing::CreateEvent("GPUInterrupt", [this](u64 message, s64) { - auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv"); - const u32 syncpt = static_cast<u32>(message >> 32); - const u32 value = static_cast<u32>(message); - nvdrv->SignalGPUInterruptSyncpt(syncpt, value); - }); + gpu_interrupt_event = Core::Timing::CreateEvent( + "GPUInterrupt", [this](std::uintptr_t message, std::chrono::nanoseconds) { + auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv"); + const u32 syncpt = static_cast<u32>(message >> 32); + const u32 value = static_cast<u32>(message); + nvdrv->SignalGPUInterruptSyncpt(syncpt, value); + }); } InterruptManager::~InterruptManager() = default; void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value; - system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg); + system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg); } } // namespace Core::Hardware diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 0dc6a4a43..1b503331f 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -229,6 +229,8 @@ inline void ResponseBuilder::Push(u32 value) { template <typename T> void ResponseBuilder::PushRaw(const T& value) { + static_assert(std::is_trivially_copyable_v<T>, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(cmdbuf + index, &value, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } @@ -384,6 +386,8 @@ inline s32 RequestParser::Pop() { template <typename T> void RequestParser::PopRaw(T& value) { + static_assert(std::is_trivially_copyable_v<T>, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(&value, cmdbuf + index, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index df0debe1b..b882eaa0f 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -81,7 +81,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 do { current_value = monitor.ExclusiveRead32(current_core, address); - if (current_value != value) { + if (current_value != static_cast<u32>(value)) { return ERR_INVALID_STATE; } current_value++; diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 5ab204b9b..be9eba519 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -48,14 +48,15 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern } ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, - Core::Memory::Memory& memory) { + 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; } // 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..e5e0690c2 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -16,6 +16,10 @@ namespace Core::Memory { class Memory; } +namespace Core::Timing { +class CoreTiming; +} + namespace Kernel { class KernelCore; @@ -42,7 +46,8 @@ public: return HANDLE_TYPE; } - ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); + ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing); bool ShouldWait(const Thread* thread) const override; diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 9277b5d08..81f85643b 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -293,13 +293,15 @@ std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { BufferDescriptorA()[buffer_index].Size()}; if (is_buffer_a) { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return buffer; }, - "BufferDescriptorA invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorA().size() > buffer_index, { return buffer; }, + "BufferDescriptorA invalid buffer_index {}", buffer_index); buffer.resize(BufferDescriptorA()[buffer_index].Size()); memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size()); } else { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return buffer; }, - "BufferDescriptorX invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorX().size() > buffer_index, { return buffer; }, + "BufferDescriptorX invalid buffer_index {}", buffer_index); buffer.resize(BufferDescriptorX()[buffer_index].Size()); memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size()); } @@ -324,16 +326,16 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, } if (is_buffer_b) { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index && - BufferDescriptorB()[buffer_index].Size() >= size, - { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", - buffer_index, size); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorB().size() > buffer_index && + BufferDescriptorB()[buffer_index].Size() >= size, + { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); } else { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index && - BufferDescriptorC()[buffer_index].Size() >= size, - { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", - buffer_index, size); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorC().size() > buffer_index && + BufferDescriptorC()[buffer_index].Size() >= size, + { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); } @@ -344,12 +346,14 @@ std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && BufferDescriptorA()[buffer_index].Size()}; if (is_buffer_a) { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return 0; }, - "BufferDescriptorA invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorA().size() > buffer_index, { return 0; }, + "BufferDescriptorA invalid buffer_index {}", buffer_index); return BufferDescriptorA()[buffer_index].Size(); } else { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return 0; }, - "BufferDescriptorX invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorX().size() > buffer_index, { return 0; }, + "BufferDescriptorX invalid buffer_index {}", buffer_index); return BufferDescriptorX()[buffer_index].Size(); } } @@ -358,12 +362,14 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && BufferDescriptorB()[buffer_index].Size()}; if (is_buffer_b) { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index, { return 0; }, - "BufferDescriptorB invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorB().size() > buffer_index, { return 0; }, + "BufferDescriptorB invalid buffer_index {}", buffer_index); return BufferDescriptorB()[buffer_index].Size(); } else { - ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index, { return 0; }, - "BufferDescriptorC invalid buffer_index {}", buffer_index); + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorC().size() > buffer_index, { return 0; }, + "BufferDescriptorC invalid buffer_index {}", buffer_index); return BufferDescriptorC()[buffer_index].Size(); } return 0; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index b31673928..f3277b766 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -13,6 +13,7 @@ #include <vector> #include <boost/container/small_vector.hpp> #include "common/common_types.h" +#include "common/concepts.h" #include "common/swap.h" #include "core/hle/ipc.h" #include "core/hle/kernel/object.h" @@ -193,23 +194,24 @@ public: /* Helper function to write a buffer using the appropriate buffer descriptor * - * @tparam ContiguousContainer an arbitrary container that satisfies the - * ContiguousContainer concept in the C++ standard library. + * @tparam T an arbitrary container that satisfies the + * ContiguousContainer concept in the C++ standard library or a trivially copyable type. * - * @param container The container to write the data of into a buffer. + * @param data The container/data to write into a buffer. * @param buffer_index The buffer in particular to write to. */ - template <typename ContiguousContainer, - typename = std::enable_if_t<!std::is_pointer_v<ContiguousContainer>>> - std::size_t WriteBuffer(const ContiguousContainer& container, - std::size_t buffer_index = 0) const { - using ContiguousType = typename ContiguousContainer::value_type; - - static_assert(std::is_trivially_copyable_v<ContiguousType>, - "Container to WriteBuffer must contain trivially copyable objects"); - - return WriteBuffer(std::data(container), std::size(container) * sizeof(ContiguousType), - buffer_index); + template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>> + std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const { + if constexpr (Common::IsSTLContainer<T>) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v<ContiguousType>, + "Container to WriteBuffer must contain trivially copyable objects"); + return WriteBuffer(std::data(data), std::size(data) * sizeof(ContiguousType), + buffer_index); + } else { + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); + return WriteBuffer(&data, sizeof(T), buffer_index); + } } /// Helper function to get the size of the input buffer diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index e1c7a0f3b..f2b0fe2fd 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -145,16 +145,18 @@ struct KernelCore::Impl { void InitializePreemption(KernelCore& kernel) { preemption_event = Core::Timing::CreateEvent( - "PreemptionCallback", [this, &kernel](u64 userdata, s64 cycles_late) { + "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) { { SchedulerLock lock(kernel); global_scheduler.PreemptThreads(); } - s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); + const auto time_interval = std::chrono::nanoseconds{ + Core::Timing::msToCycles(std::chrono::milliseconds(10))}; system.CoreTiming().ScheduleEvent(time_interval, preemption_event); }); - s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); + const auto time_interval = + std::chrono::nanoseconds{Core::Timing::msToCycles(std::chrono::milliseconds(10))}; system.CoreTiming().ScheduleEvent(time_interval, preemption_event); } @@ -217,6 +219,7 @@ struct KernelCore::Impl { return static_cast<u32>(system.GetCpuManager().CurrentCore()); } } + std::unique_lock lock{register_thread_mutex}; const auto it = host_thread_ids.find(this_id); if (it == host_thread_ids.end()) { return Core::INVALID_HOST_THREAD_ID; @@ -322,7 +325,7 @@ struct KernelCore::Impl { 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; + mutable std::mutex register_thread_mutex; // Kernel memory management std::unique_ptr<Memory::MemoryManager> memory_manager; diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp index 5d6aac00f..a3fadb533 100644 --- a/src/core/hle/kernel/memory/page_table.cpp +++ b/src/core/hle/kernel/memory/page_table.cpp @@ -604,7 +604,6 @@ ResultCode PageTable::MapPages(VAddr addr, const PageLinkedList& page_linked_lis if (const auto result{ Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())}; result.IsError()) { - const MemoryInfo info{block_manager->FindBlock(cur_addr).GetMemoryInfo()}; const std::size_t num_pages{(addr - cur_addr) / PageSize}; ASSERT( @@ -852,11 +851,12 @@ ResultCode PageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) { return result; } - block_manager->UpdateLock(addr, size / PageSize, - [](MemoryBlockManager::iterator block, MemoryPermission perm) { - block->ShareToDevice(perm); - }, - perm); + block_manager->UpdateLock( + addr, size / PageSize, + [](MemoryBlockManager::iterator block, MemoryPermission perm) { + block->ShareToDevice(perm); + }, + perm); return RESULT_SUCCESS; } @@ -874,11 +874,12 @@ ResultCode PageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) return result; } - block_manager->UpdateLock(addr, size / PageSize, - [](MemoryBlockManager::iterator block, MemoryPermission perm) { - block->UnshareToDevice(perm); - }, - perm); + block_manager->UpdateLock( + addr, size / PageSize, + [](MemoryBlockManager::iterator block, MemoryPermission perm) { + block->UnshareToDevice(perm); + }, + perm); return RESULT_SUCCESS; } diff --git a/src/core/hle/kernel/memory/system_control.cpp b/src/core/hle/kernel/memory/system_control.cpp index 2f98e9c4c..11d204bc2 100644 --- a/src/core/hle/kernel/memory/system_control.cpp +++ b/src/core/hle/kernel/memory/system_control.cpp @@ -7,22 +7,15 @@ #include "core/hle/kernel/memory/system_control.h" namespace Kernel::Memory::SystemControl { - -u64 GenerateRandomU64ForInit() { - static std::random_device device; - static std::mt19937 gen(device()); - static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); - return distribution(gen); -} - +namespace { template <typename F> u64 GenerateUniformRange(u64 min, u64 max, F f) { - /* Handle the case where the difference is too large to represent. */ + // Handle the case where the difference is too large to represent. if (max == std::numeric_limits<u64>::max() && min == std::numeric_limits<u64>::min()) { return f(); } - /* Iterate until we get a value in range. */ + // Iterate until we get a value in range. const u64 range_size = ((max + 1) - min); const u64 effective_max = (std::numeric_limits<u64>::max() / range_size) * range_size; while (true) { @@ -32,6 +25,14 @@ u64 GenerateUniformRange(u64 min, u64 max, F f) { } } +u64 GenerateRandomU64ForInit() { + static std::random_device device; + static std::mt19937 gen(device()); + static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); + return distribution(gen); +} +} // Anonymous namespace + u64 GenerateRandomRange(u64 min, u64 max) { return GenerateUniformRange(min, max, GenerateRandomU64ForInit); } diff --git a/src/core/hle/kernel/memory/system_control.h b/src/core/hle/kernel/memory/system_control.h index 3fa93111d..19cab8cbc 100644 --- a/src/core/hle/kernel/memory/system_control.h +++ b/src/core/hle/kernel/memory/system_control.h @@ -8,11 +8,6 @@ namespace Kernel::Memory::SystemControl { -u64 GenerateRandomU64ForInit(); - -template <typename F> -u64 GenerateUniformRange(u64 min, u64 max, F f); - u64 GenerateRandomRange(u64 min, u64 max); } // namespace Kernel::Memory::SystemControl diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index f93e5e4b0..5cbd3b912 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -131,7 +131,8 @@ u32 GlobalScheduler::SelectThreads() { 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 || top_threads[core]->GetProcessorID() == 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); } @@ -663,32 +664,26 @@ void Scheduler::Reload() { } void Scheduler::SwitchContextStep2() { - Thread* previous_thread = current_thread_prev.get(); - Thread* new_thread = selected_thread.get(); - // Load context of new thread - Process* const previous_process = - previous_thread != nullptr ? previous_thread->GetOwnerProcess() : nullptr; - - if (new_thread) { - ASSERT_MSG(new_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, + if (selected_thread) { + ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, "Thread must be runnable."); // Cancel any outstanding wakeup events for this thread - new_thread->SetIsRunning(true); - new_thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - new_thread->SetWasRunning(false); + 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 (!new_thread->IsHLEThread()) { - Core::ARM_Interface& cpu_core = new_thread->ArmInterface(); - cpu_core.LoadContext(new_thread->GetContext32()); - cpu_core.LoadContext(new_thread->GetContext64()); - cpu_core.SetTlsAddress(new_thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); + 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(); } @@ -761,7 +756,11 @@ void Scheduler::SwitchToCurrent() { current_thread = selected_thread; is_context_switch_pending = false; } - while (!is_context_switch_pending) { + const auto is_switch_pending = [this] { + std::scoped_lock lock{guard}; + return is_context_switch_pending; + }; + do { if (current_thread != nullptr && !current_thread->IsHLEThread()) { current_thread->context_guard.lock(); if (!current_thread->IsRunnable()) { @@ -780,7 +779,7 @@ void Scheduler::SwitchToCurrent() { next_context = &idle_thread->GetHostContext(); } Common::Fiber::YieldTo(switch_fiber, *next_context); - } + } while (!is_switch_pending()); } } diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index b3b4b5169..b6f04dcea 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -188,7 +188,7 @@ private: /// Scheduler lock mechanisms. bool is_locked{}; - Common::SpinLock inner_lock{}; + std::mutex inner_lock; std::atomic<s64> scope_lock{}; Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; @@ -289,7 +289,7 @@ private: class SchedulerLock { public: - explicit SchedulerLock(KernelCore& kernel); + [[nodiscard]] explicit SchedulerLock(KernelCore& kernel); ~SchedulerLock(); protected: diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 7b23a6889..8c19f2534 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -8,7 +8,6 @@ #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" @@ -33,8 +32,10 @@ ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kern std::string name) { std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)}; - session->request_event = Core::Timing::CreateEvent( - name, [session](u64 userdata, s64 cycles_late) { session->CompleteSyncRequest(); }); + 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); @@ -183,10 +184,11 @@ ResultCode ServerSession::CompleteSyncRequest() { } ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, - Core::Memory::Memory& memory) { - ResultCode result = QueueSyncRequest(std::move(thread), memory); - const u64 delay = kernel.IsMulticore() ? 0U : 20000U; - Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {}); + Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing) { + const ResultCode result = QueueSyncRequest(std::move(thread), memory); + const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000}; + core_timing.ScheduleEvent(delay, request_event, {}); return result; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 403aaf10b..d23e9ec68 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -18,8 +18,9 @@ class Memory; } namespace Core::Timing { +class CoreTiming; struct EventType; -} +} // namespace Core::Timing namespace Kernel { @@ -87,12 +88,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); + ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory, + Core::Timing::CoreTiming& core_timing); bool ShouldWait(const Thread* thread) const override; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 01ae57053..bafd1ced7 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -346,7 +346,7 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) { SchedulerLock lock(system.Kernel()); thread->InvalidateHLECallback(); thread->SetStatus(ThreadStatus::WaitIPC); - session->SendSyncRequest(SharedFrom(thread), system.Memory()); + session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming()); } if (thread->HasHLECallback()) { diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp index 941305e8e..95f2446c9 100644 --- a/src/core/hle/kernel/time_manager.cpp +++ b/src/core/hle/kernel/time_manager.cpp @@ -16,14 +16,14 @@ namespace Kernel { TimeManager::TimeManager(Core::System& system_) : system{system_} { time_manager_event_type = Core::Timing::CreateEvent( - "Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) { - SchedulerLock lock(system.Kernel()); - Handle proper_handle = static_cast<Handle>(thread_handle); + "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<Thread> thread = - this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); + auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); thread->OnWakeUp(); }); } @@ -34,7 +34,8 @@ void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 ASSERT(timetask); ASSERT(timetask->GetStatus() != ThreadStatus::Ready); ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex); - system.CoreTiming().ScheduleEvent(nanoseconds, time_manager_event_type, event_handle); + system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds}, + time_manager_event_type, event_handle); } else { event_handle = InvalidHandle; } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 450f61fea..b6bdbd988 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -342,8 +342,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) { */ #define CASCADE_RESULT(target, source) \ auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).Failed()) \ + if (CONCAT2(check_result_L, __LINE__).Failed()) { \ return CONCAT2(check_result_L, __LINE__).Code(); \ + } \ target = std::move(*CONCAT2(check_result_L, __LINE__)) /** @@ -351,6 +352,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) { * non-success, or discarded otherwise. */ #define CASCADE_CODE(source) \ - auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).IsError()) \ - return CONCAT2(check_result_L, __LINE__); + do { \ + auto CONCAT2(check_result_L, __LINE__) = source; \ + if (CONCAT2(check_result_L, __LINE__).IsError()) { \ + return CONCAT2(check_result_L, __LINE__); \ + } \ + } while (false) diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 8ac856ec3..6b1613510 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -35,7 +35,7 @@ constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30}; constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; static std::string GetImagePath(Common::UUID uuid) { - return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; } @@ -286,9 +286,7 @@ protected: ProfileBase profile_base{}; ProfileData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { - std::array<u8, sizeof(ProfileData)> raw_data; - std::memcpy(raw_data.data(), &data, sizeof(ProfileData)); - ctx.WriteBuffer(raw_data); + ctx.WriteBuffer(data); IPC::ResponseBuilder rb{ctx, 16}; rb.Push(RESULT_SUCCESS); rb.PushRaw(profile_base); @@ -320,7 +318,7 @@ protected: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - const FileUtil::IOFile image(GetImagePath(user_id), "rb"); + const Common::FS::IOFile image(GetImagePath(user_id), "rb"); if (!image.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); @@ -333,7 +331,7 @@ protected: std::vector<u8> buffer(size); image.ReadBytes(buffer.data(), buffer.size()); - ctx.WriteBuffer(buffer.data(), buffer.size()); + ctx.WriteBuffer(buffer); rb.Push<u32>(size); } @@ -342,7 +340,7 @@ protected: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - const FileUtil::IOFile image(GetImagePath(user_id), "rb"); + const Common::FS::IOFile image(GetImagePath(user_id), "rb"); if (!image.IsOpen()) { LOG_WARNING(Service_ACC, @@ -407,7 +405,7 @@ protected: ProfileData data; std::memcpy(&data, user_data.data(), sizeof(ProfileData)); - FileUtil::IOFile image(GetImagePath(user_id), "wb"); + Common::FS::IOFile image(GetImagePath(user_id), "wb"); if (!image.IsOpen() || !image.Resize(image_data.size()) || image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || @@ -776,6 +774,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>(profile_manager->GetLastOpenedUser()); +} + void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index d4c6395c6..c611efd89 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -34,6 +34,7 @@ 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); private: diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index cb44e06b7..75a24f8f5 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -29,7 +29,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {110, nullptr, "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/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index eb8c81645..9b829e957 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -13,6 +13,7 @@ namespace Service::Account { +namespace FS = Common::FS; using Common::UUID; struct UserRaw { @@ -58,7 +59,7 @@ ProfileManager::~ProfileManager() { /// internal management of the users profiles std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) { if (user_count >= MAX_USERS) { - return {}; + return std::nullopt; } profiles[user_count] = profile; return user_count++; @@ -101,13 +102,14 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& usern [&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) { return ERROR_USER_ALREADY_EXISTS; } - ProfileInfo profile; - profile.user_uuid = uuid; - profile.username = username; - profile.data = {}; - profile.creation_time = 0x0; - profile.is_open = false; - return AddUser(profile); + + return AddUser({ + .user_uuid = uuid, + .username = username, + .creation_time = 0, + .data = {}, + .is_open = false, + }); } /// Creates a new user on the system. This function allows a much simpler method of registration @@ -126,7 +128,7 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) std::optional<UUID> ProfileManager::GetUser(std::size_t index) const { if (index >= MAX_USERS) { - return {}; + return std::nullopt; } return profiles[index].user_uuid; @@ -135,13 +137,13 @@ std::optional<UUID> ProfileManager::GetUser(std::size_t index) const { /// Returns a users profile index based on their user id. std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { if (!uuid) { - return {}; + return std::nullopt; } const auto iter = std::find_if(profiles.begin(), profiles.end(), [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; }); if (iter == profiles.end()) { - return {}; + return std::nullopt; } return static_cast<std::size_t>(std::distance(profiles.begin(), iter)); @@ -317,9 +319,8 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& } void ProfileManager::ParseUserSaveFile() { - FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", - "rb"); + const FS::IOFile save( + FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb"); if (!save.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " @@ -339,7 +340,13 @@ void ProfileManager::ParseUserSaveFile() { continue; } - AddUser({user.uuid, user.username, user.timestamp, user.extra_data, false}); + AddUser({ + .user_uuid = user.uuid, + .username = user.username, + .creation_time = user.timestamp, + .data = user.extra_data, + .is_open = false, + }); } std::stable_partition(profiles.begin(), profiles.end(), @@ -350,29 +357,31 @@ void ProfileManager::WriteUserSaveFile() { ProfileDataRaw raw{}; for (std::size_t i = 0; i < MAX_USERS; ++i) { - raw.users[i].username = profiles[i].username; - raw.users[i].uuid2 = profiles[i].user_uuid; - raw.users[i].uuid = profiles[i].user_uuid; - raw.users[i].timestamp = profiles[i].creation_time; - raw.users[i].extra_data = profiles[i].data; + raw.users[i] = { + .uuid = profiles[i].user_uuid, + .uuid2 = profiles[i].user_uuid, + .timestamp = profiles[i].creation_time, + .username = profiles[i].username, + .extra_data = profiles[i].data, + }; } - const auto raw_path = - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"; - if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path)) - FileUtil::Delete(raw_path); + const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010"; + if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) { + FS::Delete(raw_path); + } - const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; + const auto path = + FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; - if (!FileUtil::CreateFullPath(path)) { + if (!FS::CreateFullPath(path)) { LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " "nand/system/save/8000000000000010/su/avators to mitigate this " "issue."); return; } - FileUtil::IOFile save(path, "wb"); + FS::IOFile save(path, "wb"); if (!save.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 4e7a0bec9..d7a81f64a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -378,7 +378,11 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& } void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto permission = rp.PopEnum<ScreenshotPermission>(); + LOG_DEBUG(Service_AM, "called, permission={}", permission); + + screenshot_permission = permission; IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1188,7 +1192,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {120, nullptr, "ExecuteProgram"}, {121, nullptr, "ClearUserChannel"}, {122, nullptr, "UnpopToUserChannel"}, - {123, nullptr, "GetPreviousProgramIndex"}, + {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"}, {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"}, @@ -1342,12 +1346,12 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]); - FileSys::SaveDataDescriptor descriptor{}; - descriptor.title_id = system.CurrentProcess()->GetTitleID(); - descriptor.user_id = user_id; - descriptor.type = FileSys::SaveDataType::SaveData; + FileSys::SaveDataAttribute attribute{}; + attribute.title_id = system.CurrentProcess()->GetTitleID(); + attribute.user_id = user_id; + attribute.type = FileSys::SaveDataType::SaveData; const auto res = system.GetFileSystemController().CreateSaveData( - FileSys::SaveDataSpaceId::NandUser, descriptor); + FileSys::SaveDataSpaceId::NandUser, attribute); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(res.Code()); @@ -1405,7 +1409,6 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { // Get supported languages from NACP, if possible // Default to 0 (all languages supported) u32 supported_languages = 0; - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; const auto res = [this] { const auto title_id = system.CurrentProcess()->GetTitleID(); @@ -1551,6 +1554,14 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque rb.Push<u32>(0); } +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"); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6cfb11b48..bcc06affe 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -149,6 +149,12 @@ private: void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); + enum class ScreenshotPermission : u32 { + Inherit = 0, + Enable = 1, + Disable = 2, + }; + Core::System& system; std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::EventPair launchable_event; @@ -157,6 +163,7 @@ private: u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; bool is_auto_sleep_disabled = false; + ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; }; class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { @@ -281,11 +288,13 @@ private: void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); + void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); bool launch_popped_application_specific = false; bool launch_popped_account_preselect = false; + s32 previous_program_index{-1}; Kernel::EventPair gpu_error_detected_event; Kernel::EventPair friend_invitation_storage_channel_event; Core::System& system; diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index c3261f3e6..2b626bb40 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -5,6 +5,7 @@ #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" @@ -15,6 +16,7 @@ #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" @@ -140,14 +142,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, +AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, + ErrorApplet error, ParentalControlsApplet parental_controls, 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)} {} + SoftwareKeyboard software_keyboard, WebBrowser web_browser) + : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)}, + parental_controls{std::move(parental_controls)}, 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 +166,37 @@ 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.e_commerce != nullptr) { + frontend.e_commerce = std::move(set.e_commerce); + } + + 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,24 @@ 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.e_commerce == nullptr) { + frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>(); } 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 +239,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,6 +249,8 @@ 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: diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index e75be86a2..a1f4cf897 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -17,6 +17,7 @@ class System; } namespace Core::Frontend { +class ControllerApplet; class ECommerceApplet; class ErrorApplet; class ParentalControlsApplet; @@ -155,19 +156,20 @@ protected: }; struct AppletFrontendSet { - using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; + using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>; + using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>; 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, ECommerceApplet e_commerce, ErrorApplet error, + ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, + ProfileSelect profile_select, SoftwareKeyboard software_keyboard, + WebBrowser web_browser); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -176,13 +178,14 @@ struct AppletFrontendSet { AppletFrontendSet(AppletFrontendSet&&) noexcept; AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; - ParentalControlsApplet parental_controls; + ControllerApplet controller; + ECommerceApplet e_commerce; 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..2151da783 --- /dev/null +++ b/src/core/hle/service/am/applets/controller.cpp @@ -0,0 +1,210 @@ +// 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::NPadType 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 = identification_colors, + .enable_explain_text = enable_text, + .explain_text = text, + .allow_pro_controller = npad_style_set.pro_controller == 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_) {} + +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); + + library_applet_version = LibraryAppletVersion{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(), sizeof(ControllerSupportArgPrivate)); + ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), + "Unknown ControllerSupportArgPrivate revision={} with size={}", + library_applet_version, controller_private_arg.arg_private_size); + + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto user_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(user_arg_storage != nullptr); + + const auto& user_arg = user_arg_storage->GetData(); + switch (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::Version5: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld)); + break; + case LibraryAppletVersion::Version7: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", + library_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::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + 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 (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::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 LibraryAppletVersion::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: + default: { + ConfigurationComplete(); + break; + } + } +} + +void Controller::ConfigurationComplete() { + ControllerSupportResultInfo result_info{}; + + const auto& players = Settings::values.players; + + // 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, + [](Settings::PlayerInput player) { return player.connected; })); + + result_info.selected_id = HID::Controller_NPad::IndexToNPad( + std::distance(players.begin(), + std::find_if(players.begin(), players.end(), + [](Settings::PlayerInput 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>(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..f7bb3fba9 --- /dev/null +++ b/src/core/hle/service/am/applets/controller.h @@ -0,0 +1,123 @@ +// 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 LibraryAppletVersion : 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 = 0, + ShowControllerStrapGuide = 1, + ShowControllerFirmwareUpdate = 2, +}; + +enum class ControllerSupportCaller : u8 { + Application = 0, + System = 1, +}; + +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 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; + + LibraryAppletVersion library_applet_version; + ControllerSupportArgPrivate controller_private_arg; + ControllerSupportArgOld controller_user_arg_old; + ControllerSupportArgNew controller_user_arg_new; + 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/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index fbe3686ae..bdeb0737a 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -13,11 +13,23 @@ namespace Service::AM::Applets { +namespace { +enum class Request : u32 { + Finalize = 0x4, + SetUserWordInfo = 0x6, + SetCustomizeDic = 0x7, + Calc = 0xa, + SetCustomizedDictionaries = 0xb, + UnsetCustomizedDictionaries = 0xc, + UnknownD = 0xd, + UnknownE = 0xe, +}; +constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8; constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; constexpr std::size_t DEFAULT_MAX_LENGTH = 500; constexpr bool INTERACTIVE_STATUS_OK = false; - +} // Anonymous namespace static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( KeyboardConfig config, std::u16string initial_text) { Core::Frontend::SoftwareKeyboardParameters params{}; @@ -47,6 +59,7 @@ SoftwareKeyboard::~SoftwareKeyboard() = default; void SoftwareKeyboard::Initialize() { complete = false; + is_inline = false; initial_text.clear(); final_data.clear(); @@ -56,6 +69,11 @@ void SoftwareKeyboard::Initialize() { ASSERT(keyboard_config_storage != nullptr); const auto& keyboard_config = keyboard_config_storage->GetData(); + if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) { + is_inline = true; + return; + } + ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); @@ -87,16 +105,31 @@ void SoftwareKeyboard::ExecuteInteractive() { const auto storage = broker.PopInteractiveDataToApplet(); ASSERT(storage != nullptr); const auto data = storage->GetData(); - const auto status = static_cast<bool>(data[0]); - - if (status == INTERACTIVE_STATUS_OK) { - complete = true; + if (!is_inline) { + const auto status = static_cast<bool>(data[0]); + if (status == INTERACTIVE_STATUS_OK) { + complete = true; + } else { + std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; + std::memcpy(string.data(), data.data() + 4, string.size() * 2); + frontend.SendTextCheckDialog( + Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), + [this] { broker.SignalStateChanged(); }); + } } else { - std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; - std::memcpy(string.data(), data.data() + 4, string.size() * 2); - frontend.SendTextCheckDialog( - Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), - [this] { broker.SignalStateChanged(); }); + Request request{}; + std::memcpy(&request, data.data(), sizeof(Request)); + + switch (request) { + case Request::Calc: { + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{1})); + broker.SignalStateChanged(); + break; + } + default: + UNIMPLEMENTED_MSG("Request {:X} is not implemented", request); + break; + } } } @@ -108,9 +141,10 @@ void SoftwareKeyboard::Execute() { } const auto parameters = ConvertToFrontendParameters(config, initial_text); - - frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(std::move(text)); }, - parameters); + if (!is_inline) { + frontend.RequestText( + [this](std::optional<std::u16string> text) { WriteText(std::move(text)); }, parameters); + } } void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index ef4801fc6..5a3824b5a 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -78,6 +78,7 @@ private: KeyboardConfig config; std::u16string initial_text; bool complete = false; + bool is_inline = false; std::vector<u8> final_data; }; diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 9f30e167d..efe595c4f 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -293,8 +293,8 @@ void WebBrowser::Finalize() { broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data))); broker.SignalStateChanged(); - if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) { - FileUtil::DeleteDirRecursively(temporary_dir); + if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) { + Common::FS::DeleteDirRecursively(temporary_dir); } } @@ -452,10 +452,10 @@ void WebBrowser::InitializeOffline() { }; temporary_dir = - FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" + - WEB_SOURCE_NAMES[static_cast<u32>(source) - 1], - FileUtil::DirectorySeparator::PlatformDefault); - FileUtil::DeleteDirRecursively(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); @@ -492,8 +492,8 @@ void WebBrowser::InitializeOffline() { } filename = - FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, - FileUtil::DirectorySeparator::PlatformDefault); + Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, + Common::FS::DirectorySeparator::PlatformDefault); } void WebBrowser::ExecuteShop() { @@ -551,7 +551,8 @@ void WebBrowser::ExecuteShop() { } void WebBrowser::ExecuteOffline() { - frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); + frontend.OpenPageLocal( + filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); } } // namespace Service::AM::Applets diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 106e89743..9b4910e53 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -71,7 +71,7 @@ public: stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, audio_params.channel_count, std::move(unique_name), - [=]() { buffer_event.writable->Signal(); }); + [this] { buffer_event.writable->Signal(); }); } private: @@ -206,7 +206,7 @@ private: AudioCore::StreamPtr stream; std::string device_name; - [[maybe_unused]] AudoutParams audio_params {}; + [[maybe_unused]] AudoutParams audio_params{}; /// This is the event handle used to check if the audio buffer was released Kernel::EventPair buffer_event; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index d8359abaa..a2d3ded7b 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -26,7 +26,7 @@ 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") { // clang-format off @@ -94,14 +94,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) { @@ -346,7 +347,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 +376,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 +398,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 +448,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 +462,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 +470,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 +489,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 +519,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 +546,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 +686,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/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index d19513cbb..f1d81602c 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -92,7 +92,7 @@ private: if (performance) { rb.Push<u64>(*performance); } - ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); + ctx.WriteBuffer(samples); } bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input, diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index d29e78d7e..ca021a99f 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -89,12 +89,12 @@ constexpr u32 TIMEOUT_SECONDS = 30; std::string GetBINFilePath(u64 title_id) { return fmt::format("{}bcat/{:016X}/launchparam.bin", - FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); } std::string GetZIPFilePath(u64 title_id) { return fmt::format("{}bcat/{:016X}/data.zip", - FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); } // If the error is something the user should know about (build ID mismatch, bad client version), @@ -205,8 +205,8 @@ private: {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)}, }; - if (FileUtil::Exists(path)) { - FileUtil::IOFile file{path, "rb"}; + if (Common::FS::Exists(path)) { + Common::FS::IOFile file{path, "rb"}; if (file.IsOpen()) { std::vector<u8> bytes(file.GetSize()); file.ReadBytes(bytes.data(), bytes.size()); @@ -236,8 +236,8 @@ private: return DownloadResult::InvalidContentType; } - FileUtil::CreateFullPath(path); - FileUtil::IOFile file{path, "wb"}; + Common::FS::CreateFullPath(path); + Common::FS::IOFile file{path, "wb"}; if (!file.IsOpen()) return DownloadResult::GeneralFSError; if (!file.Resize(response->body.size())) @@ -290,7 +290,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - FileUtil::Delete(zip_path); + Common::FS::Delete(zip_path); } HandleDownloadDisplayResult(applet_manager, res); @@ -300,7 +300,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe progress.StartProcessingDataList(); - FileUtil::IOFile zip{zip_path, "rb"}; + Common::FS::IOFile zip{zip_path, "rb"}; const auto size = zip.GetSize(); std::vector<u8> bytes(size); if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { @@ -365,8 +365,7 @@ bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) std::thread([this, title, &progress] { SynchronizeInternal(applet_manager, dir_getter, title, progress); - }) - .detach(); + }).detach(); return true; } @@ -377,8 +376,7 @@ bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name, std::thread([this, title, name, &progress] { SynchronizeInternal(applet_manager, dir_getter, title, progress, name); - }) - .detach(); + }).detach(); return true; } @@ -422,7 +420,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - FileUtil::Delete(path); + Common::FS::Delete(path); } HandleDownloadDisplayResult(applet_manager, res); @@ -430,7 +428,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) } } - FileUtil::IOFile bin{path, "rb"}; + Common::FS::IOFile bin{path, "rb"}; const auto size = bin.GetSize(); std::vector<u8> bytes(size); if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index 603b64d4f..db0e06ca1 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -112,7 +112,7 @@ private: void GetImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_BCAT, "called"); - ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl)); + ctx.WriteBuffer(impl); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp index ab17a187e..a0ee116fa 100644 --- a/src/core/hle/service/caps/caps_c.cpp +++ b/src/core/hle/service/caps/caps_c.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/caps/caps_c.h" namespace Service::Capture { @@ -47,7 +49,7 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") { 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 +74,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..b110301d4 100644 --- a/src/core/hle/service/caps/caps_c.h +++ b/src/core/hle/service/caps/caps_c.h @@ -16,6 +16,9 @@ class CAPS_C final : public ServiceFramework<CAPS_C> { public: explicit CAPS_C(); ~CAPS_C() override; + +private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); }; } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index fffb2ecf9..e386470f7 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -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_u.cpp b/src/core/hle/service/caps/caps_u.cpp index f36d8de2d..8e2b83629 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp @@ -31,8 +31,7 @@ public: CAPS_U::CAPS_U() : ServiceFramework("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"}, @@ -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 diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h index 689364de4..e04e56bbc 100644 --- a/src/core/hle/service/caps/caps_u.h +++ b/src/core/hle/service/caps/caps_u.h @@ -18,6 +18,7 @@ public: ~CAPS_U() override; private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); void GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx); }; diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index a41c73c48..c2737a365 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -160,7 +160,7 @@ private: return; } - ctx.WriteBuffer(key.data(), key.size()); + ctx.WriteBuffer(key); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index cadc03805..54a5fb84b 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -36,7 +36,7 @@ constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000; static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, std::string_view dir_name_) { - std::string dir_name(FileUtil::SanitizePath(dir_name_)); + std::string dir_name(Common::FS::SanitizePath(dir_name_)); if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\") return base; @@ -53,9 +53,13 @@ std::string VfsDirectoryServiceWrapper::GetName() const { } ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const { - std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - auto file = dir->CreateFile(FileUtil::GetFilename(path)); + std::string path(Common::FS::SanitizePath(path_)); + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); + // dir can be nullptr if path contains subdirectories, create those prior to creating the file. + if (dir == nullptr) { + dir = backing->CreateSubdirectory(Common::FS::GetParentPath(path)); + } + auto file = dir->CreateFile(Common::FS::GetFilename(path)); if (file == nullptr) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; @@ -68,17 +72,17 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 } ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { - std::string path(FileUtil::SanitizePath(path_)); + std::string path(Common::FS::SanitizePath(path_)); if (path.empty()) { // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but... return RESULT_SUCCESS; } - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) { + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); + if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; } - if (!dir->DeleteFile(FileUtil::GetFilename(path))) { + if (!dir->DeleteFile(Common::FS::GetFilename(path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -87,11 +91,11 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons } ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { - std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty()) + 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()) dir = backing; - auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path)); + auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path)); if (new_dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; @@ -100,9 +104,9 @@ ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) } ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const { - std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) { + std::string path(Common::FS::SanitizePath(path_)); + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); + if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -110,9 +114,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) } ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const { - std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); - if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) { + std::string path(Common::FS::SanitizePath(path_)); + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); + if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -120,10 +124,10 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str } ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const { - const std::string sanitized_path(FileUtil::SanitizePath(path)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(sanitized_path)); + const std::string sanitized_path(Common::FS::SanitizePath(path)); + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path)); - if (!dir->CleanSubdirectoryRecursive(FileUtil::GetFilename(sanitized_path))) { + if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -133,14 +137,14 @@ ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::stri ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, const std::string& dest_path_) const { - std::string src_path(FileUtil::SanitizePath(src_path_)); - std::string dest_path(FileUtil::SanitizePath(dest_path_)); + std::string src_path(Common::FS::SanitizePath(src_path_)); + std::string dest_path(Common::FS::SanitizePath(dest_path_)); auto src = backing->GetFileRelative(src_path); - if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { + if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { // Use more-optimized vfs implementation rename. if (src == nullptr) return FileSys::ERROR_PATH_NOT_FOUND; - if (!src->Rename(FileUtil::GetFilename(dest_path))) { + if (!src->Rename(Common::FS::GetFilename(dest_path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -158,7 +162,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(), "Could not write all of the bytes but everything else has succeded."); - if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) { + if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -168,14 +172,14 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, const std::string& dest_path_) const { - std::string src_path(FileUtil::SanitizePath(src_path_)); - std::string dest_path(FileUtil::SanitizePath(dest_path_)); + std::string src_path(Common::FS::SanitizePath(src_path_)); + std::string dest_path(Common::FS::SanitizePath(dest_path_)); auto src = GetDirectoryRelativeWrapped(backing, src_path); - if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { + if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { // Use more-optimized vfs implementation rename. if (src == nullptr) return FileSys::ERROR_PATH_NOT_FOUND; - if (!src->Rename(FileUtil::GetFilename(dest_path))) { + if (!src->Rename(Common::FS::GetFilename(dest_path))) { // TODO(DarkLordZach): Find a better error code for this return RESULT_UNKNOWN; } @@ -194,7 +198,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_pa ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_, FileSys::Mode mode) const { - const std::string path(FileUtil::SanitizePath(path_)); + const std::string path(Common::FS::SanitizePath(path_)); std::string_view npath = path; while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) { npath.remove_prefix(1); @@ -214,7 +218,7 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std:: } ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) { - std::string path(FileUtil::SanitizePath(path_)); + std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, path); if (dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this @@ -225,11 +229,11 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( const std::string& path_) const { - std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); + std::string path(Common::FS::SanitizePath(path_)); + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) return FileSys::ERROR_PATH_NOT_FOUND; - auto filename = FileUtil::GetFilename(path); + auto filename = Common::FS::GetFilename(path); // TODO(Subv): Some games use the '/' path, find out what this means. if (filename.empty()) return MakeResult(FileSys::EntryType::Directory); @@ -307,7 +311,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( } ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const { + 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()); @@ -319,15 +323,15 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( } ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const { + 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), descriptor.DebugInfo()); + static_cast<u8>(space), attribute.DebugInfo()); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->Open(space, descriptor); + return save_data_factory->Open(space, attribute); } ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace( @@ -375,7 +379,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( 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; } @@ -691,13 +695,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove sdmc_factory = nullptr; } - auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), + auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir), FileSys::Mode::ReadWrite); - auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), + auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), FileSys::Mode::ReadWrite); - auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), + auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), FileSys::Mode::ReadWrite); - auto dump_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), + auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), FileSys::Mode::ReadWrite); if (bis_factory == nullptr) { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 1b0a6a949..6dbbf0b2b 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -31,7 +31,7 @@ enum class SaveDataSpaceId : u8; enum class SaveDataType : u8; enum class StorageId : u8; -struct SaveDataDescriptor; +struct SaveDataAttribute; struct SaveDataSize; } // namespace FileSys @@ -69,9 +69,9 @@ public: ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const; ResultVal<FileSys::VirtualDir> CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const; + FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; ResultVal<FileSys::VirtualDir> OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const; + FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const; ResultVal<FileSys::VirtualDir> OpenSDMC() const; ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 20c331b77..649128be4 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -696,8 +696,8 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) {67, nullptr, "FindSaveDataWithFilter"}, {68, nullptr, "OpenSaveDataInfoReaderBySaveDataFilter"}, {69, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataAttribute"}, - {70, nullptr, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"}, - {71, nullptr, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"}, + {70, &FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"}, + {71, &FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"}, {80, nullptr, "OpenSaveDataMetaFile"}, {81, nullptr, "OpenSaveDataTransferManager"}, {82, nullptr, "OpenSaveDataTransferManagerVersion2"}, @@ -812,7 +812,7 @@ void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); + auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>(); [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); u128 uid = rp.PopRaw<u128>(); @@ -826,31 +826,40 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) { } void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_FS, "called."); + IPC::RequestParser rp{ctx}; struct Parameters { - FileSys::SaveDataSpaceId save_data_space_id; - FileSys::SaveDataDescriptor descriptor; + FileSys::SaveDataSpaceId space_id; + FileSys::SaveDataAttribute attribute; }; - IPC::RequestParser rp{ctx}; const auto parameters = rp.PopRaw<Parameters>(); - auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor); + LOG_INFO(Service_FS, "called."); + + auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute); if (dir.Failed()) { IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); return; } - FileSys::StorageId id; - if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) { + FileSys::StorageId id{}; + switch (parameters.space_id) { + case FileSys::SaveDataSpaceId::NandUser: id = FileSys::StorageId::NandUser; - } else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem || - parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) { + break; + case FileSys::SaveDataSpaceId::SdCardSystem: + case FileSys::SaveDataSpaceId::SdCardUser: id = FileSys::StorageId::SdCard; - } else { + break; + case FileSys::SaveDataSpaceId::NandSystem: id = FileSys::StorageId::NandSystem; + break; + case FileSys::SaveDataSpaceId::TemporaryStorage: + case FileSys::SaveDataSpaceId::ProperSystem: + case FileSys::SaveDataSpaceId::SafeMode: + UNREACHABLE(); } auto filesystem = @@ -876,22 +885,38 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc)); } -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)); +void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called."); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_FS, "called"); +void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + struct Parameters { + FileSys::SaveDataSpaceId space_id; + FileSys::SaveDataAttribute attribute; + }; + + const auto parameters = rp.PopRaw<Parameters>(); + // 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={}, space_id={}, attribute.title_id={:016X}\n" + "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n" + "attribute.type={}, attribute.rank={}, attribute.index={}", + flags, static_cast<u32>(parameters.space_id), parameters.attribute.title_id, + parameters.attribute.user_id[1], parameters.attribute.user_id[0], + parameters.attribute.save_id, static_cast<u32>(parameters.attribute.type), + static_cast<u32>(parameters.attribute.rank), parameters.attribute.index); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.PushEnum(log_mode); + rb.Push(flags); } void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { @@ -966,6 +991,24 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); } +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)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(log_mode); +} + void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { const auto raw = ctx.ReadBuffer(); auto log = Common::StringFromFixedZeroTerminatedBuffer( diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index dfb3e395b..4964e874e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -43,11 +43,13 @@ private: void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx); void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx); void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx); - void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); - void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); + void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx); + void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(Kernel::HLERequestContext& ctx); void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); + void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx); void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx); void OpenMultiCommitManager(Kernel::HLERequestContext& ctx); 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/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index cb35919e9..ad251ed4a 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -39,33 +39,36 @@ void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - cur_entry.attribute.connected.Assign(1); - auto& pad = cur_entry.pad_state; - using namespace Settings::NativeButton; - pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + if (Settings::values.debug_pad_enabled) { + cur_entry.attribute.connected.Assign(1); + auto& pad = cur_entry.pad_state; - const auto [stick_l_x_f, stick_l_y_f] = - analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = - analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); - cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); - cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); - cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); - cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + using namespace Settings::NativeButton; + pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + } std::memcpy(data, &shared_memory, sizeof(SharedMemory)); } diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index feae89525..0b896d5ad 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -40,15 +40,16 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, cur_entry.key.fill(0); cur_entry.modifier = 0; - - 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)); - } - - for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { - cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i); + 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)); + } + + for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { + cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i); + } } - std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index ef67ad690..2de4ed348 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -24,6 +24,7 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; constexpr u32 MAX_NPAD_ID = 7; +constexpr std::size_t HANDHELD_INDEX = 8; constexpr std::array<u32, 10> npad_id_list{ 0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN, }; @@ -33,19 +34,41 @@ enum class JoystickId : std::size_t { Joystick_Right, }; -static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) { +Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad( + Settings::ControllerType type) { switch (type) { case Settings::ControllerType::ProController: - return Controller_NPad::NPadControllerType::ProController; - case Settings::ControllerType::DualJoycon: - return Controller_NPad::NPadControllerType::JoyDual; + return NPadControllerType::ProController; + case Settings::ControllerType::DualJoyconDetached: + return NPadControllerType::JoyDual; case Settings::ControllerType::LeftJoycon: - return Controller_NPad::NPadControllerType::JoyLeft; + return NPadControllerType::JoyLeft; case Settings::ControllerType::RightJoycon: - return Controller_NPad::NPadControllerType::JoyRight; + return NPadControllerType::JoyRight; + case Settings::ControllerType::Handheld: + return NPadControllerType::Handheld; default: UNREACHABLE(); - return Controller_NPad::NPadControllerType::JoyDual; + return NPadControllerType::ProController; + } +} + +Settings::ControllerType Controller_NPad::MapNPadToSettingsType( + Controller_NPad::NPadControllerType type) { + switch (type) { + case NPadControllerType::ProController: + return Settings::ControllerType::ProController; + case NPadControllerType::JoyDual: + return Settings::ControllerType::DualJoyconDetached; + case NPadControllerType::JoyLeft: + return Settings::ControllerType::LeftJoycon; + case NPadControllerType::JoyRight: + return Settings::ControllerType::RightJoycon; + case NPadControllerType::Handheld: + return Settings::ControllerType::Handheld; + default: + UNREACHABLE(); + return Settings::ControllerType::ProController; } } @@ -60,9 +83,9 @@ std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) { case 6: case 7: return npad_id; - case 8: + case HANDHELD_INDEX: case NPAD_HANDHELD: - return 8; + return HANDHELD_INDEX; case 9: case NPAD_UNKNOWN: return 9; @@ -83,38 +106,48 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) { case 6: case 7: return static_cast<u32>(index); - case 8: + case HANDHELD_INDEX: return NPAD_HANDHELD; case 9: return NPAD_UNKNOWN; default: UNIMPLEMENTED_MSG("Unknown npad index {}", index); return 0; - }; + } } Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {} Controller_NPad::~Controller_NPad() = default; -void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { +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(); return; } controller.joy_styles.raw = 0; // Zero out controller.device_type.raw = 0; + controller.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; + break; case NPadControllerType::Handheld: controller.joy_styles.handheld.Assign(1); controller.device_type.handheld.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; controller.properties.is_vertical.Assign(1); controller.properties.use_plus.Assign(1); controller.properties.use_minus.Assign(1); + controller.pad_assignment = NPadAssignments::Dual; break; case NPadControllerType::JoyDual: controller.joy_styles.joycon_dual.Assign(1); @@ -144,14 +177,6 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.device_type.pokeball.Assign(1); controller.pad_assignment = NPadAssignments::Single; 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; - break; } controller.single_color_error = ColorReadError::ReadOk; @@ -168,7 +193,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { 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(); + + SignalStyleSetChangedEvent(IndexToNPad(controller_idx)); } void Controller_NPad::OnInit() { @@ -192,36 +218,25 @@ void Controller_NPad::OnInit() { style.pokeball.Assign(1); } - std::transform( - Settings::values.players.begin(), Settings::values.players.end(), - connected_controllers.begin(), [](const Settings::PlayerInput& player) { - return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected}; - }); - - std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8, - [](const ControllerHolder& holder) { return holder.is_connected; }); + std::transform(Settings::values.players.begin(), Settings::values.players.end(), + connected_controllers.begin(), [](const Settings::PlayerInput& player) { + return ControllerHolder{MapSettingsTypeToNPad(player.controller_type), + player.connected}; + }); // Account for handheld - if (connected_controllers[8].is_connected) - connected_controllers[8].type = NPadControllerType::Handheld; + if (connected_controllers[HANDHELD_INDEX].is_connected) { + connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld; + } supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), npad_id_list.size() * sizeof(u32)); - // Add a default dual joycon controller if none are present. - if (std::none_of(connected_controllers.begin(), connected_controllers.end(), - [](const ControllerHolder& controller) { return controller.is_connected; })) { - supported_npad_id_types.resize(npad_id_list.size()); - std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), - npad_id_list.size() * sizeof(u32)); - AddNewController(NPadControllerType::JoyDual); - } - for (std::size_t i = 0; i < connected_controllers.size(); ++i) { const auto& controller = connected_controllers[i]; if (controller.is_connected) { - AddNewControllerAt(controller.type, IndexToNPad(i)); + AddNewControllerAt(controller.type, i); } } } @@ -235,6 +250,9 @@ 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].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, + players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, + motions[i].begin(), Input::CreateDevice<Input::MotionDevice>); } } @@ -242,7 +260,7 @@ void Controller_NPad::OnRelease() {} 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; } @@ -251,66 +269,77 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { auto& rstick_entry = npad_pad_states[controller_idx].r_stick; const auto& button_state = buttons[controller_idx]; const auto& analog_state = sticks[controller_idx]; + const auto& motion_state = motions[controller_idx]; const auto [stick_l_x_f, stick_l_y_f] = analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); const auto [stick_r_x_f, stick_r_y_f] = 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()); + } } void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t data_len) { - if (!IsControllerActivated()) + if (!IsControllerActivated()) { return; + } 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, @@ -344,6 +373,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* continue; } const u32 npad_index = static_cast<u32>(i); + RequestPadStateUpdate(npad_index); auto& pad_state = npad_pad_states[npad_index]; @@ -360,13 +390,37 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; libnx_entry.connection_status.raw = 0; + libnx_entry.connection_status.IsConnected.Assign(1); + auto& full_sixaxis_entry = + npad.sixaxis_full.sixaxis[npad.sixaxis_full.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: + main_controller.connection_status.raw = 0; + main_controller.connection_status.IsConnected.Assign(1); + main_controller.connection_status.IsWired.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); + 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); @@ -375,57 +429,52 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* 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); 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.IsConnected.Assign(1); - - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); - libnx_entry.connection_status.IsConnected.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); 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.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); 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.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); break; case NPadControllerType::Pokeball: pokeball_entry.connection_status.raw = 0; - pokeball_entry.connection_status.IsConnected.Assign(1); - pokeball_entry.connection_status.IsWired.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; 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.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; - break; } // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate @@ -440,6 +489,143 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* shared_memory_entries.size() * sizeof(NPadEntry)); } +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_full, &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) { + 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& main_controller = + npad.main_controller_states.npad[npad.main_controller_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& 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]; + + auto& full_sixaxis_entry = + npad.sixaxis_full.sixaxis[npad.sixaxis_full.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: + if (sixaxis_sensors_enabled && motions[i][0]) { + 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: + if (sixaxis_sensors_enabled && motions[i][0]) { + 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: + if (sixaxis_sensors_enabled && motions[i][0]) { + // Set motion for the left joycon + 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.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: + if (sixaxis_sensors_enabled && motions[i][0]) { + 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: + if (sixaxis_sensors_enabled && motions[i][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::Pokeball: + break; + } + } + std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), + shared_memory_entries.size() * sizeof(NPadEntry)); +} + void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { style.raw = style_set.raw; } @@ -453,26 +639,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); std::memcpy(supported_npad_id_types.data(), data, length); - for (std::size_t i = 0; i < connected_controllers.size(); i++) { - auto& controller = connected_controllers[i]; - if (!controller.is_connected) { - continue; - } - const auto requested_controller = - i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type) - : NPadControllerType::Handheld; - if (!IsControllerSupported(requested_controller)) { - const auto is_handheld = requested_controller == NPadControllerType::Handheld; - if (is_handheld) { - controller.type = NPadControllerType::None; - controller.is_connected = false; - AddNewController(requested_controller); - } else { - controller.type = requested_controller; - InitNewlyAddedControler(i); - } - } - } } void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) { @@ -492,6 +658,14 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { return hold_type; } +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::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { const std::size_t npad_index = NPadIdToIndex(npad_id); ASSERT(npad_index < shared_memory_entries.size()); @@ -500,70 +674,86 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) } } -void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, +void Controller_NPad::VibrateController(const std::vector<u32>& controllers, const std::vector<Vibration>& vibrations) { - LOG_DEBUG(Service_HID, "(STUBBED) called"); + LOG_TRACE(Service_HID, "called"); - if (!can_controllers_vibrate) { + if (!Settings::values.vibration_enabled || !can_controllers_vibrate) { 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 + bool success = true; + for (std::size_t i = 0; i < controllers.size(); ++i) { + if (!connected_controllers[i].is_connected) { + continue; + } + using namespace Settings::NativeButton; + const auto& button_state = buttons[i]; + if (button_state[A - BUTTON_HID_BEGIN]) { + if (button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay( + vibrations[0].amp_high, vibrations[0].amp_low, vibrations[0].freq_high, + vibrations[0].freq_low)) { + success = false; + } } } - last_processed_vibration = vibrations.back(); + if (success) { + last_processed_vibration = vibrations.back(); + } +} + +Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { + return last_processed_vibration; } std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { - // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should - // be signalled at least once, and signaled after a new controller is connected? const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; return styleset_event.readable; } -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)].writable->Signal(); } -void Controller_NPad::AddNewController(NPadControllerType controller) { - controller = DecideBestController(controller); - if (controller == NPadControllerType::Handheld) { - connected_controllers[8] = {controller, true}; - InitNewlyAddedControler(8); - return; - } - const auto pos = - std::find_if(connected_controllers.begin(), connected_controllers.end() - 2, - [](const ControllerHolder& holder) { return !holder.is_connected; }); - if (pos == connected_controllers.end() - 2) { - LOG_ERROR(Service_HID, "Cannot connect any more controllers!"); +void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { + UpdateControllerAt(controller, npad_index, true); +} + +void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, + bool connected) { + if (!connected) { + DisconnectNPadAtIndex(npad_index); return; } - const auto controller_id = std::distance(connected_controllers.begin(), pos); - connected_controllers[controller_id] = {controller, true}; - InitNewlyAddedControler(controller_id); -} -void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) { - controller = DecideBestController(controller); if (controller == NPadControllerType::Handheld) { - connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true}; - InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD)); + Settings::values.players[HANDHELD_INDEX].controller_type = + MapNPadToSettingsType(controller); + Settings::values.players[HANDHELD_INDEX].connected = true; + connected_controllers[HANDHELD_INDEX] = {controller, true}; + InitNewlyAddedController(HANDHELD_INDEX); return; } - connected_controllers[NPadIdToIndex(npad_id)] = {controller, true}; - InitNewlyAddedControler(NPadIdToIndex(npad_id)); + Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller); + Settings::values.players[npad_index].connected = true; + connected_controllers[npad_index] = {controller, true}; + InitNewlyAddedController(npad_index); } -void Controller_NPad::ConnectNPad(u32 npad_id) { - connected_controllers[NPadIdToIndex(npad_id)].is_connected = true; +void Controller_NPad::DisconnectNPad(u32 npad_id) { + DisconnectNPadAtIndex(NPadIdToIndex(npad_id)); } -void Controller_NPad::DisconnectNPad(u32 npad_id) { - connected_controllers[NPadIdToIndex(npad_id)].is_connected = false; +void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) { + Settings::values.players[npad_index].connected = false; + connected_controllers[npad_index].is_connected = false; + + auto& controller = shared_memory_entries[npad_index]; + controller.joy_styles.raw = 0; // Zero out + controller.device_type.raw = 0; + controller.properties.raw = 0; + + SignalStyleSetChangedEvent(IndexToNPad(npad_index)); } void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { @@ -574,6 +764,30 @@ 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::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 @@ -599,8 +813,8 @@ bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) { std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type); - InitNewlyAddedControler(npad_index_1); - InitNewlyAddedControler(npad_index_2); + AddNewControllerAt(connected_controllers[npad_index_1].type, npad_index_1); + AddNewControllerAt(connected_controllers[npad_index_2].type, npad_index_2); return true; } @@ -614,11 +828,11 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { case 0: return LedPattern{1, 0, 0, 0}; case 1: - return LedPattern{0, 1, 0, 0}; + return LedPattern{1, 1, 0, 0}; case 2: - return LedPattern{0, 0, 1, 0}; + return LedPattern{1, 1, 1, 0}; case 3: - return LedPattern{0, 0, 0, 1}; + return LedPattern{1, 1, 1, 1}; case 4: return LedPattern{1, 0, 0, 1}; case 5: @@ -628,9 +842,17 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { case 7: return LedPattern{0, 1, 1, 0}; default: - UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id); return LedPattern{0, 0, 0, 0}; - }; + } +} + +bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const { + return unintended_home_button_input_protection[NPadIdToIndex(npad_id)]; +} + +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::SetVibrationEnabled(bool can_vibrate) { @@ -651,13 +873,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; } @@ -665,7 +887,7 @@ 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; } @@ -713,92 +935,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 5d4c58a43..fd5c5a6eb 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -32,6 +32,10 @@ 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; @@ -74,6 +78,12 @@ public: Single = 1, }; + enum class NpadHandheldActivationMode : u64 { + Dual = 0, + Single = 1, + None = 2, + }; + enum class NPadControllerType { None, ProController, @@ -110,22 +120,34 @@ public: void SetHoldType(NpadHoldType joy_hold_type); NpadHoldType GetHoldType() const; + void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode); + NpadHandheldActivationMode GetNpadHandheldActivationMode() const; + void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode); - void VibrateController(const std::vector<u32>& controller_ids, + void VibrateController(const std::vector<u32>& controllers, const std::vector<Vibration>& vibrations); - std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; - void AddNewController(NPadControllerType controller); - void AddNewControllerAt(NPadControllerType controller, u32 npad_id); + std::shared_ptr<Kernel::ReadableEvent> 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 ConnectNPad(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); LedPattern GetLedPattern(u32 npad_id); + bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const; + void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id); void SetVibrationEnabled(bool can_vibrate); bool IsVibrationEnabled() const; void ClearAllConnectedControllers(); @@ -133,6 +155,7 @@ public: 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); @@ -141,6 +164,8 @@ public: // Specifically for cheat engine and other features. u32 GetAndResetPressState(); + static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type); + static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type); static std::size_t NPadIdToIndex(u32 npad_id); static u32 IndexToNPad(std::size_t index); @@ -244,6 +269,24 @@ private: }; static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric 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{}; + s64_le always_one{1}; + }; + 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"); + enum class ColorReadError : u32_le { ReadOk = 0, ColorDoesntExist = 1, @@ -273,6 +316,13 @@ private: }; }; + struct MotionDevice { + Common::Vec3f accel; + Common::Vec3f gyro; + Common::Vec3f rotation; + std::array<Common::Vec3f, 3> orientation; + }; + struct NPadEntry { NPadType joy_styles; NPadAssignments pad_assignment; @@ -292,9 +342,12 @@ private: 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 + SixAxisGeneric sixaxis_full; + 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); @@ -309,31 +362,38 @@ private: bool is_connected; }; - void InitNewlyAddedControler(std::size_t controller_idx); + 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{}; NPadType 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 MotionArray = std::array< + std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>, + 10>; + ButtonArray buttons; + StickArray sticks; + MotionArray motions; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; + NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; // Each controller should have their own styleset changed event std::array<Kernel::EventPair, 10> styleset_changed_events; Vibration last_processed_vibration{}; 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}; + bool sixaxis_at_rest{true}; std::array<ControllerPad, 10> npad_pad_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..0df395e85 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -40,9 +40,14 @@ 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(); + bool pressed = false; + float x, y; + std::tie(x, y, pressed) = touch_device->GetStatus(); auto& touch_entry = cur_entry.states[0]; touch_entry.attribute.raw = 0; + if (!pressed && touch_btn_device) { + std::tie(x, y, pressed) = touch_btn_device->GetStatus(); + } 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); @@ -63,5 +68,10 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin void Controller_Touchscreen::OnLoadInputDevices() { touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); + if (Settings::values.use_touch_from_button) { + touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button"); + } else { + touch_btn_device.reset(); + } } } // 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..4d9042adc 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -68,6 +68,7 @@ private: "TouchScreenSharedMemory is an invalid size"); TouchScreenSharedMemory shared_memory{}; std::unique_ptr<Input::TouchDevice> touch_device; + std::unique_ptr<Input::TouchDevice> touch_btn_device; s64_le last_touch{}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index e9020e0dc..71dbaba7f 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -38,10 +38,10 @@ namespace Service::HID { // Updating period for each HID device. -// TODO(ogniK): Find actual polling rate of hid -constexpr s64 pad_update_ticks = static_cast<s64>(1000000000 / 66); -[[maybe_unused]] constexpr s64 accelerometer_update_ticks = static_cast<s64>(1000000000 / 100); -[[maybe_unused]] constexpr s64 gyroscope_update_ticks = static_cast<s64>(1000000000 / 100); +// 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{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) @@ -75,14 +75,19 @@ IAppletResource::IAppletResource(Core::System& system) GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); // Register update callbacks - pad_update_event = - Core::Timing::CreateEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 ns_late) { - UpdateControllers(userdata, ns_late); + pad_update_event = Core::Timing::CreateEvent( + "HID::UpdatePadCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + UpdateControllers(user_data, ns_late); + }); + motion_update_event = Core::Timing::CreateEvent( + "HID::MotionPadCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + UpdateMotion(user_data, ns_late); }); - // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) - - system.CoreTiming().ScheduleEvent(pad_update_ticks, pad_update_event); + system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event); + system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event); ReloadInputDevices(); } @@ -107,7 +112,8 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(shared_mem); } -void IAppletResource::UpdateControllers(u64 userdata, s64 ns_late) { +void IAppletResource::UpdateControllers(std::uintptr_t user_data, + std::chrono::nanoseconds ns_late) { auto& core_timing = system.CoreTiming(); const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); @@ -118,7 +124,17 @@ void IAppletResource::UpdateControllers(u64 userdata, s64 ns_late) { controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); } - core_timing.ScheduleEvent(pad_update_ticks - ns_late, pad_update_event); + 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(); + + for (const auto& controller : controllers) { + controller->OnMotionUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); + } + + core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event); } class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { @@ -163,8 +179,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"}, @@ -172,7 +188,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, {68, nullptr, "IsSixAxisSensorFusionEnabled"}, - {69, nullptr, "EnableSixAxisSensorFusion"}, + {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"}, {70, nullptr, "SetSixAxisSensorFusionParameters"}, {71, nullptr, "GetSixAxisSensorFusionParameters"}, {72, nullptr, "ResetSixAxisSensorFusionParameters"}, @@ -208,8 +224,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"}, @@ -328,6 +344,31 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { rb.Push(0); } +void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; @@ -432,6 +473,19 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto enable{rp.Pop<bool>()}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto handle{rp.Pop<u32>()}; @@ -483,13 +537,13 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { const auto handle{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, + 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::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { @@ -670,13 +724,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); + 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); + controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -711,8 +767,11 @@ void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { const auto applet_resource_user_id{rp.Pop<u64>()}; const auto mode{rp.Pop<u64>()}; - 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={}, mode={}", applet_resource_user_id, + mode); + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadHandheldActivationMode(Controller_NPad::NpadHandheldActivationMode{mode}); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -722,11 +781,13 @@ 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.Push<u64>( + static_cast<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetNpadHandheldActivationMode())); } void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { @@ -748,6 +809,40 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { } } +void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_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<bool>(controller.IsUnintendedHomeButtonInputProtectionEnabled(npad_id)); +} + +void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto unintended_home_button_input_protection{rp.Pop<bool>()}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, + "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={}," + "applet_resource_user_id={}", + npad_id, unintended_home_button_input_protection, applet_resource_user_id); + + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.SetUnintendedHomeButtonInputProtectionEnabled( + unintended_home_button_input_protection, npad_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>()}; @@ -769,18 +864,18 @@ void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop<u32>()}; + const auto controller{rp.Pop<u32>()}; const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, + LOG_DEBUG(Service_HID, "called, controller={}, applet_resource_user_id={}", controller, 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}); + .VibrateController({controller}, {vibration_values}); } void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { @@ -798,8 +893,6 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { 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; }); applet_resource->GetController<Controller_NPad>(HidController::NPad) .VibrateController(controller_list, vibration_list); @@ -842,8 +935,7 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_vibrate{rp.Pop<bool>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetVibrationEnabled(can_vibrate); + Settings::values.vibration_enabled = can_vibrate; LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); @@ -856,8 +948,7 @@ void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push( - applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled()); + rb.Push(Settings::values.vibration_enabled); } void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 6fb048360..fd0372b18 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,10 +4,9 @@ #pragma once -#include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/service.h" +#include <chrono> -#include "controllers/controller_base.h" +#include "core/hle/service/hid/controllers/controller_base.h" #include "core/hle/service/service.h" namespace Core::Timing { @@ -65,11 +64,13 @@ private: } void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); - void UpdateControllers(u64 userdata, s64 cycles_late); + 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; + std::shared_ptr<Core::Timing::EventType> motion_update_event; Core::System& system; std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> @@ -87,6 +88,8 @@ private: void CreateAppletResource(Kernel::HLERequestContext& ctx); void ActivateXpad(Kernel::HLERequestContext& ctx); void GetXpadIDs(Kernel::HLERequestContext& ctx); + void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); + void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void ActivateDebugPad(Kernel::HLERequestContext& ctx); void ActivateTouchScreen(Kernel::HLERequestContext& ctx); void ActivateMouse(Kernel::HLERequestContext& ctx); @@ -96,6 +99,7 @@ private: void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); void StartSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); + void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx); void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); @@ -119,6 +123,8 @@ private: void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void SwapNpadAssignment(Kernel::HLERequestContext& ctx); + void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx); + void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx); void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); void SendVibrationValue(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 64a526b9e..d8cd10e31 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -310,7 +310,7 @@ public: ResultVal<VAddr> MapProcessCodeMemory(Kernel::Process* process, VAddr baseAddress, u64 size) const { - for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) { + for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) { auto& page_table{process->PageTable()}; const VAddr addr{GetRandomMapRegion(page_table, size)}; const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)}; @@ -331,8 +331,7 @@ public: ResultVal<VAddr> MapNro(Kernel::Process* process, VAddr nro_addr, std::size_t nro_size, VAddr bss_addr, std::size_t bss_size, std::size_t size) const { - - for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) { + for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) { auto& page_table{process->PageTable()}; VAddr addr{}; diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp index 4a1d1182e..4730070cb 100644 --- a/src/core/hle/service/mii/manager.cpp +++ b/src/core/hle/service/mii/manager.cpp @@ -47,66 +47,67 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { MiiStoreBitFields bf; std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); - MiiInfo info{}; - info.name = ResizeArray<char16_t, 10, 11>(data.data.name); - info.uuid = data.data.uuid; - info.font_region = static_cast<u8>(bf.font_region.Value()); - info.favorite_color = static_cast<u8>(bf.favorite_color.Value()); - info.gender = static_cast<u8>(bf.gender.Value()); - info.height = static_cast<u8>(bf.height.Value()); - info.build = static_cast<u8>(bf.build.Value()); - info.type = static_cast<u8>(bf.type.Value()); - info.region_move = static_cast<u8>(bf.region_move.Value()); - info.faceline_type = static_cast<u8>(bf.faceline_type.Value()); - info.faceline_color = static_cast<u8>(bf.faceline_color.Value()); - info.faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()); - info.faceline_make = static_cast<u8>(bf.faceline_makeup.Value()); - info.hair_type = static_cast<u8>(bf.hair_type.Value()); - info.hair_color = static_cast<u8>(bf.hair_color.Value()); - info.hair_flip = static_cast<u8>(bf.hair_flip.Value()); - info.eye_type = static_cast<u8>(bf.eye_type.Value()); - info.eye_color = static_cast<u8>(bf.eye_color.Value()); - info.eye_scale = static_cast<u8>(bf.eye_scale.Value()); - info.eye_aspect = static_cast<u8>(bf.eye_aspect.Value()); - info.eye_rotate = static_cast<u8>(bf.eye_rotate.Value()); - info.eye_x = static_cast<u8>(bf.eye_x.Value()); - info.eye_y = static_cast<u8>(bf.eye_y.Value()); - info.eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()); - info.eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()); - info.eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()); - info.eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()); - info.eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()); - info.eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()); - info.eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3); - info.nose_type = static_cast<u8>(bf.nose_type.Value()); - info.nose_scale = static_cast<u8>(bf.nose_scale.Value()); - info.nose_y = static_cast<u8>(bf.nose_y.Value()); - info.mouth_type = static_cast<u8>(bf.mouth_type.Value()); - info.mouth_color = static_cast<u8>(bf.mouth_color.Value()); - info.mouth_scale = static_cast<u8>(bf.mouth_scale.Value()); - info.mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()); - info.mouth_y = static_cast<u8>(bf.mouth_y.Value()); - info.beard_color = static_cast<u8>(bf.beard_color.Value()); - info.beard_type = static_cast<u8>(bf.beard_type.Value()); - info.mustache_type = static_cast<u8>(bf.mustache_type.Value()); - info.mustache_scale = static_cast<u8>(bf.mustache_scale.Value()); - info.mustache_y = static_cast<u8>(bf.mustache_y.Value()); - info.glasses_type = static_cast<u8>(bf.glasses_type.Value()); - info.glasses_color = static_cast<u8>(bf.glasses_color.Value()); - info.glasses_scale = static_cast<u8>(bf.glasses_scale.Value()); - info.glasses_y = static_cast<u8>(bf.glasses_y.Value()); - info.mole_type = static_cast<u8>(bf.mole_type.Value()); - info.mole_scale = static_cast<u8>(bf.mole_scale.Value()); - info.mole_x = static_cast<u8>(bf.mole_x.Value()); - info.mole_y = static_cast<u8>(bf.mole_y.Value()); - return info; + + return { + .uuid = data.data.uuid, + .name = ResizeArray<char16_t, 10, 11>(data.data.name), + .font_region = static_cast<u8>(bf.font_region.Value()), + .favorite_color = static_cast<u8>(bf.favorite_color.Value()), + .gender = static_cast<u8>(bf.gender.Value()), + .height = static_cast<u8>(bf.height.Value()), + .build = static_cast<u8>(bf.build.Value()), + .type = static_cast<u8>(bf.type.Value()), + .region_move = static_cast<u8>(bf.region_move.Value()), + .faceline_type = static_cast<u8>(bf.faceline_type.Value()), + .faceline_color = static_cast<u8>(bf.faceline_color.Value()), + .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()), + .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()), + .hair_type = static_cast<u8>(bf.hair_type.Value()), + .hair_color = static_cast<u8>(bf.hair_color.Value()), + .hair_flip = static_cast<u8>(bf.hair_flip.Value()), + .eye_type = static_cast<u8>(bf.eye_type.Value()), + .eye_color = static_cast<u8>(bf.eye_color.Value()), + .eye_scale = static_cast<u8>(bf.eye_scale.Value()), + .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()), + .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()), + .eye_x = static_cast<u8>(bf.eye_x.Value()), + .eye_y = static_cast<u8>(bf.eye_y.Value()), + .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()), + .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()), + .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()), + .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()), + .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()), + .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()), + .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3), + .nose_type = static_cast<u8>(bf.nose_type.Value()), + .nose_scale = static_cast<u8>(bf.nose_scale.Value()), + .nose_y = static_cast<u8>(bf.nose_y.Value()), + .mouth_type = static_cast<u8>(bf.mouth_type.Value()), + .mouth_color = static_cast<u8>(bf.mouth_color.Value()), + .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()), + .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()), + .mouth_y = static_cast<u8>(bf.mouth_y.Value()), + .beard_color = static_cast<u8>(bf.beard_color.Value()), + .beard_type = static_cast<u8>(bf.beard_type.Value()), + .mustache_type = static_cast<u8>(bf.mustache_type.Value()), + .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()), + .mustache_y = static_cast<u8>(bf.mustache_y.Value()), + .glasses_type = static_cast<u8>(bf.glasses_type.Value()), + .glasses_color = static_cast<u8>(bf.glasses_color.Value()), + .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()), + .glasses_y = static_cast<u8>(bf.glasses_y.Value()), + .mole_type = static_cast<u8>(bf.mole_type.Value()), + .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()), + }; } u16 GenerateCrc16(const void* data, std::size_t size) { s32 crc{}; - for (int i = 0; i < size; i++) { - crc ^= reinterpret_cast<const u8*>(data)[i] << 8; - for (int j = 0; j < 8; j++) { + for (std::size_t i = 0; i < size; i++) { + crc ^= static_cast<const u8*>(data)[i] << 8; + for (std::size_t j = 0; j < 8; j++) { crc <<= 1; if ((crc & 0x10000) != 0) { crc = (crc ^ 0x1021) & 0xFFFF; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 4b79eb81d..a0469ffbd 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <atomic> #include "common/logging/log.h" @@ -72,10 +73,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"); @@ -127,7 +128,7 @@ private: const u32 array_size = rp.Pop<u32>(); LOG_DEBUG(Service_NFP, "called, array_size={}", array_size); - ctx.WriteBuffer(&device_handle, sizeof(device_handle)); + ctx.WriteBuffer(device_handle); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -213,14 +214,16 @@ 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; - ctx.WriteBuffer(&tag_info, sizeof(TagInfo)); + const auto& amiibo = nfp_interface.GetAmiiboBuffer(); + const TagInfo tag_info{ + .uuid = amiibo.uuid, + .uuid_length = static_cast<u8>(tag_info.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,8 +239,8 @@ private: LOG_DEBUG(Service_NFP, "called"); IPC::ResponseBuilder rb{ctx, 2}; - auto amiibo = nfp_interface.GetAmiiboBuffer(); - ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info)); + const auto& amiibo = nfp_interface.GetAmiiboBuffer(); + ctx.WriteBuffer(amiibo.model_info); rb.Push(RESULT_SUCCESS); } @@ -283,7 +286,7 @@ private: CommonInfo common_info{}; common_info.application_area_size = 0; - ctx.WriteBuffer(&common_info, sizeof(CommonInfo)); + ctx.WriteBuffer(common_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 01ddcdbd6..2e9d95195 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -9,6 +9,7 @@ #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 { @@ -174,6 +175,16 @@ private: 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"); @@ -235,7 +246,7 @@ IGeneralService::IGeneralService(Core::System& system) {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"}, diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 886450be2..58ee1f712 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -5,6 +5,7 @@ #include "common/logging/log.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" diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 1b52511a5..0240d6643 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -21,8 +21,9 @@ namespace Service::Nvidia::Devices { /// implement the ioctl interface. class nvdevice { public: - explicit nvdevice(Core::System& system) : system{system} {}; + explicit nvdevice(Core::System& system) : system{system} {} virtual ~nvdevice() = default; + union Ioctl { u32_le raw; BitField<0, 8, u32> cmd; 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 195421cc0..39bd2a45b 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -16,11 +16,12 @@ #include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { + namespace NvErrCodes { -enum { - InvalidNmapHandle = -22, -}; -} +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)) {} @@ -49,8 +50,9 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std: break; } - if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) + if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) { return Remap(input, output); + } UNIMPLEMENTED_MSG("Unimplemented ioctl command"); return 0; @@ -59,6 +61,7 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std: u32 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; @@ -67,53 +70,61 @@ u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& ou u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { IoctlAllocSpace params{}; std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages, params.page_size, params.flags); - auto& gpu = system.GPU(); - const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)}; - if (params.flags & 1) { - params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1); + const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)}; + if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) { + params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size); } else { - params.offset = gpu.MemoryManager().AllocateSpace(size, params.align); + params.offset = system.GPU().MemoryManager().Allocate(size, params.align); + } + + auto result{NvErrCodes::Success}; + if (!params.offset) { + LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size); + result = NvErrCodes::OutOfMemory; } std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return result; } u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { - std::size_t num_entries = input.size() / sizeof(IoctlRemapEntry); + const auto num_entries = input.size() / sizeof(IoctlRemapEntry); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries); + LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries); + auto result{NvErrCodes::Success}; std::vector<IoctlRemapEntry> entries(num_entries); std::memcpy(entries.data(), input.data(), input.size()); - auto& gpu = system.GPU(); for (const auto& entry : entries) { - LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", - entry.offset, entry.nvmap_handle, entry.pages); - GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10; - auto object = nvmap_dev->GetObject(entry.nvmap_handle); + LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", + entry.offset, entry.nvmap_handle, entry.pages); + + const auto object{nvmap_dev->GetObject(entry.nvmap_handle)}; if (!object) { - LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle); - std::memcpy(output.data(), entries.data(), output.size()); - return static_cast<u32>(NvErrCodes::InvalidNmapHandle); + LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle); + result = NvErrCodes::InvalidInput; + break; } - ASSERT(object->status == nvmap::Object::Status::Allocated); + const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10}; + const auto size{static_cast<u64>(entry.pages) << 0x10}; + const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10}; + const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)}; - const u64 size = static_cast<u64>(entry.pages) << 0x10; - ASSERT(size <= object->size); - const u64 map_offset = static_cast<u64>(entry.map_offset) << 0x10; - - const GPUVAddr returned = - gpu.MemoryManager().MapBufferEx(object->addr + map_offset, offset, size); - ASSERT(returned == offset); + if (!addr) { + LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!"); + result = NvErrCodes::InvalidInput; + break; + } } + std::memcpy(output.data(), entries.data(), output.size()); - return 0; + return result; } u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { @@ -126,44 +137,76 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size, params.offset); - if (!params.nvmap_handle) { - return 0; + const auto object{nvmap_dev->GetObject(params.nvmap_handle)}; + if (!object) { + LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle); + std::memcpy(output.data(), ¶ms, output.size()); + return NvErrCodes::InvalidInput; } - auto object = nvmap_dev->GetObject(params.nvmap_handle); - ASSERT(object); - - // We can only map objects that have already been assigned a CPU address. - ASSERT(object->status == nvmap::Object::Status::Allocated); - - ASSERT(params.buffer_offset == 0); - // The real nvservices doesn't make a distinction between handles and ids, and // object can only have one handle and it will be the same as its id. Assert that this is the // case to prevent unexpected behavior. ASSERT(object->id == params.nvmap_handle); - auto& gpu = system.GPU(); - if (params.flags & 1) { - params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size); - } else { - params.offset = gpu.MemoryManager().MapBufferEx(object->addr, object->size); + u64 page_size{params.page_size}; + if (!page_size) { + page_size = object->align; + } + + if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) { + if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) { + const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)}; + const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)}; + + if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) { + LOG_CRITICAL(Service_NVDRV, + "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, " + "mapping_size = {}, offset={}", + params.flags, params.nvmap_handle, params.buffer_offset, + params.mapping_size, params.offset); + + std::memcpy(output.data(), ¶ms, output.size()); + return NvErrCodes::InvalidInput; + } + + std::memcpy(output.data(), ¶ms, output.size()); + return NvErrCodes::Success; + } else { + LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset); + + std::memcpy(output.data(), ¶ms, output.size()); + return NvErrCodes::InvalidInput; + } } - // Create a new mapping entry for this operation. - ASSERT_MSG(buffer_mappings.find(params.offset) == buffer_mappings.end(), - "Offset is already mapped"); + // We can only map objects that have already been assigned a CPU address. + ASSERT(object->status == nvmap::Object::Status::Allocated); + + const auto physical_address{object->addr + params.buffer_offset}; + u64 size{params.mapping_size}; + if (!size) { + size = object->size; + } - BufferMapping mapping{}; - mapping.nvmap_handle = params.nvmap_handle; - mapping.offset = params.offset; - mapping.size = object->size; + const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None}; + if (is_alloc) { + params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size); + } else { + params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size); + } - buffer_mappings[params.offset] = mapping; + auto result{NvErrCodes::Success}; + if (!params.offset) { + LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size); + result = NvErrCodes::InvalidInput; + } else { + AddBufferMap(params.offset, size, physical_address, is_alloc); + } std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return result; } u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { @@ -172,24 +215,20 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset); - const auto itr = buffer_mappings.find(params.offset); - if (itr == buffer_mappings.end()) { - LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset); - // Hardware tests shows that unmapping an already unmapped buffer always returns successful - // and doesn't fail. - return 0; + if (const auto size{RemoveBufferMap(params.offset)}; size) { + system.GPU().MemoryManager().Unmap(params.offset, *size); + } else { + LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset); } - params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size); - buffer_mappings.erase(itr->second.offset); - std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvErrCodes::Success; } u32 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); channel = params.fd; @@ -199,6 +238,7 @@ u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& ou u32 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); @@ -210,9 +250,43 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o 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 0; } +std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const { + const auto end{buffer_mappings.upper_bound(gpu_addr)}; + for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) { + if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) { + return iter->second; + } + } + + return std::nullopt; +} + +void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) { + buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated}; +} + +std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) { + if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) { + std::size_t size{}; + + if (iter->second.IsAllocated()) { + size = iter->second.Size(); + } + + buffer_mappings.erase(iter); + + return size; + } + + return std::nullopt; +} + } // 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 f79fcc065..9a0cdff0c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -4,9 +4,12 @@ #pragma once +#include <map> #include <memory> -#include <unordered_map> +#include <optional> #include <vector> + +#include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" @@ -15,6 +18,13 @@ namespace Service::Nvidia::Devices { class nvmap; +enum class AddressSpaceFlags : u32 { + None = 0x0, + FixedOffset = 0x1, + Remap = 0x100, +}; +DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags); + class nvhost_as_gpu final : public nvdevice { public: explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); @@ -25,6 +35,45 @@ public: IoctlVersion version) override; private: + class BufferMap final { + public: + constexpr BufferMap() = default; + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size) + : start_addr{start_addr}, end_addr{start_addr + size} {} + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) + : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr}, + is_allocated{is_allocated} {} + + constexpr VAddr StartAddr() const { + return start_addr; + } + + constexpr VAddr EndAddr() const { + return end_addr; + } + + constexpr std::size_t Size() const { + return end_addr - start_addr; + } + + constexpr VAddr CpuAddr() const { + return cpu_addr; + } + + constexpr bool IsAllocated() const { + return is_allocated; + } + + private: + GPUVAddr start_addr{}; + GPUVAddr end_addr{}; + VAddr cpu_addr{}; + bool is_allocated{}; + }; + enum class IoctlCommand : u32_le { IocInitalizeExCommand = 0x40284109, IocAllocateSpaceCommand = 0xC0184102, @@ -49,7 +98,7 @@ private: struct IoctlAllocSpace { u32_le pages; u32_le page_size; - u32_le flags; + AddressSpaceFlags flags; INSERT_PADDING_WORDS(1); union { u64_le offset; @@ -69,18 +118,18 @@ private: static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); struct IoctlMapBufferEx { - u32_le flags; // bit0: fixed_offset, bit2: cacheable - u32_le kind; // -1 is default + 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 - u64_le buffer_offset; + s64_le buffer_offset; u64_le mapping_size; - u64_le offset; + s64_le offset; }; static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size"); struct IoctlUnmapBuffer { - u64_le offset; + s64_le offset; }; static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size"); @@ -106,15 +155,6 @@ private: static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2, "IoctlGetVaRegions is incorrect size"); - struct BufferMapping { - u64 offset; - u64 size; - u32 nvmap_handle; - }; - - /// Map containing the nvmap object mappings in GPU memory. - std::unordered_map<u64, BufferMapping> buffer_mappings; - u32 channel{}; u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output); @@ -125,7 +165,14 @@ private: u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output); u32 GetVARegions(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); + std::shared_ptr<nvmap> nvmap_dev; + + // This is expected to be ordered, therefore we must use a map, not unordered_map + std::map<GPUVAddr, BufferMap> buffer_mappings; }; } // 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..fcb612864 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -22,6 +22,18 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std:: switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocSetNVMAPfdCommand: return SetNVMAPfd(input, output); + case IoctlCommand::IocSubmit: + return Submit(input, output); + case IoctlCommand::IocGetSyncpoint: + return GetSyncpoint(input, output); + case IoctlCommand::IocGetWaitbase: + return GetWaitbase(input, output); + case IoctlCommand::IocMapBuffer: + return MapBuffer(input, output); + case IoctlCommand::IocMapBufferEx: + return MapBufferEx(input, output); + case IoctlCommand::IocUnmapBufferEx: + return UnmapBufferEx(input, output); } UNIMPLEMENTED_MSG("Unimplemented ioctl"); @@ -30,11 +42,67 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std:: u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); + std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; return 0; } +u32 nvhost_nvdec::Submit(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSubmit params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit)); + return 0; +} + +u32 nvhost_nvdec::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetSyncpoint params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); + 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, sizeof(IoctlGetSyncpoint)); + return 0; +} + +u32 nvhost_nvdec::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, sizeof(IoctlGetWaitbase)); + return 0; +} + +u32 nvhost_nvdec::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2, + params.address_1); + params.address_1 = 0; + params.address_2 = 0; + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); + return 0; +} + +u32 nvhost_nvdec::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBufferEx params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2, + params.address_1); + params.address_1 = 0; + params.address_2 = 0; + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx)); + return 0; +} + +u32 nvhost_nvdec::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlUnmapBufferEx params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx)); + return 0; +} + } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index cbdac8069..4332db118 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -23,16 +23,66 @@ public: private: enum class IoctlCommand : u32_le { IocSetNVMAPfdCommand = 0x40044801, + IocSubmit = 0xC0400001, + IocGetSyncpoint = 0xC0080002, + IocGetWaitbase = 0xC0080003, + IocMapBuffer = 0xC01C0009, + IocMapBufferEx = 0xC0A40009, + IocUnmapBufferEx = 0xC0A4000A, }; struct IoctlSetNvmapFD { u32_le nvmap_fd; }; - static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + static_assert(sizeof(IoctlSetNvmapFD) == 0x4, "IoctlSetNvmapFD is incorrect size"); + + struct IoctlSubmit { + INSERT_PADDING_BYTES(0x40); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit has incorrect size"); + + struct IoctlGetSyncpoint { + u32 unknown; // seems to be ignored? Nintendo added this + u32 value; + }; + static_assert(sizeof(IoctlGetSyncpoint) == 0x08, "IoctlGetSyncpoint has incorrect size"); + + struct IoctlGetWaitbase { + u32 unknown; // seems to be ignored? Nintendo added this + u32 value; + }; + static_assert(sizeof(IoctlGetWaitbase) == 0x08, "IoctlGetWaitbase has incorrect size"); + + struct IoctlMapBuffer { + u32 unknown; + u32 address_1; + u32 address_2; + INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size"); + + struct IoctlMapBufferEx { + u32 unknown; + u32 address_1; + u32 address_2; + INSERT_PADDING_BYTES(0x98); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlMapBufferEx) == 0xA4, "IoctlMapBufferEx has incorrect size"); + + struct IoctlUnmapBufferEx { + INSERT_PADDING_BYTES(0xA4); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlUnmapBufferEx) == 0xA4, "IoctlUnmapBufferEx has incorrect size"); u32_le nvmap_fd{}; u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + u32 Submit(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); + u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output); + u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); + u32 UnmapBufferEx(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..9da19ad56 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -22,6 +22,18 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocSetNVMAPfdCommand: return SetNVMAPfd(input, output); + case IoctlCommand::IocSubmit: + return Submit(input, output); + case IoctlCommand::IocGetSyncpoint: + return GetSyncpoint(input, output); + case IoctlCommand::IocGetWaitbase: + return GetWaitbase(input, output); + case IoctlCommand::IocMapBuffer: + return MapBuffer(input, output); + case IoctlCommand::IocMapBufferEx: + return MapBuffer(input, output); + case IoctlCommand::IocUnmapBufferEx: + return UnmapBufferEx(input, output); } UNIMPLEMENTED_MSG("Unimplemented ioctl"); @@ -30,11 +42,71 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); + std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; return 0; } +u32 nvhost_vic::Submit(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSubmit params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + // Workaround for Luigi's Mansion 3, as nvhost_vic is not implemented for asynch GPU + params.command_buffer = {}; + + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit)); + return 0; +} + +u32 nvhost_vic::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetSyncpoint params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); + 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, sizeof(IoctlGetSyncpoint)); + return 0; +} + +u32 nvhost_vic::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, sizeof(IoctlGetWaitbase)); + return 0; +} + +u32 nvhost_vic::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2, + params.address_1); + params.address_1 = 0; + params.address_2 = 0; + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); + return 0; +} + +u32 nvhost_vic::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBufferEx params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2, + params.address_1); + params.address_1 = 0; + params.address_2 = 0; + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx)); + return 0; +} + +u32 nvhost_vic::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlUnmapBufferEx params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx)); + return 0; +} + } // 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..a7bb7bbd5 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <vector> #include "common/common_types.h" #include "common/swap.h" @@ -23,6 +24,12 @@ public: private: enum class IoctlCommand : u32_le { IocSetNVMAPfdCommand = 0x40044801, + IocSubmit = 0xC0400001, + IocGetSyncpoint = 0xC0080002, + IocGetWaitbase = 0xC0080003, + IocMapBuffer = 0xC01C0009, + IocMapBufferEx = 0xC03C0009, + IocUnmapBufferEx = 0xC03C000A, }; struct IoctlSetNvmapFD { @@ -30,9 +37,65 @@ private: }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + struct IoctlSubmitCommandBuffer { + u32 id; + u32 offset; + u32 count; + }; + static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC, + "IoctlSubmitCommandBuffer is incorrect size"); + + struct IoctlSubmit { + u32 command_buffer_count; + u32 relocations_count; + u32 syncpt_count; + u32 wait_count; + std::array<IoctlSubmitCommandBuffer, 4> command_buffer; + }; + static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit is incorrect size"); + + struct IoctlGetSyncpoint { + u32 unknown; // seems to be ignored? Nintendo added this + u32 value; + }; + static_assert(sizeof(IoctlGetSyncpoint) == 0x8, "IoctlGetSyncpoint is incorrect size"); + + struct IoctlGetWaitbase { + u32 unknown; // seems to be ignored? Nintendo added this + u32 value; + }; + static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size"); + + struct IoctlMapBuffer { + u32 unknown; + u32 address_1; + u32 address_2; + INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size"); + + struct IoctlMapBufferEx { + u32 unknown; + u32 address_1; + u32 address_2; + INSERT_PADDING_BYTES(0x30); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlMapBufferEx) == 0x3C, "IoctlMapBufferEx is incorrect size"); + + struct IoctlUnmapBufferEx { + INSERT_PADDING_BYTES(0x3C); // TODO(DarkLordZach): RE this structure + }; + static_assert(sizeof(IoctlUnmapBufferEx) == 0x3C, "IoctlUnmapBufferEx is incorrect size"); + u32_le nvmap_fd{}; u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + u32 Submit(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); + u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output); + u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); + u32 UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 8c742316c..9436e16ad 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -18,7 +18,12 @@ enum { }; } -nvmap::nvmap(Core::System& system) : nvdevice(system) {} +nvmap::nvmap(Core::System& system) : nvdevice(system) { + // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to + // represent this. + CreateObject(0); +} + nvmap::~nvmap() = default; VAddr nvmap::GetObjectAddress(u32 handle) const { @@ -50,6 +55,21 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector< return 0; } +u32 nvmap::CreateObject(u32 size) { + // Create a new nvmap object and obtain a handle to it. + auto object = std::make_shared<Object>(); + object->id = next_id++; + object->size = size; + object->status = Object::Status::Created; + object->refcount = 1; + + const u32 handle = next_handle++; + + handles.insert_or_assign(handle, std::move(object)); + + return handle; +} + u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { IocCreateParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -59,17 +79,8 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { LOG_ERROR(Service_NVDRV, "Size is 0"); return static_cast<u32>(NvErrCodes::InvalidValue); } - // Create a new nvmap object and obtain a handle to it. - auto object = std::make_shared<Object>(); - object->id = next_id++; - object->size = params.size; - object->status = Object::Status::Created; - object->refcount = 1; - - u32 handle = next_handle++; - handles[handle] = std::move(object); - params.handle = handle; + params.handle = CreateObject(params.size); std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 73c2e8809..84624be00 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -49,10 +49,10 @@ public: private: /// Id to use for the next handle that is created. - u32 next_handle = 1; + u32 next_handle = 0; /// Id to use for the next object that is created. - u32 next_id = 1; + u32 next_id = 0; /// Mapping of currently allocated handles to the objects they represent. std::unordered_map<u32, std::shared_ptr<Object>> handles; @@ -119,6 +119,8 @@ private: }; 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); diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index deaf0808b..88fbfa9b0 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -60,24 +60,24 @@ void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) { if (ctrl.must_delay) { ctrl.fresh_call = false; - ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout, - [=](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; - u32 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(result); - }, - nvdrv->GetEventWriteable(ctrl.event_id)); + 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) { diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index d7a1bef91..7706a5590 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -54,7 +54,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]; diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index caca80dde..637b310d7 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -24,13 +24,13 @@ BufferQueue::~BufferQueue() = default; void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { LOG_WARNING(Service, "Adding graphics buffer {}", slot); - Buffer buffer{}; - buffer.slot = slot; - buffer.igbp_buffer = igbp_buffer; - buffer.status = Buffer::Status::Free; free_buffers.push_back(slot); + queue.push_back({ + .slot = slot, + .status = Buffer::Status::Free, + .igbp_buffer = igbp_buffer, + }); - queue.emplace_back(buffer); buffer_wait_event.writable->Signal(); } @@ -38,7 +38,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue u32 height) { if (free_buffers.empty()) { - return {}; + return std::nullopt; } auto f_itr = free_buffers.begin(); @@ -69,7 +69,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue } if (itr == queue.end()) { - return {}; + return std::nullopt; } itr->status = Buffer::Status::Dequeued; @@ -103,14 +103,15 @@ std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::Ac auto itr = queue.end(); // Iterate to find a queued buffer matching the requested slot. while (itr == queue.end() && !queue_sequence.empty()) { - u32 slot = queue_sequence.front(); + 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; }); queue_sequence.pop_front(); } - if (itr == queue.end()) - return {}; + if (itr == queue.end()) { + return std::nullopt; + } itr->status = Buffer::Status::Acquired; return *itr; } diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 2f44d3779..c64673dba 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -28,8 +28,7 @@ namespace Service::NVFlinger { -constexpr s64 frame_ticks = static_cast<s64>(1000000000 / 60); -constexpr s64 frame_ticks_30fps = static_cast<s64>(1000000000 / 30); +constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60}; void NVFlinger::VSyncThread(NVFlinger& nv_flinger) { nv_flinger.SplitVSync(); @@ -67,20 +66,24 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) { guard = std::make_shared<std::mutex>(); // Schedule the screen composition events - composition_event = - Core::Timing::CreateEvent("ScreenComposition", [this](u64 userdata, s64 ns_late) { - Lock(); + composition_event = Core::Timing::CreateEvent( + "ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) { + const auto guard = Lock(); Compose(); - const auto ticks = GetNextTicks(); - this->system.CoreTiming().ScheduleEvent(std::max<s64>(0LL, ticks - ns_late), - composition_event); + + const auto ticks = std::chrono::nanoseconds{GetNextTicks()}; + const auto ticks_delta = ticks - ns_late; + const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta); + + this->system.CoreTiming().ScheduleEvent(future_ns, composition_event); }); + if (system.IsMulticore()) { is_running = true; wait_event = std::make_unique<Common::Event>(); vsync_thread = std::make_unique<std::thread>(VSyncThread, std::ref(*this)); } else { - system.CoreTiming().ScheduleEvent(frame_ticks, composition_event); + system.CoreTiming().ScheduleEvent(frame_ns, composition_event); } } @@ -111,7 +114,7 @@ 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(); @@ -121,7 +124,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { auto* const display = FindDisplay(display_id); if (display == nullptr) { - return {}; + return std::nullopt; } const u64 layer_id = next_layer_id++; @@ -141,7 +144,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) co const auto* const layer = FindLayer(display_id, layer_id); if (layer == nullptr) { - return {}; + return std::nullopt; } return layer->GetBufferQueue().GetId(); diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index e4959a9af..1ebe949c0 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -54,12 +54,12 @@ public: /// Opens the specified display and returns the ID. /// /// If an invalid display name is provided, then an empty optional is returned. - std::optional<u64> OpenDisplay(std::string_view name); + [[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name); /// Creates a layer on the specified display and returns the layer ID. /// /// If an invalid display ID is specified, then an empty optional is returned. - std::optional<u64> CreateLayer(u64 display_id); + [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); /// Closes a layer on all displays for the given layer ID. void CloseLayer(u64 layer_id); @@ -67,41 +67,41 @@ public: /// Finds the buffer queue ID of the specified layer in the specified display. /// /// If an invalid display ID or layer ID is provided, then an empty optional is returned. - std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const; + [[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const; /// Gets the vsync event for the specified display. /// /// If an invalid display ID is provided, then nullptr is returned. - std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const; + [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const; /// Obtains a buffer queue identified by the ID. - BufferQueue& FindBufferQueue(u32 id); + [[nodiscard]] BufferQueue& FindBufferQueue(u32 id); /// Obtains a buffer queue identified by the ID. - const BufferQueue& FindBufferQueue(u32 id) const; + [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const; /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. void Compose(); - s64 GetNextTicks() const; + [[nodiscard]] s64 GetNextTicks() const; - std::unique_lock<std::mutex> Lock() { + [[nodiscard]] std::unique_lock<std::mutex> Lock() const { return std::unique_lock{*guard}; } private: /// Finds the display identified by the specified ID. - VI::Display* FindDisplay(u64 display_id); + [[nodiscard]] VI::Display* FindDisplay(u64 display_id); /// Finds the display identified by the specified ID. - const VI::Display* FindDisplay(u64 display_id) const; + [[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const; /// Finds the layer identified by the specified ID in the desired display. - VI::Layer* FindLayer(u64 display_id, u64 layer_id); + [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); /// Finds the layer identified by the specified ID in the desired display. - const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const; + [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const; static void VSyncThread(NVFlinger& nv_flinger); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index fa5347af9..ba9159ee0 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -89,8 +89,6 @@ namespace Service { return function_string; } -//////////////////////////////////////////////////////////////////////////////////////////////////// - ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker) : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} @@ -105,10 +103,9 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) port_installed = true; } -void ServiceFrameworkBase::InstallAsNamedPort() { +void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) { 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,10 +113,9 @@ void ServiceFrameworkBase::InstallAsNamedPort() { port_installed = true; } -std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { +std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) { 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(); @@ -191,9 +187,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co return RESULT_SUCCESS; } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Module interface - /// 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 @@ -246,7 +239,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { PSC::InstallInterfaces(*sm); PSM::InstallInterfaces(*sm); Set::InstallInterfaces(*sm); - Sockets::InstallInterfaces(*sm); + Sockets::InstallInterfaces(*sm, system); SPL::InstallInterfaces(*sm); SSL::InstallInterfaces(*sm); Time::InstallInterfaces(system); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 022d885b6..a01ef3353 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -63,9 +63,9 @@ 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(); + void InstallAsNamedPort(Kernel::KernelCore& kernel); /// Creates and returns an unregistered port for the service. - std::shared_ptr<Kernel::ClientPort> CreatePort(); + std::shared_ptr<Kernel::ClientPort> CreatePort(Kernel::KernelCore& kernel); void InvokeRequest(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 34fe2fd82..e64777668 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -106,7 +106,7 @@ void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - ctx.WriteBuffer(&layout, sizeof(KeyboardLayout)); + ctx.WriteBuffer(layout); } } // Anonymous namespace diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index d872de16c..9c1da361b 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,11 +27,11 @@ 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; } @@ -43,13 +43,13 @@ void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self, ASSERT(self->sm_interface.expired()); auto sm = std::make_shared<SM>(self, kernel); - sm->InstallAsNamedPort(); + sm->InstallAsNamedPort(kernel); self->sm_interface = sm; self->controller_interface = std::make_unique<Controller>(); } -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 +58,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); diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index b06d2f103..6790c86f0 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -9,6 +9,7 @@ #include <type_traits> #include <unordered_map> +#include "common/concepts.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_port.h" @@ -47,19 +48,17 @@ class ServiceManager { public: static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel); - 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); - template <typename T> + template <Common::DerivedFrom<Kernel::SessionRequestHandler> T> std::shared_ptr<T> GetService(const std::string& service_name) const { - static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>, - "Not a base of ServiceFrameworkBase"); auto service = registered_services.find(service_name); if (service == registered_services.end()) { LOG_DEBUG(Service, "Can't find service: {}", service_name); @@ -80,6 +79,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/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h new file mode 100644 index 000000000..2d53e52b6 --- /dev/null +++ b/src/core/hle/service/sockets/blocking_worker.h @@ -0,0 +1,161 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <memory> +#include <string> +#include <string_view> +#include <thread> +#include <variant> +#include <vector> + +#include <fmt/format.h> + +#include "common/assert.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/writable_event.h" + +namespace Service::Sockets { + +/** + * Worker abstraction to execute blocking calls on host without blocking the guest thread + * + * @tparam Service Service where the work is executed + * @tparam Types Types of work to execute + */ +template <class Service, class... Types> +class BlockingWorker { + using This = BlockingWorker<Service, Types...>; + using WorkVariant = std::variant<std::monostate, Types...>; + +public: + /// Create a new worker + static std::unique_ptr<This> Create(Core::System& system, Service* service, + std::string_view name) { + return std::unique_ptr<This>(new This(system, service, name)); + } + + ~BlockingWorker() { + while (!is_available.load(std::memory_order_relaxed)) { + // Busy wait until work is finished + std::this_thread::yield(); + } + // Monostate means to exit the thread + work = std::monostate{}; + work_event.Set(); + thread.join(); + } + + /** + * Try to capture the worker to send work after a success + * @returns True when the worker has been successfully captured + */ + bool TryCapture() { + bool expected = true; + return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed, + std::memory_order_relaxed); + } + + /** + * Send work to this worker abstraction + * @see TryCapture must be called before attempting to call this function + */ + template <class Work> + void SendWork(Work new_work) { + ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured"); + work = std::move(new_work); + work_event.Set(); + } + + /// Generate a callback for @see SleepClientThread + template <class Work> + auto Callback() { + return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + ASSERT(reason == Kernel::ThreadWakeupReason::Signal); + std::get<Work>(work).Response(ctx); + is_available.store(true); + }; + } + + /// Get kernel event that will be signalled by the worker when the host operation finishes + std::shared_ptr<Kernel::WritableEvent> KernelEvent() const { + return kernel_event; + } + +private: + explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) { + auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name)); + kernel_event = std::move(pair.writable); + thread = std::thread([this, &system, service, name] { Run(system, service, name); }); + } + + void Run(Core::System& system, Service* service, std::string_view name) { + system.RegisterHostThread(); + + const std::string thread_name = fmt::format("yuzu:{}", name); + MicroProfileOnThreadCreate(thread_name.c_str()); + Common::SetCurrentThreadName(thread_name.c_str()); + + bool keep_running = true; + while (keep_running) { + work_event.Wait(); + + const auto visit_fn = [service, &keep_running]<typename T>(T&& w) { + if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) { + keep_running = false; + } else { + w.Execute(service); + } + }; + std::visit(visit_fn, work); + + kernel_event->Signal(); + } + } + + std::thread thread; + WorkVariant work; + Common::Event work_event; + std::shared_ptr<Kernel::WritableEvent> kernel_event; + std::atomic_bool is_available{true}; +}; + +template <class Service, class... Types> +class BlockingWorkerPool { + using Worker = BlockingWorker<Service, Types...>; + +public: + explicit BlockingWorkerPool(Core::System& system_, Service* service_) + : system{system_}, service{service_} {} + + /// Returns a captured worker thread, creating new ones if necessary + Worker* CaptureWorker() { + for (auto& worker : workers) { + if (worker->TryCapture()) { + return worker.get(); + } + } + auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size())); + [[maybe_unused]] const bool success = new_worker->TryCapture(); + ASSERT(success); + + return workers.emplace_back(std::move(new_worker)).get(); + } + +private: + Core::System& system; + Service* const service; + + std::vector<std::unique_ptr<Worker>> workers; +}; + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 8d4952c0e..a74be9370 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/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={}", static_cast<int>(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,664 @@ 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, "BSD:Poll", timeout != 0, + 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, "BSD:Accept", IsBlockingSocket(fd), + 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, "BSD:Connect", IsBlockingSocket(fd), + 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::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, "BSD:Recv", IsBlockingSocket(fd), + 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, "BSD:RecvFrom", IsBlockingSocket(fd), + 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, "BSD:Send", IsBlockingSocket(fd), + 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, "BSD:SendTo", IsBlockingSocket(fd), + 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, "BSD:Write", IsBlockingSocket(fd), + 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)); +} + +template <typename Work> +void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, + bool is_blocking, Work work) { + if (!is_blocking) { + work.Execute(this); + work.Response(ctx); + return; + } + + // Signal a dummy response to make IPC validation happy + // This will be overwritten by the SleepClientThread callback + work.Response(ctx); + + auto worker = worker_pool.CaptureWorker(); + + ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(), + worker->Callback<Work>(), worker->KernelEvent()); + + worker->SendWork(std::move(work)); +} + +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}; + } + + FileDescriptor& descriptor = file_descriptors[fd].emplace(); + // 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(pollfd.revents == 0); + + if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) { + LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); + pollfd.revents = 0; + 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 = POLL_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 = 0; + 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)}; + } + + FileDescriptor& new_descriptor = file_descriptors[new_fd].emplace(); + 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={}", static_cast<int>(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={}", static_cast<int>(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; +} + +bool BSD::IsBlockingSocket(s32 fd) const noexcept { + // Inform invalid sockets as non-blocking + // This way we avoid using a worker thread as it will fail without blocking host + if (fd > static_cast<s32>(MAX_FD) || fd < 0) { + return false; + } + if (!file_descriptors[fd]) { + return false; + } + return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0; +} + +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(name), worker_pool{system, this} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -121,25 +837,25 @@ 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"}, + {15, &BSD::GetPeerName, "GetPeerName"}, + {16, &BSD::GetSockName, "GetSockName"}, {17, nullptr, "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"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 3098e3baf..357531951 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -4,30 +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/blocking_worker.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 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); - /// Id to use for the next open file descriptor. - u32 next_fd = 1; + template <typename Work> + void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, + bool is_blocking, 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; + bool IsBlockingSocket(s32 fd) const noexcept; + + void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; + + std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; + + BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork, + SendToWork> + worker_pool; }; class BSDCFG final : public ServiceFramework<BSDCFG> { diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index 08d2d306a..1d27f7906 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -10,9 +10,9 @@ 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); +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>()->InstallAsService(service_manager); std::make_shared<ETHC_C>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index ca8a6a7e0..89a410076 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -4,11 +4,94 @@ #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; +}; + +struct PollFD { + s32 fd; + u16 events; + u16 revents; +}; + +struct Linger { + u32 onoff; + u32 linger; +}; + +constexpr u16 POLL_IN = 0x01; +constexpr u16 POLL_PRI = 0x02; +constexpr u16 POLL_OUT = 0x04; +constexpr u16 POLL_ERR = 0x08; +constexpr u16 POLL_HUP = 0x10; +constexpr u16 POLL_NVAL = 0x20; + +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..139743e1d --- /dev/null +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -0,0 +1,165 @@ +// 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={}", static_cast<int>(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={}", static_cast<int>(domain)); + return {}; + } +} + +Domain Translate(Network::Domain domain) { + switch (domain) { + case Network::Domain::INET: + return Domain::INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(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={}", static_cast<int>(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={}", static_cast<int>(protocol)); + return Network::Protocol::TCP; + } +} + +u16 TranslatePollEventsToHost(u16 flags) { + u16 result = 0; + const auto translate = [&result, &flags](u16 from, u16 to) { + if ((flags & from) != 0) { + flags &= ~from; + result |= to; + } + }; + translate(POLL_IN, Network::POLL_IN); + translate(POLL_PRI, Network::POLL_PRI); + translate(POLL_OUT, Network::POLL_OUT); + translate(POLL_ERR, Network::POLL_ERR); + translate(POLL_HUP, Network::POLL_HUP); + translate(POLL_NVAL, Network::POLL_NVAL); + + UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags); + return result; +} + +u16 TranslatePollEventsToGuest(u16 flags) { + u16 result = 0; + const auto translate = [&result, &flags](u16 from, u16 to) { + if ((flags & from) != 0) { + flags &= ~from; + result |= to; + } + }; + + translate(Network::POLL_IN, POLL_IN); + translate(Network::POLL_PRI, POLL_PRI); + translate(Network::POLL_OUT, POLL_OUT); + translate(Network::POLL_ERR, POLL_ERR); + translate(Network::POLL_HUP, POLL_HUP); + translate(Network::POLL_NVAL, POLL_NVAL); + + UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", 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={}", static_cast<int>(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..8ed041e31 --- /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 +u16 TranslatePollEventsToHost(u16 flags); + +/// Translate guest poll event flags to abstract poll event flags +u16 TranslatePollEventsToGuest(u16 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/time/time.cpp b/src/core/hle/service/time/time.cpp index 13e4b3818..ee4fa4b48 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -290,7 +290,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot)); + ctx.WriteBuffer(clock_snapshot); } void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx) { @@ -313,7 +313,7 @@ void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLEReques IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot)); + ctx.WriteBuffer(clock_snapshot); } void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser( 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 c070d6e97..320672add 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -73,10 +73,8 @@ TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core:: std::string location_name; const auto timezone_setting = Settings::GetTimeZoneString(); - if (timezone_setting == "auto") { + if (timezone_setting == "auto" || timezone_setting == "default") { location_name = Common::TimeZone::GetDefaultTimeZone(); - } else if (timezone_setting == "default") { - location_name = location_name; } else { location_name = timezone_setting; } diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index db57ae069..ff3a10b3e 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -142,7 +142,7 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u32>(1); // Number of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(s64)); + ctx.WriteBuffer(posix_time); } void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { @@ -164,7 +164,7 @@ void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u32>(1); // Number of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(s64)); + ctx.WriteBuffer(posix_time); } } // namespace Service::Time diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index ea7b4ae13..480d34725 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -511,7 +511,7 @@ private: LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id, static_cast<u32>(transaction), flags); - nv_flinger->Lock(); + const auto guard = nv_flinger->Lock(); auto& buffer_queue = nv_flinger->FindBufferQueue(id); switch (transaction) { @@ -548,10 +548,10 @@ private: // Wait the current thread until a buffer becomes available ctx.SleepClientThread( "IHOSBinderDriver::DequeueBuffer", UINT64_MAX, - [=](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, - Kernel::ThreadWakeupReason reason) { + [=, this](std::shared_ptr<Kernel::Thread> thread, + Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { // Repeat TransactParcel DequeueBuffer when a buffer is available - nv_flinger->Lock(); + 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."); @@ -1199,6 +1199,23 @@ private: } } + void GetIndirectLayerImageRequiredMemoryInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto width = rp.Pop<u64>(); + 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; + const auto texture_size = width * height * 4; + const auto out_size = (texture_size + base_size - 1) / base_size * base_size; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.Push(out_size); + rb.Push(alignment); + } + static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) { switch (mode) { case NintendoScaleMode::None: @@ -1243,7 +1260,8 @@ IApplicationDisplayService::IApplicationDisplayService( {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"}, {2450, nullptr, "GetIndirectLayerImageMap"}, {2451, nullptr, "GetIndirectLayerImageCropMap"}, - {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"}, + {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo, + "GetIndirectLayerImageRequiredMemoryInfo"}, {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"}, {5203, nullptr, "GetDisplayVsyncEventForDebug"}, }; diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 134e83412..394a1bf26 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -89,7 +89,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, {}}; } @@ -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, {}}; } @@ -168,9 +168,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, {}}; } @@ -192,8 +192,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..35d340317 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 { /** @@ -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..3527933ad 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 @@ -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..5981bcd21 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -43,7 +43,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, {}}; } diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h index 12ca40269..dee05a7b5 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; } @@ -26,7 +30,7 @@ public: 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 59ca7091a..9bc3a8840 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -3,8 +3,10 @@ // Refer to the license.txt file included. #include <memory> +#include <optional> #include <ostream> #include <string> +#include "common/concepts.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -21,27 +23,41 @@ namespace Loader { -FileType IdentifyFile(FileSys::VirtualFile file) { - FileType type; - -#define CHECK_TYPE(loader) \ - type = AppLoader_##loader::IdentifyType(file); \ - if (FileType::Error != type) \ - return type; +namespace { - CHECK_TYPE(DeconstructedRomDirectory) - CHECK_TYPE(ELF) - CHECK_TYPE(NSO) - CHECK_TYPE(NRO) - CHECK_TYPE(NCA) - CHECK_TYPE(XCI) - CHECK_TYPE(NAX) - CHECK_TYPE(NSP) - CHECK_TYPE(KIP) +template <Common::DerivedFrom<AppLoader> T> +std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) { + const auto file_type = T::IdentifyType(file); + if (file_type != FileType::Error) { + return file_type; + } + return std::nullopt; +} -#undef CHECK_TYPE +} // namespace - return FileType::Unknown; +FileType IdentifyFile(FileSys::VirtualFile file) { + if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) { + return *romdir_type; + } else if (const auto elf_type = IdentifyFileLoader<AppLoader_ELF>(file)) { + return *elf_type; + } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) { + return *nso_type; + } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) { + return *nro_type; + } else if (const auto nca_type = IdentifyFileLoader<AppLoader_NCA>(file)) { + return *nca_type; + } else if (const auto xci_type = IdentifyFileLoader<AppLoader_XCI>(file)) { + return *xci_type; + } else if (const auto nax_type = IdentifyFileLoader<AppLoader_NAX>(file)) { + return *nax_type; + } else if (const auto nsp_type = IdentifyFileLoader<AppLoader_NSP>(file)) { + return *nsp_type; + } else if (const auto kip_type = IdentifyFileLoader<AppLoader_KIP>(file)) { + return *kip_type; + } else { + return FileType::Unknown; + } } FileType GuessFromFilename(const std::string& name) { @@ -51,7 +67,7 @@ FileType GuessFromFilename(const std::string& name) { return FileType::NCA; const std::string extension = - Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name))); + Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name))); if (extension == "elf") return FileType::ELF; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 227ecc704..ac60b097a 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 @@ -154,9 +158,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 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..c2b7722b5 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 { @@ -33,7 +35,7 @@ public: 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..711070294 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; } @@ -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..9fb5eddad 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -208,7 +208,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,8 +218,8 @@ 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; diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 4593d48fb..a2aab2ecc 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; } @@ -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..1e70f6e11 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -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,7 +148,6 @@ 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); if (!cheats.empty()) { @@ -166,7 +165,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, 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 +174,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, {}}; } diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index b210830f0..4bd47787d 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; } @@ -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..15e528fa8 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -71,7 +71,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 +99,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..b27deb686 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -9,6 +9,10 @@ #include "core/file_sys/vfs.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace FileSys { class NACP; class NSP; @@ -35,7 +39,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..25e83af0f 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -49,7 +49,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 +66,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..04aea286f 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -9,6 +9,10 @@ #include "core/file_sys/vfs.h" #include "core/loader/loader.h" +namespace Core { +class System; +} + namespace FileSys { class NACP; class XCI; @@ -35,7 +39,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 2c5588933..c3f4829d7 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -567,7 +567,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, @@ -704,7 +704,7 @@ struct Memory::Impl { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer != nullptr) { // NOTE: Avoid adding any extra logic to this fast-path block - T volatile* pointer = reinterpret_cast<T volatile*>(&page_pointer[vaddr]); + auto* pointer = reinterpret_cast<volatile T*>(&page_pointer[vaddr]); return Common::AtomicCompareAndSwap(pointer, data, expected); } @@ -720,9 +720,8 @@ struct Memory::Impl { case Common::PageType::RasterizerCachedMemory: { u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; system.GPU().InvalidateRegion(vaddr, sizeof(T)); - T volatile* pointer = reinterpret_cast<T volatile*>(&host_ptr); + auto* pointer = reinterpret_cast<volatile T*>(&host_ptr); return Common::AtomicCompareAndSwap(pointer, data, expected); - break; } default: UNREACHABLE(); @@ -734,7 +733,7 @@ struct Memory::Impl { 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 - u64 volatile* pointer = reinterpret_cast<u64 volatile*>(&page_pointer[vaddr]); + auto* pointer = reinterpret_cast<volatile u64*>(&page_pointer[vaddr]); return Common::AtomicCompareAndSwap(pointer, data, expected); } @@ -750,9 +749,8 @@ struct Memory::Impl { case Common::PageType::RasterizerCachedMemory: { u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; system.GPU().InvalidateRegion(vaddr, sizeof(u128)); - u64 volatile* pointer = reinterpret_cast<u64 volatile*>(&host_ptr); + auto* pointer = reinterpret_cast<volatile u64*>(&host_ptr); return Common::AtomicCompareAndSwap(pointer, data, expected); - break; } default: UNREACHABLE(); diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 53d27859b..29284a42d 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 { - -constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(1000000000 / 12); +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) {} @@ -42,7 +56,7 @@ u64 StandardVmCallbacks::HidKeysDown() { if (applet_resource == nullptr) { LOG_WARNING(CheatEngine, "Attempted to read input state, but applet resource is not initialized!"); - return false; + return 0; } const auto press_state = @@ -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 {}; } @@ -190,24 +187,38 @@ CheatEngine::~CheatEngine() { void CheatEngine::Initialize() { event = Core::Timing::CreateEvent( "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), - [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + FrameCallback(user_data, ns_late); + }); + core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event); metadata.process_id = system.CurrentProcess()->GetProcessID(); metadata.title_id = system.CurrentProcess()->GetTitleID(); const auto& page_table = system.CurrentProcess()->PageTable(); - metadata.heap_extents = {page_table.GetHeapRegionStart(), page_table.GetHeapRegionSize()}; - metadata.address_space_extents = {page_table.GetAddressSpaceStart(), - page_table.GetAddressSpaceSize()}; - metadata.alias_extents = {page_table.GetAliasCodeRegionStart(), - page_table.GetAliasCodeRegionSize()}; + metadata.heap_extents = { + .base = page_table.GetHeapRegionStart(), + .size = page_table.GetHeapRegionSize(), + }; + + metadata.address_space_extents = { + .base = page_table.GetAddressSpaceStart(), + .size = page_table.GetAddressSpaceSize(), + }; + + metadata.alias_extents = { + .base = page_table.GetAliasCodeRegionStart(), + .size = page_table.GetAliasCodeRegionSize(), + }; is_pending_reload.exchange(true); } void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { - metadata.main_nso_extents = {main_region_begin, main_region_size}; + metadata.main_nso_extents = { + .base = main_region_begin, + .size = main_region_size, + }; } void CheatEngine::Reload(std::vector<CheatEntry> cheats) { @@ -217,7 +228,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> cheats) { MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); -void CheatEngine::FrameCallback(u64 userdata, s64 ns_late) { +void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) { if (is_pending_reload.exchange(false)) { vm.LoadProgram(cheats); } @@ -230,7 +241,7 @@ void CheatEngine::FrameCallback(u64 userdata, s64 ns_late) { vm.Execute(metadata); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - ns_late, event); + core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event); } } // namespace Core::Memory diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h index 2649423f8..a31002346 100644 --- a/src/core/memory/cheat_engine.h +++ b/src/core/memory/cheat_engine.h @@ -5,6 +5,7 @@ #pragma once #include <atomic> +#include <chrono> #include <memory> #include <vector> #include "common/common_types.h" @@ -46,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 @@ -55,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 @@ -71,7 +71,7 @@ public: void Reload(std::vector<CheatEntry> cheats); private: - void FrameCallback(u64 userdata, s64 cycles_late); + void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); DmntCheatVm vm; CheatProcessMetadata metadata; diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp index 2e7da23fe..48be80c12 100644 --- a/src/core/memory/dmnt_cheat_vm.cpp +++ b/src/core/memory/dmnt_cheat_vm.cpp @@ -313,30 +313,32 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { switch (opcode_type) { case CheatVmOpcodeType::StoreStatic: { - StoreStaticOpcode store_static{}; // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) // Read additional words. const u32 second_dword = GetNextDword(); - store_static.bit_width = (first_dword >> 24) & 0xF; - store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); - store_static.offset_register = ((first_dword >> 16) & 0xF); - store_static.rel_address = - (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); - store_static.value = GetNextVmInt(store_static.bit_width); - opcode.opcode = store_static; + const u32 bit_width = (first_dword >> 24) & 0xF; + + opcode.opcode = StoreStaticOpcode{ + .bit_width = bit_width, + .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF), + .offset_register = (first_dword >> 16) & 0xF, + .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword, + .value = GetNextVmInt(bit_width), + }; } break; case CheatVmOpcodeType::BeginConditionalBlock: { - BeginConditionalOpcode begin_cond{}; // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) // Read additional words. const u32 second_dword = GetNextDword(); - begin_cond.bit_width = (first_dword >> 24) & 0xF; - begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); - begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); - begin_cond.rel_address = - (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); - begin_cond.value = GetNextVmInt(begin_cond.bit_width); - opcode.opcode = begin_cond; + const u32 bit_width = (first_dword >> 24) & 0xF; + + opcode.opcode = BeginConditionalOpcode{ + .bit_width = bit_width, + .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF), + .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF), + .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword, + .value = GetNextVmInt(bit_width), + }; } break; case CheatVmOpcodeType::EndConditionalBlock: { // 20000000 @@ -344,12 +346,14 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { opcode.opcode = EndConditionalOpcode{}; } break; case CheatVmOpcodeType::ControlLoop: { - ControlLoopOpcode ctrl_loop{}; // 300R0000 VVVVVVVV // 310R0000 // Parse register, whether loop start or loop end. - ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; - ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); + ControlLoopOpcode ctrl_loop{ + .start_loop = ((first_dword >> 24) & 0xF) == 0, + .reg_index = (first_dword >> 20) & 0xF, + .num_iters = 0, + }; // Read number of iters if loop start. if (ctrl_loop.start_loop) { @@ -358,66 +362,65 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { opcode.opcode = ctrl_loop; } break; case CheatVmOpcodeType::LoadRegisterStatic: { - LoadRegisterStaticOpcode ldr_static{}; // 400R0000 VVVVVVVV VVVVVVVV // Read additional words. - ldr_static.reg_index = ((first_dword >> 16) & 0xF); - ldr_static.value = - (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); - opcode.opcode = ldr_static; + opcode.opcode = LoadRegisterStaticOpcode{ + .reg_index = (first_dword >> 16) & 0xF, + .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(), + }; } break; case CheatVmOpcodeType::LoadRegisterMemory: { - LoadRegisterMemoryOpcode ldr_memory{}; // 5TMRI0AA AAAAAAAA // Read additional words. const u32 second_dword = GetNextDword(); - ldr_memory.bit_width = (first_dword >> 24) & 0xF; - ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); - ldr_memory.reg_index = ((first_dword >> 16) & 0xF); - ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; - ldr_memory.rel_address = - (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); - opcode.opcode = ldr_memory; + opcode.opcode = LoadRegisterMemoryOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF), + .reg_index = ((first_dword >> 16) & 0xF), + .load_from_reg = ((first_dword >> 12) & 0xF) != 0, + .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword, + }; } break; case CheatVmOpcodeType::StoreStaticToAddress: { - StoreStaticToAddressOpcode str_static{}; // 6T0RIor0 VVVVVVVV VVVVVVVV // Read additional words. - str_static.bit_width = (first_dword >> 24) & 0xF; - str_static.reg_index = ((first_dword >> 16) & 0xF); - str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; - str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; - str_static.offset_reg_index = ((first_dword >> 4) & 0xF); - str_static.value = - (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); - opcode.opcode = str_static; + opcode.opcode = StoreStaticToAddressOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .reg_index = (first_dword >> 16) & 0xF, + .increment_reg = ((first_dword >> 12) & 0xF) != 0, + .add_offset_reg = ((first_dword >> 8) & 0xF) != 0, + .offset_reg_index = (first_dword >> 4) & 0xF, + .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(), + }; } break; case CheatVmOpcodeType::PerformArithmeticStatic: { - PerformArithmeticStaticOpcode perform_math_static{}; // 7T0RC000 VVVVVVVV // Read additional words. - perform_math_static.bit_width = (first_dword >> 24) & 0xF; - perform_math_static.reg_index = ((first_dword >> 16) & 0xF); - perform_math_static.math_type = - static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF); - perform_math_static.value = GetNextDword(); - opcode.opcode = perform_math_static; + opcode.opcode = PerformArithmeticStaticOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .reg_index = ((first_dword >> 16) & 0xF), + .math_type = static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF), + .value = GetNextDword(), + }; } break; case CheatVmOpcodeType::BeginKeypressConditionalBlock: { - BeginKeypressConditionalOpcode begin_keypress_cond{}; // 8kkkkkkk // Just parse the mask. - begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; - opcode.opcode = begin_keypress_cond; + opcode.opcode = BeginKeypressConditionalOpcode{ + .key_mask = first_dword & 0x0FFFFFFF, + }; } break; case CheatVmOpcodeType::PerformArithmeticRegister: { - PerformArithmeticRegisterOpcode perform_math_reg{}; // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) - perform_math_reg.bit_width = (first_dword >> 24) & 0xF; - perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF); - perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); - perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); - perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + PerformArithmeticRegisterOpcode perform_math_reg{ + .bit_width = (first_dword >> 24) & 0xF, + .math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF), + .dst_reg_index = (first_dword >> 16) & 0xF, + .src_reg_1_index = (first_dword >> 12) & 0xF, + .src_reg_2_index = 0, + .has_immediate = ((first_dword >> 8) & 0xF) != 0, + .value = {}, + }; if (perform_math_reg.has_immediate) { perform_math_reg.src_reg_2_index = 0; perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); @@ -427,7 +430,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { opcode.opcode = perform_math_reg; } break; case CheatVmOpcodeType::StoreRegisterToAddress: { - StoreRegisterToAddressOpcode str_register{}; // ATSRIOxa (aaaaaaaa) // A = opcode 10 // T = bit width @@ -439,20 +441,23 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { // Relative Address // x = offset register (for offset type 1), memory type (for offset type 3) // a = relative address (for offset type 2+3) - str_register.bit_width = (first_dword >> 24) & 0xF; - str_register.str_reg_index = ((first_dword >> 20) & 0xF); - str_register.addr_reg_index = ((first_dword >> 16) & 0xF); - str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; - str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)); - str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + StoreRegisterToAddressOpcode str_register{ + .bit_width = (first_dword >> 24) & 0xF, + .str_reg_index = (first_dword >> 20) & 0xF, + .addr_reg_index = (first_dword >> 16) & 0xF, + .increment_reg = ((first_dword >> 12) & 0xF) != 0, + .ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)), + .mem_type = MemoryAccessType::MainNso, + .ofs_reg_index = (first_dword >> 4) & 0xF, + .rel_address = 0, + }; switch (str_register.ofs_type) { case StoreRegisterOffsetType::None: case StoreRegisterOffsetType::Reg: // Nothing more to do break; case StoreRegisterOffsetType::Imm: - str_register.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; case StoreRegisterOffsetType::MemReg: str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); @@ -460,8 +465,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { case StoreRegisterOffsetType::MemImm: case StoreRegisterOffsetType::MemImmReg: str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); - str_register.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; default: str_register.ofs_type = StoreRegisterOffsetType::None; @@ -470,7 +474,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { opcode.opcode = str_register; } break; case CheatVmOpcodeType::BeginRegisterConditionalBlock: { - BeginRegisterConditionalOpcode begin_reg_cond{}; // C0TcSX## // C0TcS0Ma aaaaaaaa // C0TcS1Mr @@ -492,11 +495,19 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { // r = offset register. // X = other register. // V = value. - begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; - begin_reg_cond.cond_type = - static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); - begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); - begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF); + + BeginRegisterConditionalOpcode begin_reg_cond{ + .bit_width = (first_dword >> 20) & 0xF, + .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF), + .val_reg_index = (first_dword >> 12) & 0xF, + .comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF), + .mem_type = MemoryAccessType::MainNso, + .addr_reg_index = 0, + .other_reg_index = 0, + .ofs_reg_index = 0, + .rel_address = 0, + .value = {}, + }; switch (begin_reg_cond.comp_type) { case CompareRegisterValueType::StaticValue: @@ -508,26 +519,25 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { case CompareRegisterValueType::MemoryRelAddr: begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); begin_reg_cond.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; case CompareRegisterValueType::MemoryOfsReg: begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); begin_reg_cond.ofs_reg_index = (first_dword & 0xF); break; case CompareRegisterValueType::RegisterRelAddr: - begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; begin_reg_cond.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; case CompareRegisterValueType::RegisterOfsReg: - begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); - begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; + begin_reg_cond.ofs_reg_index = first_dword & 0xF; break; } opcode.opcode = begin_reg_cond; } break; case CheatVmOpcodeType::SaveRestoreRegister: { - SaveRestoreRegisterOpcode save_restore_reg{}; // C10D0Sx0 // C1 = opcode 0xC1 // D = destination index. @@ -535,36 +545,37 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring // a register. // NOTE: If we add more save slots later, current encoding is backwards compatible. - save_restore_reg.dst_index = (first_dword >> 16) & 0xF; - save_restore_reg.src_index = (first_dword >> 8) & 0xF; - save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF); - opcode.opcode = save_restore_reg; + opcode.opcode = SaveRestoreRegisterOpcode{ + .dst_index = (first_dword >> 16) & 0xF, + .src_index = (first_dword >> 8) & 0xF, + .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF), + }; } break; case CheatVmOpcodeType::SaveRestoreRegisterMask: { - SaveRestoreRegisterMaskOpcode save_restore_regmask{}; // C2x0XXXX // C2 = opcode 0xC2 // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. // X = 16-bit bitmask, bit i --> save or restore register i. - save_restore_regmask.op_type = - static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF); + SaveRestoreRegisterMaskOpcode save_restore_regmask{ + .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF), + .should_operate = {}, + }; for (std::size_t i = 0; i < NumRegisters; i++) { - save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + save_restore_regmask.should_operate[i] = (first_dword & (1U << i)) != 0; } opcode.opcode = save_restore_regmask; } break; case CheatVmOpcodeType::ReadWriteStaticRegister: { - ReadWriteStaticRegisterOpcode rw_static_reg{}; // C3000XXx // C3 = opcode 0xC3. // XX = static register index. // x = register index. - rw_static_reg.static_idx = ((first_dword >> 4) & 0xFF); - rw_static_reg.idx = (first_dword & 0xF); - opcode.opcode = rw_static_reg; + opcode.opcode = ReadWriteStaticRegisterOpcode{ + .static_idx = (first_dword >> 4) & 0xFF, + .idx = first_dword & 0xF, + }; } break; case CheatVmOpcodeType::DebugLog: { - DebugLogOpcode debug_log{}; // FFFTIX## // FFFTI0Ma aaaaaaaa // FFFTI1Mr @@ -583,31 +594,36 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { // a = relative address. // r = offset register. // X = value register. - debug_log.bit_width = (first_dword >> 16) & 0xF; - debug_log.log_id = ((first_dword >> 12) & 0xF); - debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF); + DebugLogOpcode debug_log{ + .bit_width = (first_dword >> 16) & 0xF, + .log_id = (first_dword >> 12) & 0xF, + .val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF), + .mem_type = MemoryAccessType::MainNso, + .addr_reg_index = 0, + .val_reg_index = 0, + .ofs_reg_index = 0, + .rel_address = 0, + }; switch (debug_log.val_type) { case DebugLogValueType::RegisterValue: - debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + debug_log.val_reg_index = (first_dword >> 4) & 0xF; break; case DebugLogValueType::MemoryRelAddr: debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); - debug_log.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; case DebugLogValueType::MemoryOfsReg: debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); - debug_log.ofs_reg_index = (first_dword & 0xF); + debug_log.ofs_reg_index = first_dword & 0xF; break; case DebugLogValueType::RegisterRelAddr: - debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); - debug_log.rel_address = - ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + debug_log.addr_reg_index = (first_dword >> 4) & 0xF; + debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword(); break; case DebugLogValueType::RegisterOfsReg: - debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); - debug_log.ofs_reg_index = (first_dword & 0xF); + debug_log.addr_reg_index = (first_dword >> 4) & 0xF; + debug_log.ofs_reg_index = first_dword & 0xF; break; } opcode.opcode = debug_log; diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp new file mode 100644 index 000000000..56d173b5e --- /dev/null +++ b/src/core/network/network.cpp @@ -0,0 +1,654 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstring> +#include <limits> +#include <utility> +#include <vector> + +#ifdef _WIN32 +#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname +#include <winsock2.h> +#elif __unix__ +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <sys/socket.h> +#include <unistd.h> +#else +#error "Unimplemented platform" +#endif + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/network/network.h" +#include "core/network/sockets.h" + +namespace Network { + +namespace { + +#ifdef _WIN32 + +using socklen_t = int; + +void Initialize() { + WSADATA wsa_data; + (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); +} + +void Finalize() { + WSACleanup(); +} + +constexpr IPv4Address TranslateIPv4(in_addr addr) { + auto& bytes = addr.S_un.S_un_b; + return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4}; +} + +sockaddr TranslateFromSockAddrIn(SockAddrIn input) { + sockaddr_in result; + +#ifdef __unix__ + result.sin_len = sizeof(result); +#endif + + switch (static_cast<Domain>(input.family)) { + case Domain::INET: + result.sin_family = AF_INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family)); + result.sin_family = AF_INET; + break; + } + + result.sin_port = htons(input.portno); + + auto& ip = result.sin_addr.S_un.S_un_b; + ip.s_b1 = input.ip[0]; + ip.s_b2 = input.ip[1]; + ip.s_b3 = input.ip[2]; + ip.s_b4 = input.ip[3]; + + sockaddr addr; + std::memcpy(&addr, &result, sizeof(addr)); + return addr; +} + +LINGER MakeLinger(bool enable, u32 linger_value) { + ASSERT(linger_value <= std::numeric_limits<u_short>::max()); + + LINGER value; + value.l_onoff = enable ? 1 : 0; + value.l_linger = static_cast<u_short>(linger_value); + return value; +} + +int LastError() { + return WSAGetLastError(); +} + +bool EnableNonBlock(SOCKET fd, bool enable) { + u_long value = enable ? 1 : 0; + return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; +} + +#elif __unix__ // ^ _WIN32 v __unix__ + +using SOCKET = int; +using WSAPOLLFD = pollfd; +using ULONG = u64; + +constexpr SOCKET INVALID_SOCKET = -1; +constexpr SOCKET SOCKET_ERROR = -1; + +constexpr int WSAEWOULDBLOCK = EAGAIN; +constexpr int WSAENOTCONN = ENOTCONN; + +constexpr int SD_RECEIVE = SHUT_RD; +constexpr int SD_SEND = SHUT_WR; +constexpr int SD_BOTH = SHUT_RDWR; + +void Initialize() {} + +void Finalize() {} + +constexpr IPv4Address TranslateIPv4(in_addr addr) { + const u32 bytes = addr.s_addr; + return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8), + static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)}; +} + +sockaddr TranslateFromSockAddrIn(SockAddrIn input) { + sockaddr_in result; + + switch (static_cast<Domain>(input.family)) { + case Domain::INET: + result.sin_family = AF_INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family)); + result.sin_family = AF_INET; + break; + } + + result.sin_port = htons(input.portno); + + result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24; + + sockaddr addr; + std::memcpy(&addr, &result, sizeof(addr)); + return addr; +} + +int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) { + return poll(fds, nfds, timeout); +} + +int closesocket(SOCKET fd) { + return close(fd); +} + +linger MakeLinger(bool enable, u32 linger_value) { + linger value; + value.l_onoff = enable ? 1 : 0; + value.l_linger = linger_value; + return value; +} + +int LastError() { + return errno; +} + +bool EnableNonBlock(int fd, bool enable) { + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + return false; + } + if (enable) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + return fcntl(fd, F_SETFD, flags) == 0; +} + +#endif + +int TranslateDomain(Domain domain) { + switch (domain) { + case Domain::INET: + return AF_INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain)); + return 0; + } +} + +int TranslateType(Type type) { + switch (type) { + case Type::STREAM: + return SOCK_STREAM; + case Type::DGRAM: + return SOCK_DGRAM; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type)); + return 0; + } +} + +int TranslateProtocol(Protocol protocol) { + switch (protocol) { + case Protocol::TCP: + return IPPROTO_TCP; + case Protocol::UDP: + return IPPROTO_UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol)); + return 0; + } +} + +SockAddrIn TranslateToSockAddrIn(sockaddr input_) { + sockaddr_in input; + std::memcpy(&input, &input_, sizeof(input)); + + SockAddrIn result; + + switch (input.sin_family) { + case AF_INET: + result.family = Domain::INET; + break; + default: + UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family); + result.family = Domain::INET; + break; + } + + result.portno = ntohs(input.sin_port); + + result.ip = TranslateIPv4(input.sin_addr); + + return result; +} + +u16 TranslatePollEvents(u16 events) { + u16 result = 0; + + if (events & POLL_IN) { + events &= ~POLL_IN; + result |= POLLIN; + } + if (events & POLL_PRI) { + events &= ~POLL_PRI; +#ifdef _WIN32 + LOG_WARNING(Service, "Winsock doesn't support POLLPRI"); +#else + result |= POLL_PRI; +#endif + } + if (events & POLL_OUT) { + events &= ~POLL_OUT; + result |= POLLOUT; + } + + UNIMPLEMENTED_IF_MSG(events != 0, "Unhandled guest events=0x{:x}", events); + + return result; +} + +u16 TranslatePollRevents(u16 revents) { + u16 result = 0; + const auto translate = [&result, &revents](int host, unsigned guest) { + if (revents & host) { + revents &= ~host; + result |= guest; + } + }; + + translate(POLLIN, POLL_IN); + translate(POLLPRI, POLL_PRI); + translate(POLLOUT, POLL_OUT); + translate(POLLERR, POLL_ERR); + translate(POLLHUP, POLL_HUP); + + UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); + + return result; +} + +template <typename T> +Errno SetSockOpt(SOCKET fd, int option, T value) { + const int result = + setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value)); + if (result != SOCKET_ERROR) { + return Errno::SUCCESS; + } + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; +} + +} // Anonymous namespace + +NetworkInstance::NetworkInstance() { + Initialize(); +} + +NetworkInstance::~NetworkInstance() { + Finalize(); +} + +std::pair<IPv4Address, Errno> GetHostIPv4Address() { + std::array<char, 256> name{}; + if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) { + UNIMPLEMENTED_MSG("Unhandled gethostname error"); + return {IPv4Address{}, Errno::SUCCESS}; + } + + hostent* const ent = gethostbyname(name.data()); + if (!ent) { + UNIMPLEMENTED_MSG("Unhandled gethostbyname error"); + return {IPv4Address{}, Errno::SUCCESS}; + } + if (ent->h_addr_list == nullptr) { + UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list"); + return {IPv4Address{}, Errno::SUCCESS}; + } + if (ent->h_length != sizeof(in_addr)) { + UNIMPLEMENTED_MSG("Unexpected size={} in hostent->h_length", ent->h_length); + } + + in_addr addr; + std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr)); + return {TranslateIPv4(addr), Errno::SUCCESS}; +} + +std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { + const size_t num = pollfds.size(); + + std::vector<WSAPOLLFD> host_pollfds(pollfds.size()); + std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) { + WSAPOLLFD result; + result.fd = fd.socket->fd; + result.events = TranslatePollEvents(fd.events); + result.revents = 0; + return result; + }); + + const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); + if (result == 0) { + ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), + [](WSAPOLLFD fd) { return fd.revents == 0; })); + return {0, Errno::SUCCESS}; + } + + for (size_t i = 0; i < num; ++i) { + pollfds[i].revents = TranslatePollRevents(host_pollfds[i].revents); + } + + if (result > 0) { + return {result, Errno::SUCCESS}; + } + + ASSERT(result == SOCKET_ERROR); + + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {-1, Errno::SUCCESS}; +} + +Socket::~Socket() { + if (fd == INVALID_SOCKET) { + return; + } + (void)closesocket(fd); + fd = INVALID_SOCKET; +} + +Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {} + +Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { + fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol)); + if (fd != INVALID_SOCKET) { + return Errno::SUCCESS; + } + + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; +} + +std::pair<Socket::AcceptResult, Errno> Socket::Accept() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + const SOCKET new_socket = accept(fd, &addr, &addrlen); + + if (new_socket == INVALID_SOCKET) { + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {AcceptResult{}, Errno::SUCCESS}; + } + + AcceptResult result; + result.socket = std::make_unique<Socket>(); + result.socket->fd = new_socket; + + ASSERT(addrlen == sizeof(sockaddr_in)); + result.sockaddr_in = TranslateToSockAddrIn(addr); + + return {std::move(result), Errno::SUCCESS}; +} + +Errno Socket::Connect(SockAddrIn addr_in) { + const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in); + if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != INVALID_SOCKET) { + return Errno::SUCCESS; + } + + switch (const int ec = LastError()) { + case WSAEWOULDBLOCK: + LOG_DEBUG(Service, "EAGAIN generated"); + return Errno::AGAIN; + default: + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; + } +} + +std::pair<SockAddrIn, Errno> Socket::GetPeerName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {SockAddrIn{}, Errno::SUCCESS}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +std::pair<SockAddrIn, Errno> Socket::GetSockName() { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {SockAddrIn{}, Errno::SUCCESS}; + } + + ASSERT(addrlen == sizeof(sockaddr_in)); + return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; +} + +Errno Socket::Bind(SockAddrIn addr) { + const sockaddr addr_in = TranslateFromSockAddrIn(addr); + if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; +} + +Errno Socket::Listen(s32 backlog) { + if (listen(fd, backlog) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; +} + +Errno Socket::Shutdown(ShutdownHow how) { + int host_how = 0; + switch (how) { + case ShutdownHow::RD: + host_how = SD_RECEIVE; + break; + case ShutdownHow::WR: + host_how = SD_SEND; + break; + case ShutdownHow::RDWR: + host_how = SD_BOTH; + break; + default: + UNIMPLEMENTED_MSG("Unimplemented flag how={}", static_cast<int>(how)); + return Errno::SUCCESS; + } + if (shutdown(fd, host_how) != SOCKET_ERROR) { + return Errno::SUCCESS; + } + + switch (const int ec = LastError()) { + case WSAENOTCONN: + LOG_ERROR(Service, "ENOTCONN generated"); + return Errno::NOTCONN; + default: + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; + } +} + +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 = + recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0); + if (result != SOCKET_ERROR) { + return {result, Errno::SUCCESS}; + } + + switch (const int ec = LastError()) { + case WSAEWOULDBLOCK: + LOG_DEBUG(Service, "EAGAIN generated"); + return {-1, Errno::AGAIN}; + case WSAENOTCONN: + LOG_ERROR(Service, "ENOTCONN generated"); + return {-1, Errno::NOTCONN}; + default: + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {0, Errno::SUCCESS}; + } +} + +std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + sockaddr addr_in{}; + socklen_t addrlen = sizeof(addr_in); + socklen_t* const p_addrlen = addr ? &addrlen : nullptr; + sockaddr* const p_addr_in = addr ? &addr_in : nullptr; + + const int 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}; + } + + switch (const int ec = LastError()) { + case WSAEWOULDBLOCK: + LOG_DEBUG(Service, "EAGAIN generated"); + return {-1, Errno::AGAIN}; + case WSAENOTCONN: + LOG_ERROR(Service, "ENOTCONN generated"); + return {-1, Errno::NOTCONN}; + default: + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {-1, Errno::SUCCESS}; + } +} + +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); + if (result != SOCKET_ERROR) { + return {result, Errno::SUCCESS}; + } + + const int ec = LastError(); + switch (ec) { + case WSAEWOULDBLOCK: + LOG_DEBUG(Service, "EAGAIN generated"); + return {-1, Errno::AGAIN}; + case WSAENOTCONN: + LOG_ERROR(Service, "ENOTCONN generated"); + return {-1, Errno::NOTCONN}; + default: + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {-1, Errno::SUCCESS}; + } +} + +std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) { + ASSERT(flags == 0); + + const sockaddr* to = nullptr; + const int tolen = addr ? 0 : sizeof(sockaddr); + sockaddr host_addr_in; + + if (addr) { + host_addr_in = TranslateFromSockAddrIn(*addr); + to = &host_addr_in; + } + + const int 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}; + } + + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return {-1, Errno::SUCCESS}; +} + +Errno Socket::Close() { + [[maybe_unused]] const int result = closesocket(fd); + ASSERT(result == 0); + fd = INVALID_SOCKET; + + return Errno::SUCCESS; +} + +Errno Socket::SetLinger(bool enable, u32 linger) { + return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger)); +} + +Errno Socket::SetReuseAddr(bool enable) { + return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0); +} + +Errno Socket::SetBroadcast(bool enable) { + return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); +} + +Errno Socket::SetSndBuf(u32 value) { + return SetSockOpt(fd, SO_SNDBUF, value); +} + +Errno Socket::SetRcvBuf(u32 value) { + return SetSockOpt(fd, SO_RCVBUF, value); +} + +Errno Socket::SetSndTimeo(u32 value) { + return SetSockOpt(fd, SO_SNDTIMEO, value); +} + +Errno Socket::SetRcvTimeo(u32 value) { + return SetSockOpt(fd, SO_RCVTIMEO, value); +} + +Errno Socket::SetNonBlock(bool enable) { + if (EnableNonBlock(fd, enable)) { + return Errno::SUCCESS; + } + const int ec = LastError(); + UNREACHABLE_MSG("Unhandled host socket error={}", ec); + return Errno::SUCCESS; +} + +bool Socket::IsOpened() const { + return fd != INVALID_SOCKET; +} + +} // namespace Network diff --git a/src/core/network/network.h b/src/core/network/network.h new file mode 100644 index 000000000..0622e4593 --- /dev/null +++ b/src/core/network/network.h @@ -0,0 +1,87 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <utility> + +#include "common/common_types.h" + +namespace Network { + +class Socket; + +/// Error code for network functions +enum class Errno { + SUCCESS, + BADF, + INVAL, + MFILE, + NOTCONN, + AGAIN, +}; + +/// Address families +enum class Domain { + INET, ///< Address family for IPv4 +}; + +/// Socket types +enum class Type { + STREAM, + DGRAM, + RAW, + SEQPACKET, +}; + +/// Protocol values for sockets +enum class Protocol { + ICMP, + TCP, + UDP, +}; + +/// Shutdown mode +enum class ShutdownHow { + RD, + WR, + RDWR, +}; + +/// Array of IPv4 address +using IPv4Address = std::array<u8, 4>; + +/// Cross-platform sockaddr structure +struct SockAddrIn { + Domain family; + IPv4Address ip; + u16 portno; +}; + +/// Cross-platform poll fd structure +struct PollFD { + Socket* socket; + u16 events; + u16 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(); + ~NetworkInstance(); +}; + +/// @brief Returns host's IPv4 address +/// @return Pair of an array of human ordered IPv4 address (e.g. 192.168.0.1) and an error code +std::pair<IPv4Address, Errno> GetHostIPv4Address(); + +} // namespace Network diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h new file mode 100644 index 000000000..7bdff0fe4 --- /dev/null +++ b/src/core/network/sockets.h @@ -0,0 +1,94 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <utility> + +#if defined(_WIN32) +#include <winsock.h> +#elif !defined(__unix__) +#error "Platform not implemented" +#endif + +#include "common/common_types.h" +#include "core/network/network.h" + +// TODO: C++20 Replace std::vector usages with std::span + +namespace Network { + +class Socket { +public: + struct AcceptResult { + std::unique_ptr<Socket> socket; + SockAddrIn sockaddr_in; + }; + + explicit Socket() = default; + ~Socket(); + + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + + Socket(Socket&& rhs) noexcept; + + // Avoid closing sockets implicitly + Socket& operator=(Socket&&) noexcept = delete; + + Errno Initialize(Domain domain, Type type, Protocol protocol); + + Errno Close(); + + std::pair<AcceptResult, Errno> Accept(); + + Errno Connect(SockAddrIn addr_in); + + std::pair<SockAddrIn, Errno> GetPeerName(); + + std::pair<SockAddrIn, Errno> GetSockName(); + + Errno Bind(SockAddrIn addr); + + Errno Listen(s32 backlog); + + Errno Shutdown(ShutdownHow how); + + std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message); + + std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr); + + std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags); + + std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr); + + Errno SetLinger(bool enable, u32 linger); + + Errno SetReuseAddr(bool enable); + + Errno SetBroadcast(bool enable); + + Errno SetSndBuf(u32 value); + + Errno SetRcvBuf(u32 value); + + Errno SetSndTimeo(u32 value); + + Errno SetRcvTimeo(u32 value); + + Errno SetNonBlock(bool enable); + + bool IsOpened() const; + +#if defined(_WIN32) + SOCKET fd = INVALID_SOCKET; +#elif defined(__unix__) + int fd = -1; +#endif +}; + +std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); + +} // namespace Network diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 29339ead7..b93396a80 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -38,11 +38,11 @@ PerfStats::~PerfStats() { std::ostringstream stream; std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, std::ostream_iterator<double>(stream, "\n")); - const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir); // %F Date format expanded is "%Y-%m-%d" const std::string filename = fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); - FileUtil::IOFile file(filename, "w"); + Common::FS::IOFile file(filename, "w"); file.WriteString(stream.str()); } @@ -74,15 +74,16 @@ void PerfStats::EndGameFrame() { game_frames += 1; } -double PerfStats::GetMeanFrametime() { +double PerfStats::GetMeanFrametime() const { std::lock_guard lock{object_mutex}; if (current_index <= IgnoreFrames) { return 0; } + const double sum = std::accumulate(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, 0.0); - return sum / (current_index - IgnoreFrames); + return sum / static_cast<double>(current_index - IgnoreFrames); } PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) { @@ -94,12 +95,13 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval; - PerfStatsResults results{}; - results.system_fps = static_cast<double>(system_frames) / interval; - results.game_fps = static_cast<double>(game_frames) / interval; - results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / - static_cast<double>(system_frames); - results.emulation_speed = system_us_per_second.count() / 1'000'000.0; + const PerfStatsResults results{ + .system_fps = static_cast<double>(system_frames) / interval, + .game_fps = static_cast<double>(game_frames) / interval, + .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / + static_cast<double>(system_frames), + .emulation_speed = system_us_per_second.count() / 1'000'000.0, + }; // Reset counters reset_point = now; @@ -111,7 +113,7 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us return results; } -double PerfStats::GetLastFrameTimeScale() { +double PerfStats::GetLastFrameTimeScale() const { std::lock_guard lock{object_mutex}; constexpr double FRAME_LENGTH = 1.0 / 60; diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index d9a64f072..69256b960 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -30,7 +30,6 @@ struct PerfStatsResults { class PerfStats { public: explicit PerfStats(u64 title_id); - ~PerfStats(); using Clock = std::chrono::high_resolution_clock; @@ -42,18 +41,18 @@ public: PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us); /** - * Returns the Arthimetic Mean of all frametime values stored in the performance history. + * Returns the arithmetic mean of all frametime values stored in the performance history. */ - double GetMeanFrametime(); + double GetMeanFrametime() const; /** * Gets the ratio between walltime and the emulated time of the previous system frame. This is * useful for scaling inputs or outputs moving between the two time domains. */ - double GetLastFrameTimeScale(); + double GetLastFrameTimeScale() const; private: - std::mutex object_mutex{}; + mutable std::mutex object_mutex; /// Title ID for the game that is running. 0 if there is no game running yet u64 title_id{0}; @@ -61,7 +60,7 @@ private: std::size_t current_index{0}; /// Stores an hour of historical frametime data useful for processing and tracking performance /// regressions with code changes. - std::array<double, 216000> perf_history = {}; + std::array<double, 216000> perf_history{}; /// Point when the cumulative counters were reset Clock::time_point reset_point = Clock::now(); diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 76cfa5a17..0becdf642 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -28,8 +28,9 @@ namespace { std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { - return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir), - type, title_id, timestamp); + return fmt::format("{}{}/{:016X}_{}.json", + Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id, + timestamp); } std::string GetTimestamp() { @@ -40,13 +41,13 @@ std::string GetTimestamp() { using namespace nlohmann; void SaveToFile(json json, const std::string& filename) { - if (!FileUtil::CreateFullPath(filename)) { + if (!Common::FS::CreateFullPath(filename)) { LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); return; } std::ofstream file( - FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault)); + Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault)); file << std::setw(4) << json << std::endl; } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index e8a6f2a6e..28d3f9099 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -13,56 +13,6 @@ namespace Settings { -namespace NativeButton { -const std::array<const char*, NumButtons> mapping = {{ - "button_a", - "button_b", - "button_x", - "button_y", - "button_lstick", - "button_rstick", - "button_l", - "button_r", - "button_zl", - "button_zr", - "button_plus", - "button_minus", - "button_dleft", - "button_dup", - "button_dright", - "button_ddown", - "button_lstick_left", - "button_lstick_up", - "button_lstick_right", - "button_lstick_down", - "button_rstick_left", - "button_rstick_up", - "button_rstick_right", - "button_rstick_down", - "button_sl", - "button_sr", - "button_home", - "button_screenshot", -}}; -} - -namespace NativeAnalog { -const std::array<const char*, NumAnalogs> mapping = {{ - "lstick", - "rstick", -}}; -} - -namespace NativeMouseButton { -const std::array<const char*, NumMouseButtons> mapping = {{ - "left", - "right", - "middle", - "forward", - "back", -}}; -} - Values values = {}; bool configuring_global = true; @@ -115,13 +65,14 @@ void LogSettings() { values.use_asynchronous_gpu_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()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); log_setting("Audio_OutputEngine", values.sink_id); 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_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)); - log_setting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)); + 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); @@ -170,8 +121,8 @@ void RestoreGlobalState() { values.use_asynchronous_gpu_emulation.SetGlobal(true); values.use_vsync.SetGlobal(true); values.use_assembly_shaders.SetGlobal(true); + values.use_asynchronous_shaders.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true); - values.force_30fps_mode.SetGlobal(true); values.bg_red.SetGlobal(true); values.bg_green.SetGlobal(true); values.bg_blue.SetGlobal(true); diff --git a/src/core/settings.h b/src/core/settings.h index a64debd25..9834f44bb 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -12,340 +12,10 @@ #include <string> #include <vector> #include "common/common_types.h" +#include "input_common/settings.h" namespace Settings { -namespace NativeButton { -enum Values { - A, - B, - X, - Y, - LStick, - RStick, - L, - R, - ZL, - ZR, - Plus, - Minus, - - DLeft, - DUp, - DRight, - DDown, - - LStick_Left, - LStick_Up, - LStick_Right, - LStick_Down, - - RStick_Left, - RStick_Up, - RStick_Right, - RStick_Down, - - SL, - SR, - - Home, - Screenshot, - - NumButtons, -}; - -constexpr int BUTTON_HID_BEGIN = A; -constexpr int BUTTON_NS_BEGIN = Home; - -constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN; -constexpr int BUTTON_NS_END = NumButtons; - -constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; -constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; - -extern const std::array<const char*, NumButtons> mapping; - -} // namespace NativeButton - -namespace NativeAnalog { -enum Values { - LStick, - RStick, - - NumAnalogs, -}; - -constexpr int STICK_HID_BEGIN = LStick; -constexpr int STICK_HID_END = NumAnalogs; -constexpr int NUM_STICKS_HID = NumAnalogs; - -extern const std::array<const char*, NumAnalogs> mapping; -} // namespace NativeAnalog - -namespace NativeMouseButton { -enum Values { - Left, - Right, - Middle, - Forward, - Back, - - NumMouseButtons, -}; - -constexpr int MOUSE_HID_BEGIN = Left; -constexpr int MOUSE_HID_END = NumMouseButtons; -constexpr int NUM_MOUSE_HID = NumMouseButtons; - -extern const std::array<const char*, NumMouseButtons> mapping; -} // namespace NativeMouseButton - -namespace NativeKeyboard { -enum Keys { - None, - Error, - - A = 4, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - N1, - N2, - N3, - N4, - N5, - N6, - N7, - N8, - N9, - N0, - Enter, - Escape, - Backspace, - Tab, - Space, - Minus, - Equal, - LeftBrace, - RightBrace, - Backslash, - Tilde, - Semicolon, - Apostrophe, - Grave, - Comma, - Dot, - Slash, - CapsLockKey, - - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - - SystemRequest, - ScrollLockKey, - Pause, - Insert, - Home, - PageUp, - Delete, - End, - PageDown, - Right, - Left, - Down, - Up, - - NumLockKey, - KPSlash, - KPAsterisk, - KPMinus, - KPPlus, - KPEnter, - KP1, - KP2, - KP3, - KP4, - KP5, - KP6, - KP7, - KP8, - KP9, - KP0, - KPDot, - - Key102, - Compose, - Power, - KPEqual, - - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - - Open, - Help, - Properties, - Front, - Stop, - Repeat, - Undo, - Cut, - Copy, - Paste, - Find, - Mute, - VolumeUp, - VolumeDown, - CapsLockActive, - NumLockActive, - ScrollLockActive, - KPComma, - - KPLeftParenthesis, - KPRightParenthesis, - - LeftControlKey = 0xE0, - LeftShiftKey, - LeftAltKey, - LeftMetaKey, - RightControlKey, - RightShiftKey, - RightAltKey, - RightMetaKey, - - MediaPlayPause, - MediaStopCD, - MediaPrevious, - MediaNext, - MediaEject, - MediaVolumeUp, - MediaVolumeDown, - MediaMute, - MediaWebsite, - MediaBack, - MediaForward, - MediaStop, - MediaFind, - MediaScrollUp, - MediaScrollDown, - MediaEdit, - MediaSleep, - MediaCoffee, - MediaRefresh, - MediaCalculator, - - NumKeyboardKeys, -}; - -static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); - -enum Modifiers { - LeftControl, - LeftShift, - LeftAlt, - LeftMeta, - RightControl, - RightShift, - RightAlt, - RightMeta, - CapsLock, - ScrollLock, - NumLock, - - NumKeyboardMods, -}; - -constexpr int KEYBOARD_KEYS_HID_BEGIN = None; -constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; -constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; - -constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; -constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; -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 MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; -using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; -using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; - -constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; -constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; -constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; -constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; - -enum class ControllerType { - ProController, - DualJoycon, - RightJoycon, - LeftJoycon, -}; - -struct PlayerInput { - bool connected; - ControllerType type; - ButtonsRaw buttons; - AnalogsRaw analogs; - - u32 body_color_right; - u32 button_color_right; - u32 body_color_left; - u32 button_color_left; -}; - -struct TouchscreenInput { - bool enabled; - std::string device; - - u32 finger; - u32 diameter_x; - u32 diameter_y; - u32 rotation_angle; -}; - enum class RendererBackend { OpenGL = 0, Vulkan = 1, @@ -359,7 +29,8 @@ enum class GPUAccuracy : u32 { enum class CPUAccuracy { Accurate = 0, - DebugMode = 1, + Unsafe = 1, + DebugMode = 2, }; extern bool configuring_global; @@ -396,6 +67,11 @@ private: Type local{}; }; +struct TouchFromButtonMap { + std::string name; + std::vector<std::string> buttons; +}; + struct Values { // Audio std::string audio_device_id; @@ -419,6 +95,9 @@ struct Values { bool cpuopt_misc_ir; bool cpuopt_reduce_misalign_checks; + bool cpuopt_unsafe_unfuse_fma; + bool cpuopt_unsafe_reduce_fp_error; + // Renderer Setting<RendererBackend> renderer_backend; bool renderer_debug; @@ -434,7 +113,7 @@ struct Values { Setting<bool> use_asynchronous_gpu_emulation; Setting<bool> use_vsync; Setting<bool> use_assembly_shaders; - Setting<bool> force_30fps_mode; + Setting<bool> use_asynchronous_shaders; Setting<bool> use_fast_gpu_time; Setting<float> bg_red; @@ -457,6 +136,8 @@ struct Values { // Controls std::array<PlayerInput, 10> players; + bool use_docked_mode; + bool mouse_enabled; std::string mouse_device; MouseButtonsRaw mouse_buttons; @@ -469,14 +150,19 @@ struct Values { ButtonsRaw debug_pad_buttons; AnalogsRaw debug_pad_analogs; + bool vibration_enabled; + + bool motion_enabled; std::string motion_device; + std::string touch_device; TouchscreenInput touchscreen; std::atomic_bool is_device_reload_pending{true}; + bool use_touch_from_button; + int touch_from_button_map_index; std::string udp_input_address; u16 udp_input_port; u8 udp_pad_index; - - bool use_docked_mode; + std::vector<TouchFromButtonMap> touch_from_button_maps; // Data Storage bool use_virtual_sd; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 78915e6db..da09c0dbc 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -25,6 +25,8 @@ namespace Core { +namespace Telemetry = Common::Telemetry; + static u64 GenerateTelemetryId() { u64 telemetry_id{}; @@ -70,12 +72,12 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) { u64 GetTelemetryId() { u64 telemetry_id{}; - const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + + const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "telemetry_id"}; - bool generate_new_id = !FileUtil::Exists(filename); + bool generate_new_id = !Common::FS::Exists(filename); if (!generate_new_id) { - FileUtil::IOFile file(filename, "rb"); + Common::FS::IOFile file(filename, "rb"); if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; @@ -88,7 +90,7 @@ u64 GetTelemetryId() { } if (generate_new_id) { - FileUtil::IOFile file(filename, "wb"); + Common::FS::IOFile file(filename, "wb"); if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; @@ -102,10 +104,10 @@ u64 GetTelemetryId() { u64 RegenerateTelemetryId() { const u64 new_telemetry_id{GenerateTelemetryId()}; - const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + + const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + "telemetry_id"}; - FileUtil::IOFile file(filename, "wb"); + Common::FS::IOFile file(filename, "wb"); if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); return {}; @@ -207,6 +209,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { 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); } diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 17ac22377..66789d4bd 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -52,7 +52,7 @@ public: * @param value Value for the field to add. */ template <typename T> - void AddField(Telemetry::FieldType type, const char* name, T value) { + void AddField(Common::Telemetry::FieldType type, const char* name, T value) { field_collection.AddField(type, name, std::move(value)); } @@ -63,7 +63,8 @@ public: bool SubmitTestcase(); private: - Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session + /// Tracks all added fields for the session + Common::Telemetry::FieldCollection field_collection; }; /** diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp index 8b0c50d11..5c674a099 100644 --- a/src/core/tools/freezer.cpp +++ b/src/core/tools/freezer.cpp @@ -14,7 +14,7 @@ namespace Tools { namespace { -constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(1000000000 / 60); +constexpr auto memory_freezer_ns = std::chrono::nanoseconds{1000000000 / 60}; u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { switch (width) { @@ -57,8 +57,10 @@ Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& m : core_timing{core_timing_}, memory{memory_} { event = Core::Timing::CreateEvent( "MemoryFreezer::FrameCallback", - [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); - core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + FrameCallback(user_data, ns_late); + }); + core_timing.ScheduleEvent(memory_freezer_ns, event); } Freezer::~Freezer() { @@ -68,7 +70,7 @@ Freezer::~Freezer() { void Freezer::SetActive(bool active) { if (!this->active.exchange(active)) { FillEntryReads(); - core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); + core_timing.ScheduleEvent(memory_freezer_ns, event); LOG_DEBUG(Common_Memory, "Memory freezer activated!"); } else { LOG_DEBUG(Common_Memory, "Memory freezer deactivated!"); @@ -105,28 +107,21 @@ void Freezer::Unfreeze(VAddr address) { LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address); - entries.erase( - std::remove_if(entries.begin(), entries.end(), - [&address](const Entry& entry) { return entry.address == address; }), - entries.end()); + std::erase_if(entries, [address](const Entry& entry) { return entry.address == address; }); } bool Freezer::IsFrozen(VAddr address) const { std::lock_guard lock{entries_mutex}; - return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { - return entry.address == address; - }) != entries.end(); + return FindEntry(address) != entries.cend(); } void Freezer::SetFrozenValue(VAddr address, u64 value) { std::lock_guard lock{entries_mutex}; - const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { - return entry.address == address; - }); + const auto iter = FindEntry(address); - if (iter == entries.end()) { + if (iter == entries.cend()) { LOG_ERROR(Common_Memory, "Tried to set freeze value for address={:016X} that is not frozen!", address); return; @@ -141,11 +136,9 @@ void Freezer::SetFrozenValue(VAddr address, u64 value) { std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const { std::lock_guard lock{entries_mutex}; - const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { - return entry.address == address; - }); + const auto iter = FindEntry(address); - if (iter == entries.end()) { + if (iter == entries.cend()) { return std::nullopt; } @@ -158,7 +151,17 @@ std::vector<Freezer::Entry> Freezer::GetEntries() const { return entries; } -void Freezer::FrameCallback(u64 userdata, s64 ns_late) { +Freezer::Entries::iterator Freezer::FindEntry(VAddr address) { + return std::find_if(entries.begin(), entries.end(), + [address](const Entry& entry) { return entry.address == address; }); +} + +Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const { + return std::find_if(entries.begin(), entries.end(), + [address](const Entry& entry) { return entry.address == address; }); +} + +void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) { if (!IsActive()) { LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); return; @@ -173,7 +176,7 @@ void Freezer::FrameCallback(u64 userdata, s64 ns_late) { MemoryWriteWidth(memory, entry.width, entry.address, entry.value); } - core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - ns_late, event); + core_timing.ScheduleEvent(memory_freezer_ns - ns_late, event); } void Freezer::FillEntryReads() { diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h index 62fc6aa6c..0fdb701a7 100644 --- a/src/core/tools/freezer.h +++ b/src/core/tools/freezer.h @@ -5,6 +5,7 @@ #pragma once #include <atomic> +#include <chrono> #include <memory> #include <mutex> #include <optional> @@ -72,13 +73,18 @@ public: std::vector<Entry> GetEntries() const; private: - void FrameCallback(u64 userdata, s64 cycles_late); + using Entries = std::vector<Entry>; + + Entries::iterator FindEntry(VAddr address); + Entries::const_iterator FindEntry(VAddr address) const; + + void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void FillEntryReads(); std::atomic_bool active{false}; mutable std::mutex entries_mutex; - std::vector<Entry> entries; + Entries entries; std::shared_ptr<Core::Timing::EventType> event; Core::Timing::CoreTiming& core_timing; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 317c25bad..c84685214 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,6 +7,14 @@ add_library(input_common STATIC 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 diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 898a278a9..89c148aba 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,9 +4,20 @@ #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 { @@ -24,12 +35,9 @@ Adapter::Adapter() { } LOG_INFO(Input, "GC Adapter Initialization started"); - current_status = NO_ADAPTER_DETECTED; - get_origin.fill(true); - const int init_res = libusb_init(&libusb_ctx); if (init_res == LIBUSB_SUCCESS) { - StartScanThread(); + Setup(); } else { LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); } @@ -37,9 +45,9 @@ Adapter::Adapter() { GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { GCPadStatus pad = {}; + const std::size_t offset = 1 + (9 * port); - ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4); - adapter_controllers_status[port] = type; + adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); static constexpr std::array<PadButton, 8> b1_buttons{ PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, @@ -54,14 +62,19 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad PadButton::PAD_TRIGGER_L, }; + static constexpr std::array<PadAxes, 6> axes{ + PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, + PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, + }; + if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { // Controller may have been disconnected, recalibrate if reconnected. get_origin[port] = true; } if (adapter_controllers_status[port] != ControllerTypes::None) { - const u8 b1 = adapter_payload[1 + (9 * port) + 1]; - const u8 b2 = adapter_payload[1 + (9 * port) + 2]; + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; for (std::size_t i = 0; i < b1_buttons.size(); ++i) { if ((b1 & (1U << i)) != 0) { @@ -74,21 +87,13 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad pad.button |= static_cast<u16>(b2_buttons[j]); } } - - pad.stick_x = adapter_payload[1 + (9 * port) + 3]; - pad.stick_y = adapter_payload[1 + (9 * port) + 4]; - pad.substick_x = adapter_payload[1 + (9 * port) + 5]; - pad.substick_y = adapter_payload[1 + (9 * port) + 6]; - pad.trigger_left = adapter_payload[1 + (9 * port) + 7]; - pad.trigger_right = adapter_payload[1 + (9 * port) + 8]; + for (PadAxes axis : axes) { + const std::size_t index = static_cast<std::size_t>(axis); + pad.axis_values[index] = adapter_payload[offset + 3 + index]; + } if (get_origin[port]) { - origin_status[port].stick_x = pad.stick_x; - origin_status[port].stick_y = pad.stick_y; - origin_status[port].substick_x = pad.substick_x; - origin_status[port].substick_y = pad.substick_y; - origin_status[port].trigger_left = pad.trigger_left; - origin_status[port].trigger_right = pad.trigger_right; + origin_status[port].axis_values = pad.axis_values; get_origin[port] = false; } } @@ -101,82 +106,47 @@ void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { state.buttons.insert_or_assign(button_value, pad.button & button_value); } - state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x); - state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y); - state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x); - state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y); - state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left); - state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right); + for (size_t i = 0; i < pad.axis_values.size(); ++i) { + state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]); + } } void Adapter::Read() { LOG_DEBUG(Input, "GC Adapter Read() thread started"); - int payload_size_in, payload_size_copy; + int payload_size; std::array<u8, 37> adapter_payload; - std::array<u8, 37> adapter_payload_copy; std::array<GCPadStatus, 4> pads; while (adapter_thread_running) { libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), - sizeof(adapter_payload), &payload_size_in, 16); - payload_size_copy = 0; - // this mutex might be redundant? - { - std::lock_guard<std::mutex> lk(s_mutex); - std::copy(std::begin(adapter_payload), std::end(adapter_payload), - std::begin(adapter_payload_copy)); - payload_size_copy = payload_size_in; - } + sizeof(adapter_payload), &payload_size, 16); - if (payload_size_copy != sizeof(adapter_payload_copy) || - adapter_payload_copy[0] != LIBUSB_DT_HID) { - LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy, - adapter_payload_copy[0]); + 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_copy); + pads[port] = GetPadStatus(port, adapter_payload); if (DeviceConnected(port) && configuring) { if (pads[port].button != 0) { pad_queue[port].Push(pads[port]); } - // Accounting for a threshold here because of some controller variance - if (pads[port].stick_x > origin_status[port].stick_x + pads[port].THRESHOLD || - pads[port].stick_x < origin_status[port].stick_x - pads[port].THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::StickX; - pads[port].axis_value = pads[port].stick_x; - pad_queue[port].Push(pads[port]); - } - if (pads[port].stick_y > origin_status[port].stick_y + pads[port].THRESHOLD || - pads[port].stick_y < origin_status[port].stick_y - pads[port].THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::StickY; - pads[port].axis_value = pads[port].stick_y; - pad_queue[port].Push(pads[port]); - } - if (pads[port].substick_x > origin_status[port].substick_x + pads[port].THRESHOLD || - pads[port].substick_x < origin_status[port].substick_x - pads[port].THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::SubstickX; - pads[port].axis_value = pads[port].substick_x; - pad_queue[port].Push(pads[port]); - } - if (pads[port].substick_y > origin_status[port].substick_y + pads[port].THRESHOLD || - pads[port].substick_y < origin_status[port].substick_y - pads[port].THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::SubstickY; - pads[port].axis_value = pads[port].substick_y; - pad_queue[port].Push(pads[port]); - } - if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::TriggerLeft; - pads[port].axis_value = pads[port].trigger_left; - pad_queue[port].Push(pads[port]); - } - if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) { - pads[port].axis = GCAdapter::PadAxes::TriggerRight; - pads[port].axis_value = pads[port].trigger_right; - pad_queue[port].Push(pads[port]); + // 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]); @@ -185,42 +155,11 @@ void Adapter::Read() { } } -void Adapter::ScanThreadFunc() { - LOG_INFO(Input, "GC Adapter scanning thread started"); - - while (detect_thread_running) { - if (usb_adapter_handle == nullptr) { - std::lock_guard<std::mutex> lk(initialization_mutex); - Setup(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } -} - -void Adapter::StartScanThread() { - if (detect_thread_running) { - return; - } - if (!libusb_ctx) { - return; - } - - detect_thread_running = true; - detect_thread = std::thread(&Adapter::ScanThreadFunc, this); -} - -void Adapter::StopScanThread() { - detect_thread_running = false; - detect_thread.join(); -} - void Adapter::Setup() { - // Reset the error status in case the adapter gets unplugged - if (current_status < 0) { - current_status = NO_ADAPTER_DETECTED; - } - + // 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{}; @@ -229,8 +168,6 @@ void Adapter::Setup() { 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); - detect_thread_running = false; // Stop the loop constantly checking for gc adapter - // TODO: For hotplug+gc adapter checkbox implementation, revert this. return; } @@ -244,9 +181,6 @@ void Adapter::Setup() { } libusb_free_device_list(devices, 1); } - // Break out of the ScanThreadFunc() loop that is constantly looking for the device - // Assumes user has GC adapter plugged in before launch to use the adapter - detect_thread_running = false; } bool Adapter::CheckDeviceAccess(libusb_device* device) { @@ -331,32 +265,23 @@ void Adapter::GetGCEndpoint(libusb_device* device) { sizeof(clear_payload), nullptr, 16); adapter_thread_running = true; - current_status = ADAPTER_DETECTED; - adapter_input_thread = std::thread([=] { Read(); }); // Read input + adapter_input_thread = std::thread(&Adapter::Read, this); } Adapter::~Adapter() { - StopScanThread(); Reset(); } void Adapter::Reset() { - std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock); - if (!lock.try_lock()) { - return; - } - if (current_status != ADAPTER_DETECTED) { - return; - } - if (adapter_thread_running) { adapter_thread_running = false; } - adapter_input_thread.join(); + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } adapter_controllers_status.fill(ControllerTypes::None); get_origin.fill(true); - current_status = NO_ADAPTER_DETECTED; if (usb_adapter_handle) { libusb_release_interface(usb_adapter_handle, 1); @@ -369,7 +294,93 @@ void Adapter::Reset() { } } -bool Adapter::DeviceConnected(std::size_t port) { +std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + for (std::size_t port = 0; port < state.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + std::string name = fmt::format("Gamecube Controller {}", port); + devices.emplace_back(Common::ParamPackage{ + {"class", "gcpad"}, + {"display", std::move(name)}, + {"port", std::to_string(port)}, + }); + } + return devices; +} + +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::PAD_BUTTON_A}, + {Settings::NativeButton::B, PadButton::PAD_BUTTON_B}, + {Settings::NativeButton::X, PadButton::PAD_BUTTON_X}, + {Settings::NativeButton::Y, PadButton::PAD_BUTTON_Y}, + {Settings::NativeButton::Plus, PadButton::PAD_BUTTON_START}, + {Settings::NativeButton::DLeft, PadButton::PAD_BUTTON_LEFT}, + {Settings::NativeButton::DUp, PadButton::PAD_BUTTON_UP}, + {Settings::NativeButton::DRight, PadButton::PAD_BUTTON_RIGHT}, + {Settings::NativeButton::DDown, PadButton::PAD_BUTTON_DOWN}, + {Settings::NativeButton::SL, PadButton::PAD_TRIGGER_L}, + {Settings::NativeButton::SR, PadButton::PAD_TRIGGER_R}, + {Settings::NativeButton::R, PadButton::PAD_TRIGGER_Z}, + }; + 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<int>(PadButton::PAD_STICK)); + button_params.Set("axis", static_cast<int>(gcadapter_axis)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + return mapping; +} + +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 adapter_controllers_status[port] != ControllerTypes::None; } @@ -409,24 +420,7 @@ const std::array<GCState, 4>& Adapter::GetPadState() const { } int Adapter::GetOriginValue(int port, int axis) const { - const auto& status = origin_status[port]; - - switch (static_cast<PadAxes>(axis)) { - case PadAxes::StickX: - return status.stick_x; - case PadAxes::StickY: - return status.stick_y; - case PadAxes::SubstickX: - return status.substick_x; - case PadAxes::SubstickY: - return status.substick_y; - case PadAxes::TriggerLeft: - return status.trigger_left; - case PadAxes::TriggerRight: - return status.trigger_right; - default: - return 0; - } + return origin_status[port].axis_values[axis]; } } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index 3586c8bda..75bf9fe74 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; @@ -47,24 +48,10 @@ enum class PadAxes : u8 { }; struct GCPadStatus { - u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - u8 stick_x{}; // 0 <= stick_x <= 255 - u8 stick_y{}; // 0 <= stick_y <= 255 - u8 substick_x{}; // 0 <= substick_x <= 255 - u8 substick_y{}; // 0 <= substick_y <= 255 - u8 trigger_left{}; // 0 <= trigger_left <= 255 - u8 trigger_right{}; // 0 <= trigger_right <= 255 - - static constexpr u8 MAIN_STICK_CENTER_X = 0x80; - static constexpr u8 MAIN_STICK_CENTER_Y = 0x80; - static constexpr u8 MAIN_STICK_RADIUS = 0x7f; - static constexpr u8 C_STICK_CENTER_X = 0x80; - static constexpr u8 C_STICK_CENTER_Y = 0x80; - static constexpr u8 C_STICK_RADIUS = 0x7f; - static constexpr u8 THRESHOLD = 10; - - // 256/4, at least a quarter press to count as a press. For polling mostly - static constexpr u8 TRIGGER_THRESHOLD = 64; + u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + + 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 u8 port{}; PadAxes axis{PadAxes::Undefined}; @@ -78,11 +65,6 @@ struct GCState { enum class ControllerTypes { None, Wired, Wireless }; -enum { - NO_ADAPTER_DETECTED = 0, - ADAPTER_DETECTED = 1, -}; - class Adapter { public: /// Initialize the GC Adapter capture and read sequence @@ -94,8 +76,12 @@ public: void BeginConfiguration(); void EndConfiguration(); + std::vector<Common::ParamPackage> GetInputDevices() const; + InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; + /// Returns true if there is a device connected to port - bool DeviceConnected(std::size_t port); + bool DeviceConnected(std::size_t port) const; std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; @@ -111,12 +97,6 @@ private: void PadToState(const GCPadStatus& pad, GCState& state); void Read(); - void ScanThreadFunc(); - /// Begin scanning for the GC Adapter. - void StartScanThread(); - - /// Stop scanning for the adapter - void StopScanThread(); /// Resets status of device connected to port void ResetDeviceType(std::size_t port); @@ -133,19 +113,11 @@ private: /// For use in initialization, querying devices to find the adapter void Setup(); - int current_status = NO_ADAPTER_DETECTED; libusb_device_handle* usb_adapter_handle = nullptr; - std::array<ControllerTypes, 4> adapter_controllers_status{}; - - std::mutex s_mutex; std::thread adapter_input_thread; bool adapter_thread_running; - std::mutex initialization_mutex; - std::thread detect_thread; - bool detect_thread_running = false; - libusb_context* libusb_ctx; u8 input_endpoint = 0; @@ -153,10 +125,11 @@ private: bool configuring = false; - std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; 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{}; }; } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 96e22d3ad..92e9e8e89 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,7 +15,7 @@ namespace InputCommon { class GCButton final : public Input::ButtonDevice { public: - explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) + explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter) : port(port_), button(button_), gcadapter(adapter) {} ~GCButton() override; @@ -30,15 +30,16 @@ public: private: const int port; const int button; - GCAdapter::Adapter* gcadapter; + 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) + 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), + origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {} bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { @@ -59,7 +60,7 @@ private: const int axis; float threshold; bool trigger_if_greater; - GCAdapter::Adapter* gcadapter; + const GCAdapter::Adapter* gcadapter; const float origin_value; }; @@ -76,8 +77,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param // button is not an axis/stick button if (button_id != PAD_STICK_ID) { - auto button = std::make_unique<GCButton>(port, button_id, adapter.get()); - return std::move(button); + return std::make_unique<GCButton>(port, button_id, adapter.get()); } // For Axis buttons, used by the binary sticks. @@ -149,19 +149,18 @@ void GCButtonFactory::EndConfiguration() { class GCAnalog final : public Input::AnalogDevice { public: - GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter) + GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, + const 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_)) {} + origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))), + origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))), + range(range_) {} float GetAxis(int axis) const { if (gcadapter->DeviceConnected(port)) { std::lock_guard lock{mutex}; const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; - // division is not by a perfect 128 to account for some variance in center location - // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range - // [20-230] - return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / 95.0f; + return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); } return 0.0f; } @@ -194,7 +193,7 @@ public: bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.4f; + const float directional_deadzone = 0.5f; switch (direction) { case Input::AnalogDirection::RIGHT: return x > directional_deadzone; @@ -213,9 +212,10 @@ private: const int axis_x; const int axis_y; const float deadzone; - GCAdapter::Adapter* gcadapter; + const GCAdapter::Adapter* gcadapter; const float origin_value_x; const float origin_value_y; + const float range; mutable std::mutex mutex; }; @@ -234,9 +234,10 @@ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::Param const int port = params.Get("port", 0); const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); + 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()); + return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range); } void GCAnalogFactory::BeginConfiguration() { @@ -264,7 +265,8 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { 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 == port) { + } else if (analog_y_axis == -1 && analog_x_axis != axis && + controller_number == static_cast<int>(port)) { analog_y_axis = axis; } } diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b9d5d0ec3..3d97d95f7 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -11,6 +11,9 @@ #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/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" @@ -18,67 +21,227 @@ namespace InputCommon { -static std::shared_ptr<Keyboard> keyboard; -static std::shared_ptr<MotionEmu> motion_emu; +struct InputSubsystem::Impl { + void Initialize() { + 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); + + keyboard = std::make_shared<Keyboard>(); + Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); + Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", + std::make_shared<AnalogFromButton>()); + Input::RegisterFactory<Input::MotionDevice>("keyboard", + std::make_shared<MotionFromButton>()); + motion_emu = std::make_shared<MotionEmu>(); + Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); + Input::RegisterFactory<Input::TouchDevice>("touch_from_button", + std::make_shared<TouchFromButtonFactory>()); + #ifdef HAVE_SDL2 -static std::unique_ptr<SDL::State> sdl; + sdl = SDL::Init(); #endif -static std::unique_ptr<CemuhookUDP::State> udp; -static std::shared_ptr<GCButtonFactory> gcbuttons; -static std::shared_ptr<GCAnalogFactory> gcanalog; - -void Init() { - auto 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); - - 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); + 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); + } + + 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 = SDL::Init(); + sdl.reset(); #endif + Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); + Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); - udp = CemuhookUDP::Init(); -} + gcbuttons.reset(); + gcanalog.reset(); + + Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); + Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); + + udpmotion.reset(); + udptouch.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"}}, + }; +#ifdef HAVE_SDL2 + auto sdl_devices = sdl->GetInputDevices(); + devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); +#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; + } + + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + 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") { + return sdl->GetAnalogMappingForDevice(params); + } +#endif + return {}; + } + + [[nodiscard]] ButtonMapping GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + 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") { + return sdl->GetButtonMappingForDevice(params); + } +#endif + 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 {}; + } -void Shutdown() { - Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); - keyboard.reset(); - Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); - Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); - motion_emu.reset(); + std::shared_ptr<Keyboard> keyboard; + std::shared_ptr<MotionEmu> motion_emu; #ifdef HAVE_SDL2 - sdl.reset(); + std::unique_ptr<SDL::State> sdl; #endif - udp.reset(); - Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); - Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); + std::shared_ptr<GCButtonFactory> gcbuttons; + std::shared_ptr<GCAnalogFactory> gcanalog; + std::shared_ptr<UDPMotionFactory> udpmotion; + std::shared_ptr<UDPTouchFactory> udptouch; + std::shared_ptr<CemuhookUDP::Client> udp; + std::shared_ptr<GCAdapter::Adapter> gcadapter; +}; + +InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} + +InputSubsystem::~InputSubsystem() = default; + +void InputSubsystem::Initialize() { + impl->Initialize(); +} + +void InputSubsystem::Shutdown() { + impl->Shutdown(); +} + +Keyboard* InputSubsystem::GetKeyboard() { + return impl->keyboard.get(); +} + +const Keyboard* InputSubsystem::GetKeyboard() const { + return impl->keyboard.get(); +} + +MotionEmu* InputSubsystem::GetMotionEmu() { + return impl->motion_emu.get(); +} - gcbuttons.reset(); - gcanalog.reset(); +const MotionEmu* InputSubsystem::GetMotionEmu() const { + return impl->motion_emu.get(); } -Keyboard* GetKeyboard() { - return keyboard.get(); +std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { + return impl->GetInputDevices(); } -MotionEmu* GetMotionEmu() { - return motion_emu.get(); +AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const { + return impl->GetAnalogMappingForDevice(device); } -GCButtonFactory* GetGCButtons() { - return gcbuttons.get(); +ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const { + return impl->GetButtonMappingForDevice(device); } -GCAnalogFactory* GetGCAnalogs() { - return gcanalog.get(); +GCAnalogFactory* InputSubsystem::GetGCAnalogs() { + return impl->gcanalog.get(); +} + +const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const { + return impl->gcanalog.get(); +} + +GCButtonFactory* InputSubsystem::GetGCButtons() { + return impl->gcbuttons.get(); +} + +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(); +} + +void InputSubsystem::ReloadInputDevices() { + if (!impl->udp) { + return; + } + impl->udp->ReloadUDPClient(); +} + +std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( + Polling::DeviceType type) const { +#ifdef HAVE_SDL2 + return impl->sdl->GetPollers(type); +#else + return {}; +#endif } std::string GenerateKeyboardParam(int key_code) { @@ -102,18 +265,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, }; return circle_pad_param.Serialize(); } - -namespace Polling { - -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { - std::vector<std::unique_ptr<DevicePoller>> pollers; - -#ifdef HAVE_SDL2 - pollers = sdl->GetPollers(type); -#endif - - return pollers; -} - -} // namespace Polling } // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h index 0e32856f6..dded3f1ef 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -6,45 +6,29 @@ #include <memory> #include <string> +#include <unordered_map> #include <vector> -#include "input_common/gcadapter/gc_poller.h" namespace Common { class ParamPackage; } -namespace InputCommon { - -/// Initializes and registers all built-in input device factories. -void Init(); - -/// Deregisters all built-in input device factories and shuts them down. -void Shutdown(); - -class Keyboard; - -/// Gets the keyboard button device factory. -Keyboard* GetKeyboard(); - -class MotionEmu; - -/// Gets the motion emulation factory. -MotionEmu* GetMotionEmu(); - -GCButtonFactory* GetGCButtons(); - -GCAnalogFactory* GetGCAnalogs(); +namespace Settings::NativeAnalog { +enum Values : int; +} -/// Generates a serialized param package for creating a keyboard button device -std::string GenerateKeyboardParam(int key_code); +namespace Settings::NativeButton { +enum Values : int; +} -/// Generates a serialized param package for creating an analog device taking input from keyboard -std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, - int key_modifier, float modifier_scale); +namespace Settings::NativeMotion { +enum Values : int; +} +namespace InputCommon { namespace Polling { -enum class DeviceType { Button, Analog }; +enum class DeviceType { Button, AnalogPreferred, Motion }; /** * A class that can be used to get inputs from an input device like controllers without having to @@ -54,7 +38,9 @@ class DevicePoller { public: virtual ~DevicePoller() = default; /// Setup and start polling for inputs, should be called before GetNextInput - virtual void Start() = 0; + /// If a device_id is provided, events should be filtered to only include events from this + /// device id + virtual void Start(const std::string& device_id = "") = 0; /// Stop polling virtual void Stop() = 0; /** @@ -64,8 +50,110 @@ public: */ virtual Common::ParamPackage GetNextInput() = 0; }; - -// Get all DevicePoller from all backends for a specific device type -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type); } // namespace Polling + +class GCAnalogFactory; +class GCButtonFactory; +class UDPMotionFactory; +class UDPTouchFactory; +class Keyboard; +class MotionEmu; + +/** + * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default + * mapping for the device. This is currently only implemented for the SDL backend devices. + */ +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: + explicit InputSubsystem(); + ~InputSubsystem(); + + InputSubsystem(const InputSubsystem&) = delete; + InputSubsystem& operator=(const InputSubsystem&) = delete; + + InputSubsystem(InputSubsystem&&) = delete; + InputSubsystem& operator=(InputSubsystem&&) = delete; + + /// Initializes and registers all built-in input device factories. + void Initialize(); + + /// Unregisters all built-in input device factories and shuts them down. + void Shutdown(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] Keyboard* GetKeyboard(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] const Keyboard* GetKeyboard() const; + + /// Retrieves the underlying motion emulation factory. + [[nodiscard]] MotionEmu* GetMotionEmu(); + + /// Retrieves the underlying motion emulation factory. + [[nodiscard]] const MotionEmu* GetMotionEmu() const; + + /** + * Returns all available input devices that this Factory can create a new device with. + * Each returned ParamPackage should have a `display` field used for display, a class field for + * backends to determine if this backend is meant to service the request and any other + * information needed to identify this in the backend later. + */ + [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; + + /// Retrieves the analog mappings for the given device. + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const; + + /// 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(); + + /// Retrieves the underlying GameCube analog handler. + [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] GCButtonFactory* GetGCButtons(); + + /// 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; + + /// 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; + +private: + struct Impl; + std::unique_ptr<Impl> impl; +}; + +/// Generates a serialized param package for creating a keyboard button device +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale); + } // namespace InputCommon diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index d4cdf76a3..69fd3c1d2 100644 --- a/src/input_common/motion_emu.cpp +++ b/src/input_common/motion_emu.cpp @@ -56,7 +56,7 @@ public: is_tilting = false; } - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { + Input::MotionStatus GetStatus() { std::lock_guard guard{status_mutex}; return status; } @@ -76,7 +76,7 @@ private: Common::Event shutdown_event; - std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; + Input::MotionStatus status; std::mutex status_mutex; // Note: always keep the thread declaration at the end so that other objects are initialized @@ -113,10 +113,19 @@ private: gravity = QuaternionRotate(inv_q, gravity); angular_rate = QuaternionRotate(inv_q, angular_rate); + // TODO: Calculate the correct rotation vector and orientation matrix + const auto matrix4x4 = q.ToMatrix(); + const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f); + const std::array orientation{ + Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]), + }; + // Update the sensor state { std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate); + status = std::make_tuple(gravity, angular_rate, rotation, orientation); } } } @@ -131,7 +140,7 @@ public: device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); } - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { + Input::MotionStatus GetStatus() const override { return device->GetStatus(); } diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp new file mode 100644 index 000000000..9d459f963 --- /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>; + + 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..e89019723 --- /dev/null +++ b/src/input_common/motion_input.cpp @@ -0,0 +1,305 @@ +// 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), quat{{0, 0, -1}, 0} {} + +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 f32 sample_period = 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 f32 sample_period = 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 acelerometer 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 = { + distribution(gen) * 0.001f, + distribution(gen) * 0.001f, + distribution(gen) * 0.001f, + }; + const Common::Vec3f accelerometer = { + distribution(gen) * 0.001f, + distribution(gen) * 0.001f, + distribution(gen) * 0.001f, + }; + const Common::Vec3f rotation = {}; + const std::array<Common::Vec3f, 3> orientation = { + Common::Vec3f{1.0f, 0, 0}, + Common::Vec3f{0, 1.0f, 0}, + Common::Vec3f{0, 0, 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(); + const f32 ax = -normal_accel.x; + const f32 ay = normal_accel.y; + const f32 az = -normal_accel.z; + + 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..6342d0318 --- /dev/null +++ b/src/input_common/motion_input.h @@ -0,0 +1,73 @@ +// 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: + 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& acceleration); + 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); + + std::array<Common::Vec3f, 3> GetOrientation() const; + Common::Vec3f GetAcceleration() const; + Common::Vec3f GetGyroscope() const; + Common::Vec3f GetRotations() const; + Common::Quaternion<f32> GetQuaternion() const; + Input::MotionStatus GetMotion() const; + Input::MotionStatus GetRandomMotion(int accel_magnitude, int gyro_magnitude) const; + + bool IsMoving(f32 sensitivity) const; + bool IsCalibrated(f32 sensitivity) const; + +private: + void ResetOrientation(); + void SetOrientationFromAccelerometer(); + + // PID constants + const f32 kp; + const f32 ki; + const f32 kd; + + // PID errors + Common::Vec3f real_error; + Common::Vec3f integral_error; + Common::Vec3f derivative_error; + + Common::Quaternion<f32> quat; + 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/sdl/sdl.h b/src/input_common/sdl/sdl.h index 5306daa70..f3554be9a 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -6,6 +6,7 @@ #include <memory> #include <vector> +#include "common/param_package.h" #include "input_common/main.h" namespace InputCommon::Polling { @@ -22,14 +23,24 @@ public: /// Unregisters SDL device factories and shut them down. virtual ~State() = default; - virtual Pollers GetPollers(Polling::DeviceType type) = 0; + virtual Pollers GetPollers(Polling::DeviceType type) { + return {}; + } + + virtual std::vector<Common::ParamPackage> GetInputDevices() { + return {}; + } + + virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) { + return {}; + } + virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { + return {}; + } }; class NullState : public State { public: - Pollers GetPollers(Polling::DeviceType type) override { - return {}; - } }; std::unique_ptr<State> Init(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 675b477fa..bd480570a 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -3,10 +3,14 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <atomic> +#include <chrono> #include <cmath> #include <functional> #include <mutex> +#include <optional> +#include <sstream> #include <string> #include <thread> #include <tuple> @@ -15,15 +19,17 @@ #include <vector> #include <SDL.h> #include "common/logging/log.h" -#include "common/math_util.h" #include "common/param_package.h" #include "common/threadsafe_queue.h" #include "core/frontend/input.h" +#include "input_common/motion_input.h" #include "input_common/sdl/sdl_impl.h" +#include "input_common/settings.h" namespace InputCommon::SDL { -static std::string GetGUID(SDL_Joystick* joystick) { +namespace { +std::string GetGUID(SDL_Joystick* joystick) { const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); char guid_str[33]; SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); @@ -31,7 +37,8 @@ static std::string GetGUID(SDL_Joystick* joystick) { } /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +} // Anonymous namespace static int SDLEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast<SDLState*>(user_data); @@ -48,8 +55,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) { class SDLJoystick { public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {} + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + SDL_GameController* gamecontroller) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + sdl_controller{gamecontroller, &SDL_GameControllerClose} {} void SetButton(int button, bool value) { std::lock_guard lock{mutex}; @@ -66,14 +75,41 @@ public: state.axes.insert_or_assign(axis, value); } - float GetAxis(int axis) const { + float GetAxis(int axis, float range) const { std::lock_guard lock{mutex}; - return state.axes.at(axis) / 32767.0f; + return state.axes.at(axis) / (32767.0f * range); } - std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); + bool RumblePlay(f32 amp_low, f32 amp_high, int time) { + const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF); + const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF); + // Lower drastically the number of state changes + if (raw_amp_low >> 11 == last_state_rumble_low >> 11 && + raw_amp_high >> 11 == last_state_rumble_high >> 11) { + if (raw_amp_low + raw_amp_high != 0 || + last_state_rumble_low + last_state_rumble_high == 0) { + return false; + } + } + // Don't change state if last vibration was < 20ms + const auto now = std::chrono::system_clock::now(); + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) < + std::chrono::milliseconds(20)) { + return raw_amp_low + raw_amp_high == 0; + } + + last_vibration = now; + last_state_rumble_low = raw_amp_low; + last_state_rumble_high = raw_amp_high; + if (sdl_joystick) { + SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time); + } + return false; + } + + std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { + float x = GetAxis(axis_x, range); + float y = GetAxis(axis_y, range); y = -y; // 3DS uses an y-axis inverse from SDL // Make sure the coordinates are in the unit circle, @@ -88,6 +124,10 @@ public: return std::make_tuple(x, y); } + const InputCommon::MotionInput& GetMotion() const { + return motion; + } + void SetHat(int hat, Uint8 direction) { std::lock_guard lock{mutex}; state.hats.insert_or_assign(hat, direction); @@ -115,10 +155,15 @@ public: return sdl_joystick.get(); } - void SetSDLJoystick(SDL_Joystick* joystick) { + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_controller.reset(controller); sdl_joystick.reset(joystick); } + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + private: struct State { std::unordered_map<int, bool> buttons; @@ -127,8 +172,15 @@ private: } state; std::string guid; int port; + u16 last_state_rumble_high; + u16 last_state_rumble_low; + std::chrono::time_point<std::chrono::system_clock> last_vibration; 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 initalized without PID values as motion input is not aviable for SDL2 + InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; }; std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { @@ -136,18 +188,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g 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); + 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]; } - auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr); + 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}; @@ -171,32 +224,36 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_ }); if (nullptr_it != map_it->second.end()) { // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick); + (*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); + 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); + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller); return joystick_map[guid].emplace_back(std::move(joystick)); } 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}; if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -205,11 +262,11 @@ void SDLState::InitJoystick(int joystick_index) { 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); + (*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); + auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -231,7 +288,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { // Destruct SDL_Joystick outside the lock guard because SDL can internally call the // event callback which locks the mutex again. - joystick->SetSDLJoystick(nullptr); + joystick->SetSDLJoystick(nullptr, nullptr); } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -285,6 +342,12 @@ public: return joystick->GetButton(button); } + bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { + const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f)); + const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f)); + return joystick->RumblePlay(new_amp_low, new_amp_high, 250); + } + private: std::shared_ptr<SDLJoystick> joystick; int button; @@ -313,7 +376,7 @@ public: trigger_if_greater(trigger_if_greater_) {} bool GetStatus() const override { - const float axis_value = joystick->GetAxis(axis); + const float axis_value = joystick->GetAxis(axis, 1.0f); if (trigger_if_greater) { return axis_value > threshold; } @@ -329,22 +392,24 @@ private: class SDLAnalog final : public Input::AnalogDevice { public: - SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} + 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_) {} std::tuple<float, float> GetStatus() const override { - const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); + const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); const float r = std::sqrt((x * x) + (y * y)); if (r > deadzone) { return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), y / r * (r - deadzone) / (1 - deadzone)); } - return std::make_tuple<float, float>(0.0f, 0.0f); + return {}; } bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.4f; + const float directional_deadzone = 0.5f; switch (direction) { case Input::AnalogDirection::RIGHT: return x > directional_deadzone; @@ -363,6 +428,69 @@ private: const int axis_x; const int axis_y; const float deadzone; + const float range; +}; + +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 @@ -457,14 +585,78 @@ public: const int port = params.Get("port", 0); const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); - + 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); 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); + return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range); + } + +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: @@ -473,8 +665,12 @@ private: SDLState::SDLState() { using namespace Input; - RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); - RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); + analog_factory = std::make_shared<SDLAnalogFactory>(*this); + button_factory = std::make_shared<SDLButtonFactory>(*this); + motion_factory = std::make_shared<SDLMotionFactory>(*this); + RegisterFactory<AnalogDevice>("sdl", analog_factory); + RegisterFactory<ButtonDevice>("sdl", button_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); @@ -482,6 +678,7 @@ SDLState::SDLState() { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); return; } + has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); 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()); } @@ -494,7 +691,7 @@ SDLState::SDLState() { using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); - std::this_thread::sleep_for(10ms); + std::this_thread::sleep_for(5ms); } }); } @@ -509,6 +706,7 @@ SDLState::~SDLState() { using namespace Input; UnregisterFactory<ButtonDevice>("sdl"); UnregisterFactory<AnalogDevice>("sdl"); + UnregisterFactory<MotionDevice>("sdl"); CloseJoysticks(); SDL_DelEventWatch(&SDLEventWatcher, this); @@ -520,65 +718,251 @@ SDLState::~SDLState() { } } -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { +std::vector<Common::ParamPackage> SDLState::GetInputDevices() { + std::scoped_lock lock(joystick_map_mutex); + 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()) { + std::string name = + fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"class", "sdl"}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } else if (joy) { + std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"class", "sdl"}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + } + return devices; +} + +namespace { +Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis, + float value = 0.1f) { Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("axis", axis); + if (value > 0) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.5"); + } + return params; +} + +Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) { + Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("port", port); + 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 params({{"engine", "sdl"}}); + + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("hat", hat); + switch (value) { + case SDL_HAT_UP: + params.Set("direction", "up"); + break; + case SDL_HAT_DOWN: + params.Set("direction", "down"); + break; + case SDL_HAT_LEFT: + params.Set("direction", "left"); + break; + case SDL_HAT_RIGHT: + params.Set("direction", "right"); + break; + default: + return {}; + } + return params; +} + +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { switch (event.type) { case SDL_JOYAXISMOTION: { const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis", event.jaxis.axis); - if (event.jaxis.value > 0) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.5"); - } - break; + return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jaxis.axis, event.jaxis.value); } case SDL_JOYBUTTONUP: { const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("button", event.jbutton.button); - break; + return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jbutton.button); } case SDL_JOYHATMOTION: { const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("hat", event.jhat.hat); - switch (event.jhat.value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - break; + return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jhat.hat, event.jhat.value); + } + } + return {}; +} + +Common::ParamPackage SDLEventToMotionParamPackage(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); } + case SDL_JOYBUTTONUP: { + const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); + return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jbutton.button); } + case SDL_JOYHATMOTION: { + const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); + return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jhat.hat, event.jhat.value); + } + } + return {}; +} + +Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, + const SDL_GameControllerButtonBind& binding) { + switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); + case SDL_CONTROLLER_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.value.button); + case SDL_CONTROLLER_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, + binding.value.hat.hat_mask); + } + return {}; +} + +Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, + int axis_y) { + Common::ParamPackage params; + params.Set("engine", "sdl"); + params.Set("port", port); + params.Set("guid", guid); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); return params; } +} // Anonymous namespace -namespace Polling { +ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { + 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 == nullptr) { + return {}; + } + + // 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.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + 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 == nullptr) { + return {}; + } + + AnalogMapping mapping = {}; + const auto& binding_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + 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.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + binding_right_x.value.axis, + binding_right_y.value.axis)); + return mapping; +} +namespace Polling { class SDLPoller : public InputCommon::Polling::DevicePoller { public: explicit SDLPoller(SDLState& state_) : state(state_) {} - void Start() override { + void Start(const std::string& device_id) override { state.event_queue.Clear(); state.polling = true; } @@ -598,71 +982,135 @@ public: Common::ParamPackage GetNextInput() override { SDL_Event event; while (state.event_queue.Pop(event)) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(state, 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 {SDLEventToButtonParamPackage(state, event)}; + } + return std::nullopt; + } }; -class SDLAnalogPoller final : public SDLPoller { +class SDLMotionPoller final : public SDLPoller { public: - explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} + explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {} - void Start() override { - SDLPoller::Start(); + 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; + } +}; + +/** + * Attempts to match the press to a controller joy axis (left/right stick) and if a match + * isn't found, checks if the event matches anything from SDLButtonPoller and uses that + * instead + */ +class SDLAnalogPreferredPoller final : public SDLPoller { +public: + explicit SDLAnalogPreferredPoller(SDLState& state_) + : SDLPoller(state_), button_poller(state_) {} + 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; - analog_axes_joystick = -1; } Common::ParamPackage GetNextInput() override { SDL_Event event; while (state.event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { + // Filter out axis events that are below a threshold + if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second SDL event. The axes also must be from the same joystick. - const int axis = event.jaxis.axis; - if (analog_x_axis == -1) { - analog_x_axis = axis; - analog_axes_joystick = event.jaxis.which; - } else if (analog_y_axis == -1 && analog_x_axis != axis && - analog_axes_joystick == event.jaxis.which) { - analog_y_axis = axis; + // 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; + } + } + } + + // 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; } } - Common::ParamPackage params; + if (analog_x_axis != -1 && analog_y_axis != -1) { const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("engine", "sdl"); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis_x", analog_x_axis); - params.Set("axis_y", analog_y_axis); + auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + analog_x_axis, analog_y_axis); analog_x_axis = -1; analog_y_axis = -1; - analog_axes_joystick = -1; return params; } - return params; + return {}; } private: int analog_x_axis = -1; int analog_y_axis = -1; - SDL_JoystickID analog_axes_joystick = -1; + SDLButtonPoller button_poller; }; } // namespace Polling @@ -670,12 +1118,15 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { Pollers pollers; switch (type) { - case InputCommon::Polling::DeviceType::Analog: - pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); + case InputCommon::Polling::DeviceType::AnalogPreferred: + pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this)); break; 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 606a32c5b..b9bb4dc56 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -21,6 +21,7 @@ namespace InputCommon::SDL { class SDLAnalogFactory; class SDLButtonFactory; +class SDLMotionFactory; class SDLJoystick; class SDLState : public State { @@ -50,6 +51,11 @@ public: std::atomic<bool> polling = false; Common::SPSCQueue<SDL_Event> event_queue; + std::vector<Common::ParamPackage> GetInputDevices() override; + + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + private: void InitJoystick(int joystick_index); void CloseJoystick(SDL_Joystick* sdl_joystick); @@ -57,12 +63,16 @@ private: /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + // Set to true if SDL supports game controller subsystem + bool has_gamecontroller = false; + /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::mutex joystick_map_mutex; std::shared_ptr<SDLButtonFactory> button_factory; std::shared_ptr<SDLAnalogFactory> analog_factory; + 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 new file mode 100644 index 000000000..b66c05856 --- /dev/null +++ b/src/input_common/settings.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/settings.h" + +namespace Settings { +namespace NativeButton { +const std::array<const char*, NumButtons> mapping = {{ + "button_a", "button_b", "button_x", "button_y", "button_lstick", + "button_rstick", "button_l", "button_r", "button_zl", "button_zr", + "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright", + "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot", +}}; +} + +namespace NativeMotion { +const std::array<const char*, NumMotions> mapping = {{ + "motionleft", + "motionright", +}}; +} + +namespace NativeAnalog { +const std::array<const char*, NumAnalogs> mapping = {{ + "lstick", + "rstick", +}}; +} + +namespace NativeMouseButton { +const std::array<const char*, NumMouseButtons> mapping = {{ + "left", + "right", + "middle", + "forward", + "back", +}}; +} +} // namespace Settings diff --git a/src/input_common/settings.h b/src/input_common/settings.h new file mode 100644 index 000000000..ab0b95cf1 --- /dev/null +++ b/src/input_common/settings.h @@ -0,0 +1,352 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include "common/common_types.h" + +namespace Settings { +namespace NativeButton { +enum Values : int { + A, + B, + X, + Y, + LStick, + RStick, + L, + R, + ZL, + ZR, + Plus, + Minus, + + DLeft, + DUp, + DRight, + DDown, + + SL, + SR, + + Home, + Screenshot, + + NumButtons, +}; + +constexpr int BUTTON_HID_BEGIN = A; +constexpr int BUTTON_NS_BEGIN = Home; + +constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN; +constexpr int BUTTON_NS_END = NumButtons; + +constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; +constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; + +extern const std::array<const char*, NumButtons> mapping; + +} // namespace NativeButton + +namespace NativeAnalog { +enum Values : int { + LStick, + RStick, + + NumAnalogs, +}; + +constexpr int STICK_HID_BEGIN = LStick; +constexpr int STICK_HID_END = NumAnalogs; +constexpr int NUM_STICKS_HID = NumAnalogs; + +extern const std::array<const char*, NumAnalogs> mapping; +} // namespace NativeAnalog + +namespace NativeMotion { +enum Values : int { + MOTIONLEFT, + MOTIONRIGHT, + + NumMotions, +}; + +constexpr int MOTION_HID_BEGIN = MOTIONLEFT; +constexpr int MOTION_HID_END = NumMotions; +constexpr int NUM_MOTION_HID = NumMotions; + +extern const std::array<const char*, NumMotions> mapping; +} // namespace NativeMotion + +namespace NativeMouseButton { +enum Values { + Left, + Right, + Middle, + Forward, + Back, + + NumMouseButtons, +}; + +constexpr int MOUSE_HID_BEGIN = Left; +constexpr int MOUSE_HID_END = NumMouseButtons; +constexpr int NUM_MOUSE_HID = NumMouseButtons; + +extern const std::array<const char*, NumMouseButtons> mapping; +} // namespace NativeMouseButton + +namespace NativeKeyboard { +enum Keys { + None, + Error, + + A = 4, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + N0, + Enter, + Escape, + Backspace, + Tab, + Space, + Minus, + Equal, + LeftBrace, + RightBrace, + Backslash, + Tilde, + Semicolon, + Apostrophe, + Grave, + Comma, + Dot, + Slash, + CapsLockKey, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + SystemRequest, + ScrollLockKey, + Pause, + Insert, + Home, + PageUp, + Delete, + End, + PageDown, + Right, + Left, + Down, + Up, + + NumLockKey, + KPSlash, + KPAsterisk, + KPMinus, + KPPlus, + KPEnter, + KP1, + KP2, + KP3, + KP4, + KP5, + KP6, + KP7, + KP8, + KP9, + KP0, + KPDot, + + Key102, + Compose, + Power, + KPEqual, + + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + Open, + Help, + Properties, + Front, + Stop, + Repeat, + Undo, + Cut, + Copy, + Paste, + Find, + Mute, + VolumeUp, + VolumeDown, + CapsLockActive, + NumLockActive, + ScrollLockActive, + KPComma, + + KPLeftParenthesis, + KPRightParenthesis, + + LeftControlKey = 0xE0, + LeftShiftKey, + LeftAltKey, + LeftMetaKey, + RightControlKey, + RightShiftKey, + RightAltKey, + RightMetaKey, + + MediaPlayPause, + MediaStopCD, + MediaPrevious, + MediaNext, + MediaEject, + MediaVolumeUp, + MediaVolumeDown, + MediaMute, + MediaWebsite, + MediaBack, + MediaForward, + MediaStop, + MediaFind, + MediaScrollUp, + MediaScrollDown, + MediaEdit, + MediaSleep, + MediaCoffee, + MediaRefresh, + MediaCalculator, + + NumKeyboardKeys, +}; + +static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); + +enum Modifiers { + LeftControl, + LeftShift, + LeftAlt, + LeftMeta, + RightControl, + RightShift, + RightAlt, + RightMeta, + CapsLock, + ScrollLock, + NumLock, + + NumKeyboardMods, +}; + +constexpr int KEYBOARD_KEYS_HID_BEGIN = None; +constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; +constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; + +constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; +constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; +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 MotionRaw = std::array<std::string, NativeMotion::NumMotions>; +using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; +using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; +using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; + +enum class ControllerType { + ProController, + DualJoyconDetached, + LeftJoycon, + RightJoycon, + Handheld, +}; + +struct PlayerInput { + bool connected; + ControllerType controller_type; + ButtonsRaw buttons; + AnalogsRaw analogs; + MotionRaw motions; + std::string lstick_mod; + std::string rstick_mod; + + u32 body_color_left; + u32 body_color_right; + u32 button_color_left; + u32 button_color_right; +}; + +struct TouchscreenInput { + bool enabled; + std::string device; + + u32 finger; + u32 diameter_x; + u32 diameter_y; + u32 rotation_angle; +}; +} // namespace Settings diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp new file mode 100644 index 000000000..98da0ef1a --- /dev/null +++ b/src/input_common/touch_from_button.cpp @@ -0,0 +1,50 @@ +// 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() { + for (const auto& config_entry : + Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index] + .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))); + } + } + + std::tuple<float, float, bool> GetStatus() const override { + for (const auto& m : map) { + const bool state = std::get<0>(m)->GetStatus(); + if (state) { + const float x = static_cast<float>(std::get<1>(m)) / + static_cast<int>(Layout::ScreenUndocked::Width); + const float y = static_cast<float>(std::get<2>(m)) / + static_cast<int>(Layout::ScreenUndocked::Height); + return {x, y, true}; + } + } + return {}; + } + +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& params) { + 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 e63c73c4f..9d0b9f31d 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -2,15 +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 <boost/bind.hpp> #include "common/logging/log.h" +#include "core/settings.h" #include "input_common/udp/client.h" #include "input_common/udp/protocol.h" @@ -132,21 +130,59 @@ 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"); + for (std::size_t client = 0; client < clients.size(); client++) { + u8 pad = client % 4; + StartCommunication(client, Settings::values.udp_input_address, + Settings::values.udp_input_port, pad, 24872); + // 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); + } } Client::~Client() { - socket->Stop(); - thread.join(); + Reset(); +} + +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; } +bool Client::DeviceConnected(std::size_t pad) const { + // Use last timestamp to detect if the socket has stopped sending data + const auto now = std::chrono::system_clock::now(); + u64 time_difference = + std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update) + .count(); + return time_difference < 1000 && clients[pad].active == 1; +} + +void Client::ReloadUDPClient() { + for (std::size_t client = 0; client < clients.size(); client++) { + ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client); + } +} 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 number must be determined from host / port and pad index + std::size_t client = pad_index; + clients[client].socket->Stop(); + clients[client].thread.join(); + StartCommunication(client, host, port, pad_index, client_id); } void Client::OnVersion(Response::Version data) { @@ -158,23 +194,35 @@ void Client::OnPortInfo(Response::PortInfo data) { } void Client::OnPadData(Response::PadData data) { + // client number must be determined from host / port and pad index + std::size_t client = data.info.id; 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); - { - std::lock_guard guard(status->update_mutex); + clients[client].active = data.info.is_pad_active; + clients[client].packet_sequence = data.packet_counter; + const auto now = std::chrono::system_clock::now(); + u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>( + now - clients[client].last_motion_update) + .count(); + clients[client].last_motion_update = now; + 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); - status->motion_status = {accel, gyro}; + { + std::lock_guard guard(clients[client].status.update_mutex); + clients[client].status.motion_status = clients[client].motion.GetMotion(); // 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. @@ -183,11 +231,11 @@ void Client::OnPadData(Response::PadData data) { float x = 0; float y = 0; - 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; + if (is_active && clients[client].status.touch_calibration) { + const u16 min_x = clients[client].status.touch_calibration->min_x; + const u16 max_x = clients[client].status.touch_calibration->max_x; + const u16 min_y = clients[client].status.touch_calibration->min_y; + const u16 max_y = clients[client].status.touch_calibration->max_y; x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / static_cast<float>(max_x - min_x); @@ -195,17 +243,86 @@ void Client::OnPadData(Response::PadData data) { static_cast<float>(max_y - min_y); } - status->touch_status = {x, y, is_active}; + clients[client].status.touch_status = {x, y, is_active}; + + if (configuring) { + const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope(); + const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration(); + UpdateYuzuSettings(client, accelerometer, gyroscope, is_active); + } } } -void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { +void Client::StartCommunication(std::size_t client, 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()}; + clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; +} + +void Client::Reset() { + for (std::size_t client = 0; client < clients.size(); client++) { + clients[client].socket->Stop(); + clients[client].thread.join(); + } +} + +void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro, bool touch) { + if (gyro.Length() > 0.2f) { + LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {}), touch={}", + client, gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2], touch); + } + UDPPadStatus pad; + if (touch) { + pad.touch = PadTouch::Click; + pad_queue[client].Push(pad); + } + for (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[client].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[client].Push(pad); + } + } +} + +void Client::BeginConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Client::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +DeviceStatus& Client::GetPadState(std::size_t pad) { + return clients[pad].status; +} + +const DeviceStatus& Client::GetPadState(std::size_t pad) const { + return clients[pad].status; +} + +std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() { + return pad_queue; +} + +const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const { + return pad_queue; } void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, @@ -225,8 +342,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie } else { failure_callback(); } - }) - .detach(); + }).detach(); } CalibrationConfigurationJob::CalibrationConfigurationJob( @@ -280,8 +396,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( complete_event.Wait(); socket.Stop(); worker_thread.join(); - }) - .detach(); + }).detach(); } CalibrationConfigurationJob::~CalibrationConfigurationJob() { diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index b8c654755..523dc6a7a 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -12,8 +12,12 @@ #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 { @@ -28,9 +32,30 @@ struct PortInfo; struct Version; } // namespace Response +enum class PadMotion { + GyroX, + GyroY, + GyroZ, + AccX, + AccY, + AccZ, + Undefined, +}; + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + PadTouch touch{PadTouch::Undefined}; + 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 +70,58 @@ 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(); + + // Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::vector<Common::ParamPackage> GetInputDevices() const; + + bool DeviceConnected(std::size_t pad) const; + void ReloadUDPClient(); void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, u32 client_id = 24872); + std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue(); + const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const; + + DeviceStatus& GetPadState(std::size_t pad); + const DeviceStatus& GetPadState(std::size_t pad) const; + private: + struct ClientData { + std::unique_ptr<Socket> socket; + DeviceStatus status; + std::thread thread; + u64 packet_sequence = 0; + u8 active; + + // 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::system_clock> last_motion_update; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + 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 StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index, + u32 client_id); + void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro, bool touch); + + bool configuring = false; - std::unique_ptr<Socket> socket; - std::shared_ptr<DeviceStatus> status; - std::thread thread; - u64 packet_sequence = 0; + std::array<ClientData, 4> clients; + std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue; }; /// An async job allowing configuration of the touchpad calibration. diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp index 8c6ef1394..eba077a36 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -1,99 +1,144 @@ -// 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 <atomic> +#include <list> #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; + UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) + : ip(ip_), port(port_), pad(pad_), client(client_) {} + + Input::MotionStatus GetStatus() const override { + return client->GetPadState(pad).motion_status; } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + const int port; + const int 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 JC Adapter +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 nth jcpad on the adapter + */ +std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); + + return std::make_unique<UDPMotion>(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(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast<int>(pad_number)); + params.Set("motion", static_cast<u16>(pad.motion)); + return params; } - return std::make_unique<UDPTouchDevice>(status); } + 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_)) {} + UDPTouch(std::string ip_, int port_, int 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); + std::tuple<float, float, bool> GetStatus() const override { + return client->GetPadState(pad).touch_status; } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + const int port; + const int 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); - - Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", - std::make_shared<UDPTouchFactory>(status)); - Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", - std::make_shared<UDPMotionFactory>(status)); +/// A motion device factory that creates motion devices from JC Adapter +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 nth jcpad on the adapter + */ +std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); + + return std::make_unique<UDPTouch>(ip, port, pad, client.get()); } -State::~State() { - Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); - Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); +void UDPTouchFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); } -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, - Settings::values.udp_pad_index); +void UDPTouchFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); } -std::unique_ptr<State> Init() { - return std::make_unique<State>(); +Common::ParamPackage UDPTouchFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.touch == CemuhookUDP::PadTouch::Undefined) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast<int>(pad_number)); + params.Set("touch", static_cast<u16>(pad.touch)); + return params; + } + } + return params; } -} // namespace InputCommon::CemuhookUDP + +} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 4f83f0441..ea3fd4175 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -1,25 +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 "core/frontend/input.h" +#include "input_common/udp/client.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class Client; - -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(); + 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<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/core/core_timing.cpp b/src/tests/core/core_timing.cpp index e66db1940..b35459152 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -6,6 +6,7 @@ #include <array> #include <bitset> +#include <chrono> #include <cstdlib> #include <memory> #include <string> @@ -17,7 +18,6 @@ namespace { // Numbers are chosen randomly to make sure the correct one is given. constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; -constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}}; std::array<s64, 5> delays{}; @@ -25,12 +25,12 @@ std::bitset<CB_IDS.size()> callbacks_ran_flags; u64 expected_callback = 0; template <unsigned int IDX> -void HostCallbackTemplate(u64 userdata, s64 nanoseconds_late) { +void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { static_assert(IDX < CB_IDS.size(), "IDX out of range"); callbacks_ran_flags.set(IDX); - REQUIRE(CB_IDS[IDX] == userdata); + REQUIRE(CB_IDS[IDX] == user_data); REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]); - delays[IDX] = nanoseconds_late; + delays[IDX] = ns_late.count(); ++expected_callback; } @@ -46,20 +46,16 @@ struct ScopeInit final { Core::Timing::CoreTiming core_timing; }; -#pragma optimize("", off) - u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) { - u64 start = core_timing.GetGlobalTimeNs().count(); - u64 placebo = 0; + const u64 start = core_timing.GetGlobalTimeNs().count(); + volatile u64 placebo = 0; for (std::size_t i = 0; i < 1000; i++) { - placebo += core_timing.GetGlobalTimeNs().count(); + placebo = placebo + core_timing.GetGlobalTimeNs().count(); } - u64 end = core_timing.GetGlobalTimeNs().count(); - return (end - start); + const u64 end = core_timing.GetGlobalTimeNs().count(); + return end - start; } -#pragma optimize("", on) - } // Anonymous namespace TEST_CASE("CoreTiming[BasicOrder]", "[core]") { @@ -77,10 +73,12 @@ TEST_CASE("CoreTiming[BasicOrder]", "[core]") { core_timing.SyncPause(true); - u64 one_micro = 1000U; + const u64 one_micro = 1000U; for (std::size_t i = 0; i < events.size(); i++) { - u64 order = calls_order[i]; - core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); + const u64 order = calls_order[i]; + const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)}; + + core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]); } /// test pause REQUIRE(callbacks_ran_flags.none()); @@ -116,13 +114,16 @@ TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") { expected_callback = 0; - u64 start = core_timing.GetGlobalTimeNs().count(); - u64 one_micro = 1000U; + const u64 start = core_timing.GetGlobalTimeNs().count(); + const u64 one_micro = 1000U; + for (std::size_t i = 0; i < events.size(); i++) { - u64 order = calls_order[i]; - core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); + const u64 order = calls_order[i]; + const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)}; + core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]); } - u64 end = core_timing.GetGlobalTimeNs().count(); + + const u64 end = core_timing.GetGlobalTimeNs().count(); const double scheduling_time = static_cast<double>(end - start); const double timer_time = static_cast<double>(TestTimerSpeed(core_timing)); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 21c46a567..3df54816d 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(host_shaders) + add_library(video_core STATIC buffer_cache/buffer_block.h buffer_cache/buffer_cache.h @@ -98,6 +100,8 @@ add_library(video_core STATIC sampler_cache.cpp sampler_cache.h shader_cache.h + shader_notify.cpp + shader_notify.h shader/decode/arithmetic.cpp shader/decode/arithmetic_immediate.cpp shader/decode/bfe.cpp @@ -128,6 +132,8 @@ add_library(video_core STATIC shader/decode/other.cpp shader/ast.cpp shader/ast.h + shader/async_shaders.cpp + shader/async_shaders.h shader/compiler_settings.cpp shader/compiler_settings.h shader/control_flow.cpp @@ -184,6 +190,8 @@ if (ENABLE_VULKAN) 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 @@ -198,6 +206,8 @@ if (ENABLE_VULKAN) renderer_vulkan/vk_graphics_pipeline.h renderer_vulkan/vk_image.cpp renderer_vulkan/vk_image.h + renderer_vulkan/vk_master_semaphore.cpp + renderer_vulkan/vk_master_semaphore.h renderer_vulkan/vk_memory_manager.cpp renderer_vulkan/vk_memory_manager.h renderer_vulkan/vk_pipeline_cache.cpp @@ -208,8 +218,8 @@ if (ENABLE_VULKAN) 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_resource_pool.cpp + renderer_vulkan/vk_resource_pool.h renderer_vulkan/vk_sampler_cache.cpp renderer_vulkan/vk_sampler_cache.h renderer_vulkan/vk_scheduler.cpp @@ -240,6 +250,9 @@ create_target_directory_groups(video_core) target_link_libraries(video_core PUBLIC common core) target_link_libraries(video_core PRIVATE glad xbyak) +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) @@ -260,5 +273,12 @@ endif() if (MSVC) target_compile_options(video_core PRIVATE /we4267) 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=switch + -Werror=unused-variable + -Werror=unused-but-set-variable + -Werror=class-memaccess + ) endif() diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index dd7ce8c99..e7edd733f 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -51,46 +51,43 @@ public: bool is_written = false, bool use_fast_cbuf = false) { std::lock_guard lock{mutex}; - auto& memory_manager = system.GPU().MemoryManager(); - const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr); - if (!cpu_addr_opt) { + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); + if (!cpu_addr) { 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 (!is_written && !IsRegionWritten(*cpu_addr, *cpu_addr + size - 1)) { + const bool is_granular = gpu_memory.IsGranularRange(gpu_addr, size); if (use_fast_cbuf) { u8* dest; if (is_granular) { - dest = memory_manager.GetPointer(gpu_addr); + dest = gpu_memory.GetPointer(gpu_addr); } else { staging_buffer.resize(size); dest = staging_buffer.data(); - memory_manager.ReadBlockUnsafe(gpu_addr, dest, size); + gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size); } return ConstBufferUpload(dest, size); } if (is_granular) { - u8* const host_ptr = memory_manager.GetPointer(gpu_addr); + u8* const host_ptr = gpu_memory.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); - }); + return StreamBufferUpload(size, alignment, [this, gpu_addr, size](u8* dest) { + gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size); + }); } } } - Buffer* const block = GetBlock(cpu_addr, size); - MapInterval* const map = MapAddress(block, gpu_addr, cpu_addr, size); + Buffer* const block = GetBlock(*cpu_addr, size); + MapInterval* const map = MapAddress(block, gpu_addr, *cpu_addr, size); if (!map) { return GetEmptyBuffer(size); } @@ -106,7 +103,7 @@ public: } } - return BufferInfo{block->Handle(), block->Offset(cpu_addr), block->Address()}; + return BufferInfo{block->Handle(), block->Offset(*cpu_addr), block->Address()}; } /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset. @@ -262,9 +259,11 @@ public: virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0; 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)} {} + explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_, + std::unique_ptr<StreamBuffer> stream_buffer_) + : rasterizer{rasterizer_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, + stream_buffer{std::move(stream_buffer_)}, stream_buffer_handle{stream_buffer->Handle()} {} ~BufferCache() = default; @@ -326,14 +325,13 @@ 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); + if (gpu_memory.IsGranularRange(gpu_addr, size)) { + u8* const host_ptr = gpu_memory.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); + gpu_memory.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size); block->Upload(block->Offset(cpu_addr), size, staging_buffer.data()); } return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr)); @@ -392,7 +390,7 @@ private: continue; } staging_buffer.resize(size); - system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size); + cpu_memory.ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size); block->Upload(block->Offset(interval.lower()), size, staging_buffer.data()); } } @@ -431,7 +429,7 @@ private: 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); + cpu_memory.WriteBlockUnsafe(map->start, staging_buffer.data(), size); map->MarkAsModified(false, 0); } @@ -524,11 +522,8 @@ private: 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) { - auto it = written_pages.find(page_start); - if (it != written_pages.end()) { - it->second = it->second + 1; - } else { - written_pages.insert_or_assign(page_start, 1); + if (const auto [it, inserted] = written_pages.emplace(page_start, 1); !inserted) { + ++it->second; } } } @@ -539,7 +534,7 @@ private: auto it = written_pages.find(page_start); if (it != written_pages.end()) { if (it->second > 1) { - it->second = it->second - 1; + --it->second; } else { written_pages.erase(it); } @@ -570,7 +565,8 @@ private: } VideoCore::RasterizerInterface& rasterizer; - Core::System& system; + Tegra::MemoryManager& gpu_memory; + Core::Memory::Memory& cpu_memory; std::unique_ptr<StreamBuffer> stream_buffer; BufferType stream_buffer_handle; diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp index 6c426b035..b06c32c84 100644 --- a/src/video_core/compatible_formats.cpp +++ b/src/video_core/compatible_formats.cpp @@ -17,101 +17,94 @@ namespace { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt constexpr std::array VIEW_CLASS_128_BITS = { - PixelFormat::RGBA32F, - PixelFormat::RGBA32UI, + PixelFormat::R32G32B32A32_FLOAT, + PixelFormat::R32G32B32A32_UINT, + PixelFormat::R32G32B32A32_SINT, }; -// Missing formats: -// PixelFormat::RGBA32I constexpr std::array VIEW_CLASS_96_BITS = { - PixelFormat::RGB32F, + PixelFormat::R32G32B32_FLOAT, }; // Missing formats: // PixelFormat::RGB32UI, // PixelFormat::RGB32I, constexpr std::array VIEW_CLASS_64_BITS = { - PixelFormat::RGBA16F, PixelFormat::RG32F, PixelFormat::RGBA16UI, PixelFormat::RG32UI, - PixelFormat::RGBA16U, PixelFormat::RGBA16F, PixelFormat::RGBA16S, + PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_UINT, + PixelFormat::R32G32_SINT, PixelFormat::R16G16B16A16_FLOAT, + PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM, + PixelFormat::R16G16B16A16_UINT, PixelFormat::R16G16B16A16_SINT, }; -// Missing formats: -// PixelFormat::RGBA16I -// PixelFormat::RG32I // TODO: How should we handle 48 bits? constexpr std::array VIEW_CLASS_32_BITS = { - PixelFormat::RG16F, PixelFormat::R11FG11FB10F, PixelFormat::R32F, - PixelFormat::A2B10G10R10U, PixelFormat::RG16UI, PixelFormat::R32UI, - PixelFormat::RG16I, PixelFormat::R32I, PixelFormat::ABGR8U, - PixelFormat::RG16, PixelFormat::ABGR8S, PixelFormat::RG16S, - PixelFormat::RGBA8_SRGB, PixelFormat::E5B9G9R9F, PixelFormat::BGRA8, - PixelFormat::BGRA8_SRGB, + 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, + PixelFormat::R16G16_UNORM, PixelFormat::A8B8G8R8_SNORM, PixelFormat::R16G16_SNORM, + PixelFormat::A8B8G8R8_SRGB, PixelFormat::E5B9G9R9_FLOAT, PixelFormat::B8G8R8A8_UNORM, + PixelFormat::B8G8R8A8_SRGB, PixelFormat::A8B8G8R8_UINT, PixelFormat::A8B8G8R8_SINT, + PixelFormat::A2B10G10R10_UINT, }; -// Missing formats: -// PixelFormat::RGBA8UI -// PixelFormat::RGBA8I -// PixelFormat::RGB10_A2_UI // TODO: How should we handle 24 bits? constexpr std::array VIEW_CLASS_16_BITS = { - PixelFormat::R16F, PixelFormat::RG8UI, PixelFormat::R16UI, PixelFormat::R16I, - PixelFormat::RG8U, PixelFormat::R16U, PixelFormat::RG8S, PixelFormat::R16S, + 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, }; -// Missing formats: -// PixelFormat::RG8I constexpr std::array VIEW_CLASS_8_BITS = { - PixelFormat::R8UI, - PixelFormat::R8U, + PixelFormat::R8_UINT, + PixelFormat::R8_UNORM, + PixelFormat::R8_SINT, + PixelFormat::R8_SNORM, }; -// Missing formats: -// PixelFormat::R8I -// PixelFormat::R8S constexpr std::array VIEW_CLASS_RGTC1_RED = { - PixelFormat::DXN1, + PixelFormat::BC4_UNORM, + PixelFormat::BC4_SNORM, }; -// Missing formats: -// COMPRESSED_SIGNED_RED_RGTC1 constexpr std::array VIEW_CLASS_RGTC2_RG = { - PixelFormat::DXN2UNORM, - PixelFormat::DXN2SNORM, + PixelFormat::BC5_UNORM, + PixelFormat::BC5_SNORM, }; constexpr std::array VIEW_CLASS_BPTC_UNORM = { - PixelFormat::BC7U, - PixelFormat::BC7U_SRGB, + PixelFormat::BC7_UNORM, + PixelFormat::BC7_SRGB, }; constexpr std::array VIEW_CLASS_BPTC_FLOAT = { - PixelFormat::BC6H_SF16, - PixelFormat::BC6H_UF16, + PixelFormat::BC6H_SFLOAT, + PixelFormat::BC6H_UFLOAT, }; // 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 = { - PixelFormat::RGBA32UI, PixelFormat::RGBA32F, PixelFormat::DXT23, - PixelFormat::DXT23_SRGB, PixelFormat::DXT45, PixelFormat::DXT45_SRGB, - PixelFormat::DXN2SNORM, PixelFormat::BC7U, PixelFormat::BC7U_SRGB, - PixelFormat::BC6H_SF16, PixelFormat::BC6H_UF16, + 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, + PixelFormat::BC7_UNORM, PixelFormat::BC7_SRGB, PixelFormat::BC6H_SFLOAT, + PixelFormat::BC6H_UFLOAT, }; // Missing formats: // PixelFormat::RGBA32I // COMPRESSED_RG_RGTC2 constexpr std::array COPY_CLASS_64_BITS = { - PixelFormat::RGBA16F, PixelFormat::RG32F, PixelFormat::RGBA16UI, PixelFormat::RG32UI, - PixelFormat::RGBA16U, PixelFormat::RGBA16S, PixelFormat::DXT1_SRGB, PixelFormat::DXT1, - + PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT, + PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM, + PixelFormat::R16G16B16A16_SINT, PixelFormat::R32G32_UINT, + PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_SINT, + PixelFormat::BC1_RGBA_UNORM, PixelFormat::BC1_RGBA_SRGB, }; // Missing formats: -// PixelFormat::RGBA16I -// PixelFormat::RG32I, // COMPRESSED_RGB_S3TC_DXT1_EXT // COMPRESSED_SRGB_S3TC_DXT1_EXT // COMPRESSED_RGBA_S3TC_DXT1_EXT diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h index d1082566d..51766349b 100644 --- a/src/video_core/compatible_formats.h +++ b/src/video_core/compatible_formats.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <array> #include <bitset> #include <cstddef> diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index ff10ff40d..9409c4075 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -10,7 +10,13 @@ namespace Tegra::Engines { -Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {} +Fermi2D::Fermi2D() = default; + +Fermi2D::~Fermi2D() = default; + +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, @@ -81,13 +87,13 @@ void Fermi2D::HandleSurfaceCopy() { 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; - - if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { + const Config copy_config{ + .operation = regs.operation, + .filter = regs.blit_control.filter, + .src_rect = src_rect, + .dst_rect = dst_rect, + }; + if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { UNIMPLEMENTED(); } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 8f37d053f..0909709ec 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -34,8 +34,11 @@ namespace Tegra::Engines { class Fermi2D final : public EngineInterface { public: - explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer); - ~Fermi2D() = default; + explicit Fermi2D(); + ~Fermi2D(); + + /// Binds a rasterizer to this engine. + 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; @@ -142,14 +145,14 @@ public: } regs{}; struct Config { - Operation operation; - Filter filter; + Operation operation{}; + Filter filter{}; Common::Rectangle<u32> src_rect; Common::Rectangle<u32> dst_rect; }; private: - VideoCore::RasterizerInterface& rasterizer; + VideoCore::RasterizerInterface* rasterizer; /// Performs the copy from the source surface to the destination surface as configured in the /// registers. diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index a82b06a38..898370739 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -16,14 +16,15 @@ namespace Tegra::Engines { -KeplerCompute::KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager) - : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, upload_state{ - memory_manager, - regs.upload} {} +KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_) + : system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} {} KeplerCompute::~KeplerCompute() = default; +void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { + rasterizer = &rasterizer_; +} + void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid KeplerCompute register, increase the size of the Regs structure"); @@ -104,11 +105,11 @@ SamplerDescriptor KeplerCompute::AccessSampler(u32 handle) const { } VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() { - return rasterizer.AccessGuestDriverProfile(); + return rasterizer->AccessGuestDriverProfile(); } const VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() const { - return rasterizer.AccessGuestDriverProfile(); + return rasterizer->AccessGuestDriverProfile(); } void KeplerCompute::ProcessLaunch() { @@ -119,7 +120,7 @@ void KeplerCompute::ProcessLaunch() { const GPUVAddr code_addr = regs.code_loc.Address() + launch_description.program_start; LOG_TRACE(HW_GPU, "Compute invocation launched at address 0x{:016x}", code_addr); - rasterizer.DispatchCompute(code_addr); + rasterizer->DispatchCompute(code_addr); } Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const { diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index b7f668d88..7f2500aab 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -42,10 +42,12 @@ namespace Tegra::Engines { class KeplerCompute final : public ConstBufferEngineInterface, public EngineInterface { public: - explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager); + explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); ~KeplerCompute(); + /// Binds a rasterizer to this engine. + void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + static constexpr std::size_t NumConstBuffers = 8; struct Regs { @@ -230,11 +232,6 @@ public: const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override; private: - Core::System& system; - VideoCore::RasterizerInterface& rasterizer; - MemoryManager& memory_manager; - Upload::State upload_state; - void ProcessLaunch(); /// Retrieves information about a specific TIC entry from the TIC buffer. @@ -242,6 +239,11 @@ private: /// Retrieves information about a specific TSC entry from the TSC buffer. Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; + + Core::System& system; + MemoryManager& memory_manager; + VideoCore::RasterizerInterface* rasterizer = nullptr; + Upload::State upload_state; }; #define ASSERT_REG_POSITION(field_name, position) \ diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index c01436295..57ebc785f 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -22,14 +22,19 @@ using VideoCore::QueryType; /// First register id that is actually a Macro call. constexpr u32 MacroRegistersStart = 0xE00; -Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager) - : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, - macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} { +Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) + : system{system_}, memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, + upload_state{memory_manager, regs.upload} { dirty.flags.flip(); InitializeRegisterDefaults(); } +Maxwell3D::~Maxwell3D() = default; + +void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { + rasterizer = &rasterizer_; +} + void Maxwell3D::InitializeRegisterDefaults() { // Initializes registers to their default values - what games expect them to be at boot. This is // for certain registers that may not be explicitly set by games. @@ -192,7 +197,7 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { switch (method) { case MAXWELL3D_REG_INDEX(wait_for_idle): { - rasterizer.WaitForIdle(); + rasterizer->WaitForIdle(); break; } case MAXWELL3D_REG_INDEX(shadow_ram_control): { @@ -402,7 +407,7 @@ void Maxwell3D::FlushMMEInlineDraw() { const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed; if (ShouldExecute()) { - rasterizer.Draw(is_indexed, true); + rasterizer->Draw(is_indexed, true); } // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if @@ -465,7 +470,7 @@ void Maxwell3D::ProcessQueryGet() { switch (regs.query.query_get.operation) { case Regs::QueryOperation::Release: if (regs.query.query_get.fence == 1) { - rasterizer.SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence); + rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence); } else { StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0); } @@ -533,7 +538,7 @@ void Maxwell3D::ProcessQueryCondition() { void Maxwell3D::ProcessCounterReset() { switch (regs.counter_reset) { case Regs::CounterReset::SampleCnt: - rasterizer.ResetCounter(QueryType::SamplesPassed); + rasterizer->ResetCounter(QueryType::SamplesPassed); break; default: LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", @@ -547,7 +552,7 @@ void Maxwell3D::ProcessSyncPoint() { const u32 increment = regs.sync_info.increment.Value(); [[maybe_unused]] const u32 cache_flush = regs.sync_info.unknown.Value(); if (increment) { - rasterizer.SignalSyncPoint(sync_point); + rasterizer->SignalSyncPoint(sync_point); } } @@ -570,7 +575,7 @@ void Maxwell3D::DrawArrays() { const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; if (ShouldExecute()) { - rasterizer.Draw(is_indexed, false); + rasterizer->Draw(is_indexed, false); } // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if @@ -590,9 +595,9 @@ std::optional<u64> Maxwell3D::GetQueryResult() { return 0; case Regs::QuerySelect::SamplesPassed: // Deferred. - rasterizer.Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed, - system.GPU().GetTicks()); - return {}; + rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed, + system.GPU().GetTicks()); + return std::nullopt; default: LOG_DEBUG(HW_GPU, "Unimplemented query select type {}", static_cast<u32>(regs.query.query_get.select.Value())); @@ -718,7 +723,7 @@ void Maxwell3D::ProcessClearBuffers() { regs.clear_buffers.R == regs.clear_buffers.B && regs.clear_buffers.R == regs.clear_buffers.A); - rasterizer.Clear(); + rasterizer->Clear(); } u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const { @@ -752,11 +757,11 @@ SamplerDescriptor Maxwell3D::AccessSampler(u32 handle) const { } VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() { - return rasterizer.AccessGuestDriverProfile(); + return rasterizer->AccessGuestDriverProfile(); } const VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() const { - return rasterizer.AccessGuestDriverProfile(); + return rasterizer->AccessGuestDriverProfile(); } } // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index ef1618990..bc289c55d 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -51,9 +51,11 @@ namespace Tegra::Engines { class Maxwell3D final : public ConstBufferEngineInterface, public EngineInterface { public: - explicit Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager); - ~Maxwell3D() = default; + explicit Maxwell3D(Core::System& system, MemoryManager& memory_manager); + ~Maxwell3D(); + + /// Binds a rasterizer to this engine. + 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. @@ -647,7 +649,7 @@ public: GetX() + GetWidth(), // right GetY() // bottom }; - }; + } f32 GetX() const { return std::max(0.0f, translate_x - std::fabs(scale_x)); @@ -1418,12 +1420,12 @@ public: return execute_on; } - VideoCore::RasterizerInterface& GetRasterizer() { - return rasterizer; + VideoCore::RasterizerInterface& Rasterizer() { + return *rasterizer; } - const VideoCore::RasterizerInterface& GetRasterizer() const { - return rasterizer; + const VideoCore::RasterizerInterface& Rasterizer() const { + return *rasterizer; } /// Notify a memory write has happened. @@ -1460,11 +1462,10 @@ private: void InitializeRegisterDefaults(); Core::System& system; - - VideoCore::RasterizerInterface& rasterizer; - MemoryManager& memory_manager; + VideoCore::RasterizerInterface* rasterizer = nullptr; + /// Start offsets of each macro in macro_memory std::array<u32, 0x80> macro_positions = {}; diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index a2d3d7823..8fa359d0a 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -94,7 +94,8 @@ void MaxwellDMA::CopyPitchToPitch() { } void MaxwellDMA::CopyBlockLinearToPitch() { - ASSERT(regs.src_params.block_size.depth == 0); + UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0); + UNIMPLEMENTED_IF(regs.src_params.layer != 0); // Optimized path for micro copies. const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count; @@ -113,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); @@ -123,17 +122,12 @@ void MaxwellDMA::CopyBlockLinearToPitch() { write_buffer.resize(dst_size); } - if (Settings::IsGPULevelExtreme()) { - memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); - memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); - } else { - memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size); - memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size); - } + memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); + memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel, - read_buffer.data() + src_layer_size * src_params.layer, write_buffer.data(), - block_height, src_params.origin.x, src_params.origin.y); + block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(), + read_buffer.data()); memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); } @@ -198,7 +192,6 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() { if (read_buffer.size() < src_size) { read_buffer.resize(src_size); } - if (write_buffer.size() < dst_size) { write_buffer.resize(dst_size); } @@ -212,8 +205,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() { } UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width, - bytes_per_pixel, read_buffer.data(), write_buffer.data(), - regs.src_params.block_size.height, pos_x, pos_y); + bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y, + write_buffer.data(), read_buffer.data()); memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); } diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h index 72e2a33d5..ceec05459 100644 --- a/src/video_core/engines/shader_header.h +++ b/src/video_core/engines/shader_header.h @@ -41,30 +41,30 @@ 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 { @@ -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 8b2a6a42c..de6991ef6 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -5,15 +5,10 @@ #pragma once #include <algorithm> -#include <array> -#include <memory> #include <queue> -#include "common/assert.h" #include "common/common_types.h" #include "core/core.h" -#include "core/memory.h" -#include "core/settings.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" @@ -79,8 +74,6 @@ public: } void WaitPendingFences() { - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; while (!fences.empty()) { TFence& current_fence = fences.front(); if (ShouldWait()) { @@ -88,8 +81,8 @@ 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()); } @@ -98,13 +91,13 @@ public: } 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 @@ -118,16 +111,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)) { @@ -135,8 +127,8 @@ 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()); } diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 758bfe148..4bb9256e9 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -20,26 +20,35 @@ #include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/renderer_base.h" +#include "video_core/shader_notify.h" #include "video_core/video_core.h" namespace Tegra { MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192)); -GPU::GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_, bool is_async) - : system{system}, renderer{std::move(renderer_)}, is_async{is_async} { - auto& rasterizer{renderer->Rasterizer()}; - memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer); - dma_pusher = std::make_unique<Tegra::DmaPusher>(system, *this); - maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager); - fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer); - kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager); - maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager); - kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager); -} +GPU::GPU(Core::System& system_, bool is_async_) + : system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)}, + dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)}, + maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)}, + fermi_2d{std::make_unique<Engines::Fermi2D>()}, + kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)}, + maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)}, + kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)}, + shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {} GPU::~GPU() = default; +void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) { + renderer = std::move(renderer_); + + VideoCore::RasterizerInterface& rasterizer = renderer->Rasterizer(); + memory_manager->BindRasterizer(rasterizer); + maxwell_3d->BindRasterizer(rasterizer); + fermi_2d->BindRasterizer(rasterizer); + kepler_compute->BindRasterizer(rasterizer); +} + Engines::Maxwell3D& GPU::Maxwell3D() { return *maxwell_3d; } @@ -79,7 +88,7 @@ void GPU::WaitFence(u32 syncpoint_id, u32 value) { } MICROPROFILE_SCOPE(GPU_wait); std::unique_lock lock{sync_mutex}; - sync_cv.wait(lock, [=]() { return syncpoints[syncpoint_id].load() >= value; }); + sync_cv.wait(lock, [=, this] { return syncpoints[syncpoint_id].load() >= value; }); } void GPU::IncrementSyncPoint(const u32 syncpoint_id) { diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 2c42483bd..2d15d1c6f 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -33,59 +33,68 @@ class System; namespace VideoCore { class RendererBase; +class ShaderNotify; } // namespace VideoCore namespace Tegra { enum class RenderTargetFormat : u32 { NONE = 0x0, - RGBA32_FLOAT = 0xC0, - RGBA32_UINT = 0xC2, - RGBA16_UNORM = 0xC6, - RGBA16_SNORM = 0xC7, - RGBA16_UINT = 0xC9, - RGBA16_FLOAT = 0xCA, - RG32_FLOAT = 0xCB, - RG32_UINT = 0xCD, - RGBX16_FLOAT = 0xCE, - BGRA8_UNORM = 0xCF, - BGRA8_SRGB = 0xD0, - RGB10_A2_UNORM = 0xD1, - RGBA8_UNORM = 0xD5, - RGBA8_SRGB = 0xD6, - RGBA8_SNORM = 0xD7, - RGBA8_UINT = 0xD9, - RG16_UNORM = 0xDA, - RG16_SNORM = 0xDB, - RG16_SINT = 0xDC, - RG16_UINT = 0xDD, - RG16_FLOAT = 0xDE, - R11G11B10_FLOAT = 0xE0, + R32B32G32A32_FLOAT = 0xC0, + R32G32B32A32_SINT = 0xC1, + R32G32B32A32_UINT = 0xC2, + R16G16B16A16_UNORM = 0xC6, + R16G16B16A16_SNORM = 0xC7, + R16G16B16A16_SINT = 0xC8, + R16G16B16A16_UINT = 0xC9, + R16G16B16A16_FLOAT = 0xCA, + R32G32_FLOAT = 0xCB, + R32G32_SINT = 0xCC, + R32G32_UINT = 0xCD, + R16G16B16X16_FLOAT = 0xCE, + B8G8R8A8_UNORM = 0xCF, + B8G8R8A8_SRGB = 0xD0, + A2B10G10R10_UNORM = 0xD1, + A2B10G10R10_UINT = 0xD2, + A8B8G8R8_UNORM = 0xD5, + A8B8G8R8_SRGB = 0xD6, + A8B8G8R8_SNORM = 0xD7, + A8B8G8R8_SINT = 0xD8, + A8B8G8R8_UINT = 0xD9, + R16G16_UNORM = 0xDA, + R16G16_SNORM = 0xDB, + R16G16_SINT = 0xDC, + R16G16_UINT = 0xDD, + R16G16_FLOAT = 0xDE, + B10G11R11_FLOAT = 0xE0, R32_SINT = 0xE3, R32_UINT = 0xE4, R32_FLOAT = 0xE5, - B5G6R5_UNORM = 0xE8, - BGR5A1_UNORM = 0xE9, - RG8_UNORM = 0xEA, - RG8_SNORM = 0xEB, - RG8_UINT = 0xED, + R5G6B5_UNORM = 0xE8, + A1R5G5B5_UNORM = 0xE9, + R8G8_UNORM = 0xEA, + R8G8_SNORM = 0xEB, + R8G8_SINT = 0xEC, + R8G8_UINT = 0xED, R16_UNORM = 0xEE, R16_SNORM = 0xEF, R16_SINT = 0xF0, R16_UINT = 0xF1, R16_FLOAT = 0xF2, R8_UNORM = 0xF3, + R8_SNORM = 0xF4, + R8_SINT = 0xF5, R8_UINT = 0xF6, }; enum class DepthFormat : u32 { - Z32_FLOAT = 0xA, - Z16_UNORM = 0x13, - S8_Z24_UNORM = 0x14, - Z24_X8_UNORM = 0x15, - Z24_S8_UNORM = 0x16, - Z24_C8_UNORM = 0x18, - Z32_S8_X24_FLOAT = 0x19, + D32_FLOAT = 0xA, + D16_UNORM = 0x13, + S8_UINT_Z24_UNORM = 0x14, + D24X8_UNORM = 0x15, + D24S8_UNORM = 0x16, + D24C8_UNORM = 0x18, + D32_FLOAT_S8X24_UINT = 0x19, }; struct CommandListHeader; @@ -96,9 +105,9 @@ class DebugContext; */ struct FramebufferConfig { enum class PixelFormat : u32 { - ABGR8 = 1, - RGB565 = 4, - BGRA8 = 5, + A8B8G8R8_UNORM = 1, + RGB565_UNORM = 4, + B8G8R8A8_UNORM = 5, }; VAddr address; @@ -133,11 +142,6 @@ class MemoryManager; class GPU { public: - explicit GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer, - bool is_async); - - virtual ~GPU(); - struct MethodCall { u32 method{}; u32 argument{}; @@ -153,6 +157,12 @@ public: method_count(method_count) {} }; + explicit GPU(Core::System& system, bool is_async); + virtual ~GPU(); + + /// Binds a renderer to the GPU. + void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer); + /// Calls a GPU method. void CallMethod(const MethodCall& method_call); @@ -207,6 +217,14 @@ public: return *renderer; } + VideoCore::ShaderNotify& ShaderNotify() { + return *shader_notify; + } + + const VideoCore::ShaderNotify& ShaderNotify() const { + return *shader_notify; + } + // Waits for the GPU to finish working virtual void WaitIdle() const = 0; @@ -235,7 +253,7 @@ public: const Tegra::DmaPusher& DmaPusher() const; struct Regs { - static constexpr size_t NUM_REGS = 0x100; + static constexpr size_t NUM_REGS = 0x40; union { struct { @@ -254,7 +272,7 @@ public: u32 semaphore_trigger; INSERT_UNION_PADDING_WORDS(0xC); - // The puser and the puller share the reference counter, the pusher only has read + // The pusher and the puller share the reference counter, the pusher only has read // access u32 reference_count; INSERT_UNION_PADDING_WORDS(0x5); @@ -328,13 +346,12 @@ private: bool ExecuteMethodOnEngine(u32 method); protected: - std::unique_ptr<Tegra::DmaPusher> dma_pusher; Core::System& system; + std::unique_ptr<Tegra::MemoryManager> memory_manager; + std::unique_ptr<Tegra::DmaPusher> dma_pusher; std::unique_ptr<VideoCore::RendererBase> renderer; 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 @@ -347,6 +364,8 @@ private: std::unique_ptr<Engines::MaxwellDMA> maxwell_dma; /// Inline memory engine std::unique_ptr<Engines::KeplerMemory> kepler_memory; + /// Shader build notifier + std::unique_ptr<VideoCore::ShaderNotify> shader_notify; std::array<std::atomic<u32>, Service::Nvidia::MaxSyncPoints> syncpoints{}; diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp index 7b855f63e..70a3d5738 100644 --- a/src/video_core/gpu_asynch.cpp +++ b/src/video_core/gpu_asynch.cpp @@ -10,16 +10,14 @@ namespace VideoCommon { -GPUAsynch::GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_, - std::unique_ptr<Core::Frontend::GraphicsContext>&& context) - : GPU(system, std::move(renderer_), true), gpu_thread{system}, - cpu_context(renderer->GetRenderWindow().CreateSharedContext()), - gpu_context(std::move(context)) {} +GPUAsynch::GPUAsynch(Core::System& system) : GPU{system, true}, gpu_thread{system} {} GPUAsynch::~GPUAsynch() = default; void GPUAsynch::Start() { - gpu_thread.StartThread(*renderer, *gpu_context, *dma_pusher); + gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher); + cpu_context = renderer->GetRenderWindow().CreateSharedContext(); + cpu_context->MakeCurrent(); } void GPUAsynch::ObtainContext() { diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h index 15e9f1d38..f89c855a5 100644 --- a/src/video_core/gpu_asynch.h +++ b/src/video_core/gpu_asynch.h @@ -20,8 +20,7 @@ namespace VideoCommon { /// Implementation of GPU interface that runs the GPU asynchronously class GPUAsynch final : public Tegra::GPU { public: - explicit GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer, - std::unique_ptr<Core::Frontend::GraphicsContext>&& context); + explicit GPUAsynch(Core::System& system); ~GPUAsynch() override; void Start() override; @@ -42,7 +41,6 @@ protected: private: GPUThread::ThreadManager gpu_thread; std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context; - std::unique_ptr<Core::Frontend::GraphicsContext> gpu_context; }; } // namespace VideoCommon diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp index aaeb9811d..1ca47ddef 100644 --- a/src/video_core/gpu_synch.cpp +++ b/src/video_core/gpu_synch.cpp @@ -7,20 +7,18 @@ namespace VideoCommon { -GPUSynch::GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer, - std::unique_ptr<Core::Frontend::GraphicsContext>&& context) - : GPU(system, std::move(renderer), false), context{std::move(context)} {} +GPUSynch::GPUSynch(Core::System& system) : GPU{system, false} {} GPUSynch::~GPUSynch() = default; void GPUSynch::Start() {} void GPUSynch::ObtainContext() { - context->MakeCurrent(); + renderer->Context().MakeCurrent(); } void GPUSynch::ReleaseContext() { - context->DoneCurrent(); + renderer->Context().DoneCurrent(); } void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) { diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h index 762c20aa5..297258cb1 100644 --- a/src/video_core/gpu_synch.h +++ b/src/video_core/gpu_synch.h @@ -19,8 +19,7 @@ namespace VideoCommon { /// Implementation of GPU interface that runs the GPU synchronously class GPUSynch final : public Tegra::GPU { public: - explicit GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer, - std::unique_ptr<Core::Frontend::GraphicsContext>&& context); + explicit GPUSynch(Core::System& system); ~GPUSynch() override; void Start() override; @@ -36,9 +35,6 @@ public: protected: void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id, [[maybe_unused]] u32 value) const override {} - -private: - std::unique_ptr<Core::Frontend::GraphicsContext> context; }; } // namespace VideoCommon diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 738c6f0c1..bf761abf2 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -44,9 +44,9 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, dma_pusher.DispatchCalls(); } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr); - } else if (const auto data = std::get_if<OnCommandListEndCommand>(&next.data)) { + } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) { renderer.Rasterizer().ReleaseFences(); - } else if (const auto data = std::get_if<GPUTickCommand>(&next.data)) { + } 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); diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt new file mode 100644 index 000000000..aa62363a7 --- /dev/null +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -0,0 +1,43 @@ +set(SHADER_FILES + opengl_present.frag + opengl_present.vert +) + +set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include) +set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE) + +set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders) +add_custom_command( + OUTPUT + ${SHADER_DIR} + COMMAND + ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} +) + +set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in) +set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake) + +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}) +endforeach() + +add_custom_target(host_shaders + DEPENDS + ${SHADER_HEADERS} + SOURCES + ${SHADER_FILES} +) diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake new file mode 100644 index 000000000..368bce0ed --- /dev/null +++ b/src/video_core/host_shaders/StringShaderHeader.cmake @@ -0,0 +1,11 @@ +set(SOURCE_FILE ${CMAKE_ARGV3}) +set(HEADER_FILE ${CMAKE_ARGV4}) +set(INPUT_FILE ${CMAKE_ARGV5}) + +get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME) +string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME}) +string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME) + +file(READ ${SOURCE_FILE} CONTENTS) + +configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY) diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag new file mode 100644 index 000000000..8a4cb024b --- /dev/null +++ b/src/video_core/host_shaders/opengl_present.frag @@ -0,0 +1,10 @@ +#version 430 core + +layout (location = 0) in vec2 frag_tex_coord; +layout (location = 0) out vec4 color; + +layout (binding = 0) uniform sampler2D color_texture; + +void main() { + color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f); +} diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert new file mode 100644 index 000000000..2235d31a4 --- /dev/null +++ b/src/video_core/host_shaders/opengl_present.vert @@ -0,0 +1,24 @@ +#version 430 core + +out gl_PerVertex { + vec4 gl_Position; +}; + +layout (location = 0) in vec2 vert_position; +layout (location = 1) in vec2 vert_tex_coord; +layout (location = 0) out vec2 frag_tex_coord; + +// This is a truncated 3x3 matrix for 2D transformations: +// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. +// The third column performs translation. +// The third row could be used for projection, which we don't need in 2D. It hence is assumed to +// implicitly be [0, 0, 1] +layout (location = 0) uniform mat3x2 modelview_matrix; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in new file mode 100644 index 000000000..ccdb0d2a9 --- /dev/null +++ b/src/video_core/host_shaders/source_shader.h.in @@ -0,0 +1,9 @@ +#pragma once + +#include <string_view> + +namespace HostShaders { + +constexpr std::string_view @CONTENTS_NAME@ = R"(@CONTENTS@)"; + +} // namespace HostShaders 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 0c9ff59a4..df00b57df 100644 --- a/src/video_core/macro/macro_hle.cpp +++ b/src/video_core/macro/macro_hle.cpp @@ -24,7 +24,7 @@ void HLE_771BB18C62444DA0(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& maxwell3d.regs.index_array.first = parameters[4]; if (maxwell3d.ShouldExecute()) { - maxwell3d.GetRasterizer().Draw(true, true); + maxwell3d.Rasterizer().Draw(true, true); } maxwell3d.regs.index_array.count = 0; maxwell3d.mme_draw.instance_count = 0; @@ -42,7 +42,7 @@ void HLE_0D61FC9FAAC9FCAD(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& maxwell3d.mme_draw.instance_count = count; if (maxwell3d.ShouldExecute()) { - maxwell3d.GetRasterizer().Draw(false, true); + maxwell3d.Rasterizer().Draw(false, true); } maxwell3d.regs.vertex_buffer.count = 0; maxwell3d.mme_draw.instance_count = 0; @@ -65,7 +65,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& maxwell3d.regs.draw.topology.Assign( static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0])); if (maxwell3d.ShouldExecute()) { - maxwell3d.GetRasterizer().Draw(true, true); + maxwell3d.Rasterizer().Draw(true, true); } maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base? maxwell3d.regs.index_array.count = 0; diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp index aa5256419..bd01fd1f2 100644 --- a/src/video_core/macro/macro_interpreter.cpp +++ b/src/video_core/macro/macro_interpreter.cpp @@ -34,7 +34,6 @@ void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 metho this->parameters = std::make_unique<u32[]>(num_parameters); } std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32)); - this->num_parameters = num_parameters; // Execute the code until we hit an exit condition. bool keep_executing = true; diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index 07292702f..954b87515 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, @@ -419,7 +419,6 @@ void Tegra::MacroJITx64Impl::Optimizer_ScanFlags() { void MacroJITx64Impl::Compile() { MICROPROFILE_SCOPE(MacroJitCompile); - bool keep_executing = true; labels.fill(Xbyak::Label()); Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index ff5505d12..02cf53d15 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -4,7 +4,6 @@ #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" @@ -15,122 +14,142 @@ namespace Tegra { -MemoryManager::MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer) - : rasterizer{rasterizer}, system{system} { - page_table.Resize(address_space_width, page_bits, false); - - // Initialize the map with a single free region covering the entire managed space. - VirtualMemoryArea initial_vma; - initial_vma.size = address_space_end; - vma_map.emplace(initial_vma.base, initial_vma); - - UpdatePageTableForVMA(initial_vma); -} +MemoryManager::MemoryManager(Core::System& system_) + : system{system_}, page_table(page_table_size) {} MemoryManager::~MemoryManager() = default; -GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) { - const u64 aligned_size{Common::AlignUp(size, page_size)}; - const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; - - AllocateMemory(gpu_addr, 0, aligned_size); +void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) { + rasterizer = &rasterizer_; +} +GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) { + u64 remaining_size{size}; + for (u64 offset{}; offset < size; offset += page_size) { + if (remaining_size < page_size) { + SetPageEntry(gpu_addr + offset, page_entry + offset, remaining_size); + } else { + SetPageEntry(gpu_addr + offset, page_entry + offset); + } + remaining_size -= page_size; + } return gpu_addr; } -GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) { - const u64 aligned_size{Common::AlignUp(size, page_size)}; - - AllocateMemory(gpu_addr, 0, aligned_size); +GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) { + return UpdateRange(gpu_addr, cpu_addr, size); +} - return gpu_addr; +GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align) { + return Map(cpu_addr, *FindFreeRange(size, align), size); } -GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) { - const u64 aligned_size{Common::AlignUp(size, page_size)}; - const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; +void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { + if (!size) { + return; + } - MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr); - ASSERT( - system.CurrentProcess()->PageTable().LockForDeviceAddressSpace(cpu_addr, size).IsSuccess()); + // Flush and invalidate through the GPU interface, to be asynchronous if possible. + system.GPU().FlushAndInvalidateRegion(*GpuToCpuAddress(gpu_addr), size); - return gpu_addr; + UpdateRange(gpu_addr, PageEntry::State::Unmapped, size); } -GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) { - ASSERT((gpu_addr & page_mask) == 0); +std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) { + for (u64 offset{}; offset < size; offset += page_size) { + if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) { + return std::nullopt; + } + } - const u64 aligned_size{Common::AlignUp(size, page_size)}; + return UpdateRange(gpu_addr, PageEntry::State::Allocated, size); +} - MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr); - ASSERT( - system.CurrentProcess()->PageTable().LockForDeviceAddressSpace(cpu_addr, size).IsSuccess()); - return gpu_addr; +GPUVAddr MemoryManager::Allocate(std::size_t size, std::size_t align) { + return *AllocateFixed(*FindFreeRange(size, align), size); } -GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { - ASSERT((gpu_addr & page_mask) == 0); +void MemoryManager::TryLockPage(PageEntry page_entry, std::size_t size) { + if (!page_entry.IsValid()) { + return; + } - const u64 aligned_size{Common::AlignUp(size, page_size)}; - const auto cpu_addr = GpuToCpuAddress(gpu_addr); - ASSERT(cpu_addr); + ASSERT(system.CurrentProcess() + ->PageTable() + .LockForDeviceAddressSpace(page_entry.ToAddress(), size) + .IsSuccess()); +} - // Flush and invalidate through the GPU interface, to be asynchronous if possible. - system.GPU().FlushAndInvalidateRegion(*cpu_addr, aligned_size); +void MemoryManager::TryUnlockPage(PageEntry page_entry, std::size_t size) { + if (!page_entry.IsValid()) { + return; + } - UnmapRange(gpu_addr, aligned_size); ASSERT(system.CurrentProcess() ->PageTable() - .UnlockForDeviceAddressSpace(cpu_addr.value(), size) + .UnlockForDeviceAddressSpace(page_entry.ToAddress(), size) .IsSuccess()); +} - return gpu_addr; +PageEntry MemoryManager::GetPageEntry(GPUVAddr gpu_addr) const { + return page_table[PageEntryIndex(gpu_addr)]; } -GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) const { - // Find the first Free VMA. - const VMAHandle vma_handle{ - std::find_if(vma_map.begin(), vma_map.end(), [region_start, size](const auto& vma) { - if (vma.second.type != VirtualMemoryArea::Type::Unmapped) { - return false; - } +void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) { + // TODO(bunnei): We should lock/unlock device regions. This currently causes issues due to + // improper tracking, but should be fixed in the future. - const VAddr vma_end{vma.second.base + vma.second.size}; - return vma_end > region_start && vma_end >= region_start + size; - })}; + //// Unlock the old page + // TryUnlockPage(page_table[PageEntryIndex(gpu_addr)], size); - if (vma_handle == vma_map.end()) { - return {}; - } + //// Lock the new page + // TryLockPage(page_entry, size); - return std::max(region_start, vma_handle->second.base); + page_table[PageEntryIndex(gpu_addr)] = page_entry; } -bool MemoryManager::IsAddressValid(GPUVAddr addr) const { - return (addr >> page_bits) < page_table.pointers.size(); -} +std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align) const { + if (!align) { + align = page_size; + } else { + align = Common::AlignUp(align, page_size); + } -std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) const { - if (!IsAddressValid(addr)) { - return {}; + u64 available_size{}; + GPUVAddr gpu_addr{address_space_start}; + while (gpu_addr + available_size < address_space_size) { + if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) { + available_size += page_size; + + if (available_size >= size) { + return gpu_addr; + } + } else { + gpu_addr += available_size + page_size; + available_size = 0; + + const auto remainder{gpu_addr % align}; + if (remainder) { + gpu_addr = (gpu_addr - remainder) + align; + } + } } - const VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]}; - if (cpu_addr) { - return cpu_addr + (addr & page_mask); + return std::nullopt; +} + +std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { + const auto page_entry{GetPageEntry(gpu_addr)}; + if (!page_entry.IsValid()) { + return std::nullopt; } - return {}; + return page_entry.ToAddress() + (gpu_addr & page_mask); } template <typename T> T MemoryManager::Read(GPUVAddr addr) const { - if (!IsAddressValid(addr)) { - return {}; - } - - const u8* page_pointer{GetPointer(addr)}; - if (page_pointer) { + if (auto page_pointer{GetPointer(addr)}; page_pointer) { // NOTE: Avoid adding any extra logic to this fast-path block T value; std::memcpy(&value, page_pointer, sizeof(T)); @@ -144,12 +163,7 @@ T MemoryManager::Read(GPUVAddr addr) const { template <typename T> void MemoryManager::Write(GPUVAddr addr, T data) { - if (!IsAddressValid(addr)) { - return; - } - - u8* page_pointer{GetPointer(addr)}; - if (page_pointer) { + if (auto page_pointer{GetPointer(addr)}; page_pointer) { // NOTE: Avoid adding any extra logic to this fast-path block std::memcpy(page_pointer, &data, sizeof(T)); return; @@ -167,66 +181,49 @@ template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data); template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data); template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data); -u8* MemoryManager::GetPointer(GPUVAddr addr) { - if (!IsAddressValid(addr)) { +u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) { + if (!GetPageEntry(gpu_addr).IsValid()) { return {}; } - auto& memory = system.Memory(); - - const VAddr page_addr{page_table.backing_addr[addr >> page_bits]}; - - if (page_addr != 0) { - return memory.GetPointer(page_addr + (addr & page_mask)); + const auto address{GpuToCpuAddress(gpu_addr)}; + if (!address) { + return {}; } - LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr); - return {}; + return system.Memory().GetPointer(*address); } -const u8* MemoryManager::GetPointer(GPUVAddr addr) const { - if (!IsAddressValid(addr)) { +const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const { + if (!GetPageEntry(gpu_addr).IsValid()) { return {}; } - const auto& memory = system.Memory(); - - const VAddr page_addr{page_table.backing_addr[addr >> page_bits]}; - - if (page_addr != 0) { - return memory.GetPointer(page_addr + (addr & page_mask)); + const auto address{GpuToCpuAddress(gpu_addr)}; + if (!address) { + return {}; } - LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr); - return {}; -} - -bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const { - const std::size_t inner_size = size - 1; - const GPUVAddr end = start + inner_size; - const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start)); - const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end)); - const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start); - return range == inner_size; + return system.Memory().GetPointer(*address); } -void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, - const std::size_t size) const { +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}; std::size_t page_offset{gpu_src_addr & page_mask}; - auto& memory = system.Memory(); - while (remaining_size > 0) { const std::size_t copy_amount{ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)}; - const VAddr src_addr{page_table.backing_addr[page_index] + page_offset}; - // Flush must happen on the rasterizer interface, such that memory is always synchronous - // when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu. - rasterizer.FlushRegion(src_addr, copy_amount); - memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount); + if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) { + const auto src_addr{*page_addr + page_offset}; + + // Flush must happen on the rasterizer interface, such that memory is always synchronous + // when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu. + rasterizer->FlushRegion(src_addr, copy_amount); + system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount); + } page_index++; page_offset = 0; @@ -241,18 +238,17 @@ void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t page_index{gpu_src_addr >> page_bits}; std::size_t page_offset{gpu_src_addr & page_mask}; - auto& memory = system.Memory(); - while (remaining_size > 0) { const std::size_t copy_amount{ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)}; - const u8* page_pointer = page_table.pointers[page_index]; - if (page_pointer) { - const VAddr src_addr{page_table.backing_addr[page_index] + page_offset}; - memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount); + + if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) { + const auto src_addr{*page_addr + page_offset}; + system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount); } else { std::memset(dest_buffer, 0, copy_amount); } + page_index++; page_offset = 0; dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; @@ -260,23 +256,23 @@ void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, } } -void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, - const std::size_t size) { +void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) { std::size_t remaining_size{size}; std::size_t page_index{gpu_dest_addr >> page_bits}; std::size_t page_offset{gpu_dest_addr & page_mask}; - auto& memory = system.Memory(); - while (remaining_size > 0) { const std::size_t copy_amount{ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)}; - const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset}; - // Invalidate must happen on the rasterizer interface, such that memory is always - // synchronous when it is written (even when in asynchronous GPU mode). - rasterizer.InvalidateRegion(dest_addr, copy_amount); - memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount); + if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) { + const auto dest_addr{*page_addr + page_offset}; + + // Invalidate must happen on the rasterizer interface, such that memory is always + // synchronous when it is written (even when in asynchronous GPU mode). + rasterizer->InvalidateRegion(dest_addr, copy_amount); + system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount); + } page_index++; page_offset = 0; @@ -286,21 +282,20 @@ void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, } void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, - const std::size_t size) { + std::size_t size) { std::size_t remaining_size{size}; std::size_t page_index{gpu_dest_addr >> page_bits}; std::size_t page_offset{gpu_dest_addr & page_mask}; - auto& memory = system.Memory(); - while (remaining_size > 0) { const std::size_t copy_amount{ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)}; - u8* page_pointer = page_table.pointers[page_index]; - if (page_pointer) { - const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset}; - memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount); + + if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) { + const auto dest_addr{*page_addr + page_offset}; + system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount); } + page_index++; page_offset = 0; src_buffer = static_cast<const u8*>(src_buffer) + copy_amount; @@ -308,273 +303,26 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buf } } -void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, - const std::size_t size) { +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, - const std::size_t size) { + 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); } -bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) { - const VAddr addr = page_table.backing_addr[gpu_addr >> page_bits]; - const std::size_t page = (addr & Core::Memory::PAGE_MASK) + size; - return page <= Core::Memory::PAGE_SIZE; -} - -void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type, - VAddr backing_addr) { - LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size, - (base + size) * page_size); - - const VAddr end{base + size}; - ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", - base + page_table.pointers.size()); - - if (memory == nullptr) { - while (base != end) { - page_table.pointers[base] = nullptr; - page_table.backing_addr[base] = 0; - - base += 1; - } - } else { - while (base != end) { - page_table.pointers[base] = memory; - page_table.backing_addr[base] = backing_addr; - - base += 1; - memory += page_size; - backing_addr += page_size; - } - } -} - -void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) { - ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base); - MapPages(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr); -} - -void MemoryManager::UnmapRegion(GPUVAddr base, u64 size) { - ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base); - MapPages(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped); -} - -bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { - ASSERT(base + size == next.base); - if (type != next.type) { - return {}; - } - if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) { - return {}; - } - if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) { - return {}; - } - return true; -} - -MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const { - if (target >= address_space_end) { - return vma_map.end(); - } else { - return std::prev(vma_map.upper_bound(target)); - } -} - -MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) { - VirtualMemoryArea& vma{vma_handle->second}; - - vma.type = VirtualMemoryArea::Type::Allocated; - vma.backing_addr = 0; - vma.backing_memory = {}; - UpdatePageTableForVMA(vma); - - return MergeAdjacent(vma_handle); -} - -MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset, - u64 size) { - - // This is the appropriately sized VMA that will turn into our allocation. - VMAIter vma_handle{CarveVMA(target, size)}; - VirtualMemoryArea& vma{vma_handle->second}; - - ASSERT(vma.size == size); - - vma.offset = offset; - - return Allocate(vma_handle); -} - -MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size, - VAddr backing_addr) { - // This is the appropriately sized VMA that will turn into our allocation. - VMAIter vma_handle{CarveVMA(target, size)}; - VirtualMemoryArea& vma{vma_handle->second}; - - ASSERT(vma.size == size); - - vma.type = VirtualMemoryArea::Type::Mapped; - vma.backing_memory = memory; - vma.backing_addr = backing_addr; - UpdatePageTableForVMA(vma); - - return MergeAdjacent(vma_handle); -} - -void MemoryManager::UnmapRange(GPUVAddr target, u64 size) { - VMAIter vma{CarveVMARange(target, size)}; - const VAddr target_end{target + size}; - const VMAIter end{vma_map.end()}; - - // The comparison against the end of the range must be done using addresses since VMAs can be - // merged during this process, causing invalidation of the iterators. - while (vma != end && vma->second.base < target_end) { - // Unmapped ranges return to allocated state and can be reused - // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games - vma = std::next(Allocate(vma)); - } - - ASSERT(FindVMA(target)->second.size >= size); -} - -MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) { - // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given - // non-const access to its container. - return vma_map.erase(iter, iter); // Erases an empty range of elements -} - -MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) { - ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size); - ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base); - - VMAIter vma_handle{StripIterConstness(FindVMA(base))}; - if (vma_handle == vma_map.end()) { - // Target address is outside the managed range - return {}; - } - - const VirtualMemoryArea& vma{vma_handle->second}; - if (vma.type == VirtualMemoryArea::Type::Mapped) { - // Region is already allocated - return vma_handle; - } - - const VAddr start_in_vma{base - vma.base}; - const VAddr end_in_vma{start_in_vma + size}; - - ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}", - vma.size, end_in_vma); - - if (end_in_vma < vma.size) { - // Split VMA at the end of the allocated region - SplitVMA(vma_handle, end_in_vma); - } - if (start_in_vma != 0) { - // Split VMA at the start of the allocated region - vma_handle = SplitVMA(vma_handle, start_in_vma); - } - - return vma_handle; -} - -MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) { - ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size); - ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target); - - const VAddr target_end{target + size}; - ASSERT(target_end >= target); - ASSERT(size > 0); - - VMAIter begin_vma{StripIterConstness(FindVMA(target))}; - const VMAIter i_end{vma_map.lower_bound(target_end)}; - if (std::any_of(begin_vma, i_end, [](const auto& entry) { - return entry.second.type == VirtualMemoryArea::Type::Unmapped; - })) { - return {}; - } - - if (target != begin_vma->second.base) { - begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base); - } - - VMAIter end_vma{StripIterConstness(FindVMA(target_end))}; - if (end_vma != vma_map.end() && target_end != end_vma->second.base) { - end_vma = SplitVMA(end_vma, target_end - end_vma->second.base); - } - - return begin_vma; -} - -MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) { - VirtualMemoryArea& old_vma{vma_handle->second}; - VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA - - // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably - // a bug. This restriction might be removed later. - ASSERT(offset_in_vma < old_vma.size); - ASSERT(offset_in_vma > 0); - - old_vma.size = offset_in_vma; - new_vma.base += offset_in_vma; - new_vma.size -= offset_in_vma; - - switch (new_vma.type) { - case VirtualMemoryArea::Type::Unmapped: - break; - case VirtualMemoryArea::Type::Allocated: - new_vma.offset += offset_in_vma; - break; - case VirtualMemoryArea::Type::Mapped: - new_vma.backing_memory += offset_in_vma; - break; - } - - ASSERT(old_vma.CanBeMergedWith(new_vma)); - - return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); -} - -MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) { - const VMAIter next_vma{std::next(iter)}; - if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) { - iter->second.size += next_vma->second.size; - vma_map.erase(next_vma); - } - - if (iter != vma_map.begin()) { - VMAIter prev_vma{std::prev(iter)}; - if (prev_vma->second.CanBeMergedWith(iter->second)) { - prev_vma->second.size += iter->second.size; - vma_map.erase(iter); - iter = prev_vma; - } - } - - return iter; -} - -void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { - switch (vma.type) { - case VirtualMemoryArea::Type::Unmapped: - UnmapRegion(vma.base, vma.size); - break; - case VirtualMemoryArea::Type::Allocated: - MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr); - break; - case VirtualMemoryArea::Type::Mapped: - MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr); - break; +bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const { + const auto cpu_addr{GpuToCpuAddress(gpu_addr)}; + if (!cpu_addr) { + return false; } + const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size}; + return page <= Core::Memory::PAGE_SIZE; } } // namespace Tegra diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 87658e87a..53c8d122a 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -6,9 +6,9 @@ #include <map> #include <optional> +#include <vector> #include "common/common_types.h" -#include "common/page_table.h" namespace VideoCore { class RasterizerInterface; @@ -20,58 +20,70 @@ class System; namespace Tegra { -/** - * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space - * with homogeneous attributes across its extents. In this particular implementation each VMA is - * also backed by a single host memory allocation. - */ -struct VirtualMemoryArea { - enum class Type : u8 { - Unmapped, - Allocated, - Mapped, +class PageEntry final { +public: + enum class State : u32 { + Unmapped = static_cast<u32>(-1), + Allocated = static_cast<u32>(-2), }; - /// Virtual base address of the region. - GPUVAddr base{}; - /// Size of the region. - u64 size{}; - /// Memory area mapping type. - Type type{Type::Unmapped}; - /// CPU memory mapped address corresponding to this memory area. - VAddr backing_addr{}; - /// Offset into the backing_memory the mapping starts from. - std::size_t offset{}; - /// Pointer backing this VMA. - u8* backing_memory{}; - - /// Tests if this area can be merged to the right with `next`. - bool CanBeMergedWith(const VirtualMemoryArea& next) const; + constexpr PageEntry() = default; + constexpr PageEntry(State state) : state{state} {} + constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {} + + [[nodiscard]] constexpr bool IsUnmapped() const { + return state == State::Unmapped; + } + + [[nodiscard]] constexpr bool IsAllocated() const { + return state == State::Allocated; + } + + [[nodiscard]] constexpr bool IsValid() const { + return !IsUnmapped() && !IsAllocated(); + } + + [[nodiscard]] constexpr VAddr ToAddress() const { + if (!IsValid()) { + return {}; + } + + return static_cast<VAddr>(state) << ShiftBits; + } + + [[nodiscard]] constexpr PageEntry operator+(u64 offset) const { + // If this is a reserved value, offsets do not apply + if (!IsValid()) { + return *this; + } + return PageEntry{(static_cast<VAddr>(state) << ShiftBits) + offset}; + } + +private: + static constexpr std::size_t ShiftBits{12}; + + State state{State::Unmapped}; }; +static_assert(sizeof(PageEntry) == 4, "PageEntry is too large"); class MemoryManager final { public: - explicit MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer); + explicit MemoryManager(Core::System& system); ~MemoryManager(); - GPUVAddr AllocateSpace(u64 size, u64 align); - GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align); - GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size); - GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size); - GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size); - std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const; + /// Binds a renderer to the memory manager. + void BindRasterizer(VideoCore::RasterizerInterface& rasterizer); + + [[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const; template <typename T> - T Read(GPUVAddr addr) const; + [[nodiscard]] T Read(GPUVAddr addr) const; template <typename T> void Write(GPUVAddr addr, T data); - u8* GetPointer(GPUVAddr addr); - const u8* GetPointer(GPUVAddr addr) const; - - /// Returns true if the block is continuous in host memory, false otherwise - bool IsBlockContinuous(GPUVAddr start, std::size_t size) const; + [[nodiscard]] u8* GetPointer(GPUVAddr addr); + [[nodiscard]] const u8* GetPointer(GPUVAddr addr) const; /** * ReadBlock and WriteBlock are full read and write operations over virtual @@ -98,92 +110,43 @@ public: 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 + * IsGranularRange checks if a gpu region can be simply read with a pointer. */ - bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size); - -private: - using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>; - using VMAHandle = VMAMap::const_iterator; - using VMAIter = VMAMap::iterator; - - bool IsAddressValid(GPUVAddr addr) const; - void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type, - VAddr backing_addr = 0); - void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr); - void UnmapRegion(GPUVAddr base, u64 size); + [[nodiscard]] bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const; - /// Finds the VMA in which the given address is included in, or `vma_map.end()`. - VMAHandle FindVMA(GPUVAddr target) const; - - VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size); - - /** - * Maps an unmanaged host memory pointer at a given address. - * - * @param target The guest address to start the mapping at. - * @param memory The memory to be mapped. - * @param size Size of the mapping in bytes. - * @param backing_addr The base address of the range to back this mapping. - */ - VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr); + [[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]] 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); - /// Unmaps a range of addresses, splitting VMAs as necessary. - void UnmapRange(GPUVAddr target, u64 size); - - /// Converts a VMAHandle to a mutable VMAIter. - VMAIter StripIterConstness(const VMAHandle& iter); - - /// Marks as the specified VMA as allocated. - VMAIter Allocate(VMAIter vma); - - /** - * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing - * the appropriate error checking. - */ - VMAIter CarveVMA(GPUVAddr base, u64 size); - - /** - * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each - * end of the range. - */ - VMAIter CarveVMARange(GPUVAddr base, u64 size); - - /** - * Splits a VMA in two, at the specified offset. - * @returns the right side of the split, with the original iterator becoming the left side. - */ - VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma); - - /** - * Checks for and merges the specified VMA with adjacent ones if possible. - * @returns the merged VMA or the original if no merging was possible. - */ - VMAIter MergeAdjacent(VMAIter vma); +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; - /// Updates the pages corresponding to this VMA so they match the VMA's attributes. - void UpdatePageTableForVMA(const VirtualMemoryArea& vma); + void TryLockPage(PageEntry page_entry, std::size_t size); + void TryUnlockPage(PageEntry page_entry, std::size_t size); - /// Finds a free (unmapped region) of the specified size starting at the specified address. - GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size) const; + [[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) { + return (gpu_addr >> page_bits) & page_table_mask; + } -private: + static constexpr u64 address_space_size = 1ULL << 40; + static constexpr u64 address_space_start = 1ULL << 32; static constexpr u64 page_bits{16}; static constexpr u64 page_size{1 << page_bits}; static constexpr u64 page_mask{page_size - 1}; + static constexpr u64 page_table_bits{24}; + static constexpr u64 page_table_size{1 << page_table_bits}; + static constexpr u64 page_table_mask{page_table_size - 1}; - /// Address space in bits, according to Tegra X1 TRM - static constexpr u32 address_space_width{40}; - /// Start address for mapping, this is fairly arbitrary but must be non-zero. - static constexpr GPUVAddr address_space_base{0x100000}; - /// End of address space, based on address space in bits. - static constexpr GPUVAddr address_space_end{1ULL << address_space_width}; + Core::System& system; - Common::PageTable page_table; - VMAMap vma_map; - VideoCore::RasterizerInterface& rasterizer; + VideoCore::RasterizerInterface* rasterizer = nullptr; - Core::System& system; + std::vector<PageEntry> page_table; }; } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index 836b25c1d..9da9fb4ff 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -41,146 +41,168 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth } static constexpr ConversionArray morton_to_linear_fns = { - MortonCopy<true, PixelFormat::ABGR8U>, - MortonCopy<true, PixelFormat::ABGR8S>, - MortonCopy<true, PixelFormat::ABGR8UI>, - MortonCopy<true, PixelFormat::B5G6R5U>, - MortonCopy<true, PixelFormat::A2B10G10R10U>, - MortonCopy<true, PixelFormat::A1B5G5R5U>, - MortonCopy<true, PixelFormat::R8U>, - MortonCopy<true, PixelFormat::R8UI>, - MortonCopy<true, PixelFormat::RGBA16F>, - MortonCopy<true, PixelFormat::RGBA16U>, - MortonCopy<true, PixelFormat::RGBA16S>, - MortonCopy<true, PixelFormat::RGBA16UI>, - MortonCopy<true, PixelFormat::R11FG11FB10F>, - MortonCopy<true, PixelFormat::RGBA32UI>, - MortonCopy<true, PixelFormat::DXT1>, - MortonCopy<true, PixelFormat::DXT23>, - MortonCopy<true, PixelFormat::DXT45>, - MortonCopy<true, PixelFormat::DXN1>, - MortonCopy<true, PixelFormat::DXN2UNORM>, - MortonCopy<true, PixelFormat::DXN2SNORM>, - MortonCopy<true, PixelFormat::BC7U>, - MortonCopy<true, PixelFormat::BC6H_UF16>, - MortonCopy<true, PixelFormat::BC6H_SF16>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4>, - MortonCopy<true, PixelFormat::BGRA8>, - MortonCopy<true, PixelFormat::RGBA32F>, - MortonCopy<true, PixelFormat::RG32F>, - MortonCopy<true, PixelFormat::R32F>, - MortonCopy<true, PixelFormat::R16F>, - MortonCopy<true, PixelFormat::R16U>, - MortonCopy<true, PixelFormat::R16S>, - MortonCopy<true, PixelFormat::R16UI>, - MortonCopy<true, PixelFormat::R16I>, - MortonCopy<true, PixelFormat::RG16>, - MortonCopy<true, PixelFormat::RG16F>, - MortonCopy<true, PixelFormat::RG16UI>, - MortonCopy<true, PixelFormat::RG16I>, - MortonCopy<true, PixelFormat::RG16S>, - MortonCopy<true, PixelFormat::RGB32F>, - MortonCopy<true, PixelFormat::RGBA8_SRGB>, - MortonCopy<true, PixelFormat::RG8U>, - MortonCopy<true, PixelFormat::RG8S>, - MortonCopy<true, PixelFormat::RG8UI>, - MortonCopy<true, PixelFormat::RG32UI>, - MortonCopy<true, PixelFormat::RGBX16F>, - MortonCopy<true, PixelFormat::R32UI>, - MortonCopy<true, PixelFormat::R32I>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4>, - MortonCopy<true, PixelFormat::BGRA8_SRGB>, - MortonCopy<true, PixelFormat::DXT1_SRGB>, - MortonCopy<true, PixelFormat::DXT23_SRGB>, - MortonCopy<true, PixelFormat::DXT45_SRGB>, - MortonCopy<true, PixelFormat::BC7U_SRGB>, - MortonCopy<true, PixelFormat::R4G4B4A4U>, + 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>, + MortonCopy<true, PixelFormat::ASTC_2D_5X5_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8>, + MortonCopy<true, PixelFormat::ASTC_2D_10X8_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_6X6>, + MortonCopy<true, PixelFormat::ASTC_2D_6X6_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_6X6_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X10>, + MortonCopy<true, PixelFormat::ASTC_2D_10X10_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_10X10_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_12X12>, + MortonCopy<true, PixelFormat::ASTC_2D_12X12_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_12X12_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X6>, + MortonCopy<true, PixelFormat::ASTC_2D_8X6_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_8X6_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_6X5>, + MortonCopy<true, PixelFormat::ASTC_2D_6X5_UNORM>, MortonCopy<true, PixelFormat::ASTC_2D_6X5_SRGB>, - MortonCopy<true, PixelFormat::E5B9G9R9F>, - MortonCopy<true, PixelFormat::Z32F>, - MortonCopy<true, PixelFormat::Z16>, - MortonCopy<true, PixelFormat::Z24S8>, - MortonCopy<true, PixelFormat::S8Z24>, - MortonCopy<true, PixelFormat::Z32FS8>, + 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::ABGR8U>, - MortonCopy<false, PixelFormat::ABGR8S>, - MortonCopy<false, PixelFormat::ABGR8UI>, - MortonCopy<false, PixelFormat::B5G6R5U>, - MortonCopy<false, PixelFormat::A2B10G10R10U>, - MortonCopy<false, PixelFormat::A1B5G5R5U>, - MortonCopy<false, PixelFormat::R8U>, - MortonCopy<false, PixelFormat::R8UI>, - MortonCopy<false, PixelFormat::RGBA16F>, - MortonCopy<false, PixelFormat::RGBA16S>, - MortonCopy<false, PixelFormat::RGBA16U>, - MortonCopy<false, PixelFormat::RGBA16UI>, - MortonCopy<false, PixelFormat::R11FG11FB10F>, - MortonCopy<false, PixelFormat::RGBA32UI>, - MortonCopy<false, PixelFormat::DXT1>, - MortonCopy<false, PixelFormat::DXT23>, - MortonCopy<false, PixelFormat::DXT45>, - MortonCopy<false, PixelFormat::DXN1>, - MortonCopy<false, PixelFormat::DXN2UNORM>, - MortonCopy<false, PixelFormat::DXN2SNORM>, - MortonCopy<false, PixelFormat::BC7U>, - MortonCopy<false, PixelFormat::BC6H_UF16>, - MortonCopy<false, PixelFormat::BC6H_SF16>, + 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::BGRA8>, - MortonCopy<false, PixelFormat::RGBA32F>, - MortonCopy<false, PixelFormat::RG32F>, - MortonCopy<false, PixelFormat::R32F>, - MortonCopy<false, PixelFormat::R16F>, - MortonCopy<false, PixelFormat::R16U>, - MortonCopy<false, PixelFormat::R16S>, - MortonCopy<false, PixelFormat::R16UI>, - MortonCopy<false, PixelFormat::R16I>, - MortonCopy<false, PixelFormat::RG16>, - MortonCopy<false, PixelFormat::RG16F>, - MortonCopy<false, PixelFormat::RG16UI>, - MortonCopy<false, PixelFormat::RG16I>, - MortonCopy<false, PixelFormat::RG16S>, - MortonCopy<false, PixelFormat::RGB32F>, - MortonCopy<false, PixelFormat::RGBA8_SRGB>, - MortonCopy<false, PixelFormat::RG8U>, - MortonCopy<false, PixelFormat::RG8S>, - MortonCopy<false, PixelFormat::RG8UI>, - MortonCopy<false, PixelFormat::RG32UI>, - MortonCopy<false, PixelFormat::RGBX16F>, - MortonCopy<false, PixelFormat::R32UI>, - MortonCopy<false, PixelFormat::R32I>, + 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::BGRA8_SRGB>, - MortonCopy<false, PixelFormat::DXT1_SRGB>, - MortonCopy<false, PixelFormat::DXT23_SRGB>, - MortonCopy<false, PixelFormat::DXT45_SRGB>, - MortonCopy<false, PixelFormat::BC7U_SRGB>, - MortonCopy<false, PixelFormat::R4G4B4A4U>, + 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, @@ -199,12 +221,12 @@ static constexpr ConversionArray linear_to_morton_fns = { nullptr, nullptr, nullptr, - MortonCopy<false, PixelFormat::E5B9G9R9F>, - MortonCopy<false, PixelFormat::Z32F>, - MortonCopy<false, PixelFormat::Z16>, - MortonCopy<false, PixelFormat::Z24S8>, - MortonCopy<false, PixelFormat::S8Z24>, - MortonCopy<false, PixelFormat::Z32FS8>, + 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) { diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index 0d3a88765..fc54ca0ef 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -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; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 3cbdac8e7..b3e0919f8 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -106,11 +106,8 @@ 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() { diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index dfb06e87e..a93a1732c 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -9,7 +9,9 @@ namespace VideoCore { -RendererBase::RendererBase(Core::Frontend::EmuWindow& window) : render_window{window} { +RendererBase::RendererBase(Core::Frontend::EmuWindow& window_, + std::unique_ptr<Core::Frontend::GraphicsContext> context_) + : render_window{window_}, context{std::move(context_)} { RefreshBaseSettings(); } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 1d85219b6..5c650808b 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -15,7 +15,8 @@ namespace Core::Frontend { class EmuWindow; -} +class GraphicsContext; +} // namespace Core::Frontend namespace VideoCore { @@ -25,14 +26,15 @@ struct RendererSettings { // Screenshot std::atomic<bool> screenshot_requested{false}; - void* screenshot_bits; + void* screenshot_bits{}; std::function<void()> screenshot_complete_callback; Layout::FramebufferLayout screenshot_framebuffer_layout; }; class RendererBase : NonCopyable { public: - explicit RendererBase(Core::Frontend::EmuWindow& window); + explicit RendererBase(Core::Frontend::EmuWindow& window, + std::unique_ptr<Core::Frontend::GraphicsContext> context); virtual ~RendererBase(); /// Initialize the renderer @@ -44,11 +46,6 @@ public: /// 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; - // Getter/setter functions: // ------------------------ @@ -68,6 +65,14 @@ public: return *rasterizer; } + Core::Frontend::GraphicsContext& Context() { + return *context; + } + + const Core::Frontend::GraphicsContext& Context() const { + return *context; + } + Core::Frontend::EmuWindow& GetRenderWindow() { return render_window; } @@ -94,6 +99,7 @@ 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 eb5158407..b7e9ed2e9 100644 --- a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp @@ -185,10 +185,6 @@ std::string TextureType(const MetaTexture& meta) { return type; } -std::string GlobalMemoryName(const GlobalMemoryBase& base) { - return fmt::format("gmem{}_{}", base.cbuf_index, base.cbuf_offset); -} - class ARBDecompiler final { public: explicit ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry, @@ -199,6 +195,8 @@ public: } private: + void DefineGlobalMemory(); + void DeclareHeader(); void DeclareVertex(); void DeclareGeometry(); @@ -228,6 +226,7 @@ private: std::pair<std::string, std::size_t> BuildCoords(Operation); std::string BuildAoffi(Operation); + std::string GlobalMemoryPointer(const GmemNode& gmem); void Exit(); std::string Assign(Operation); @@ -378,10 +377,8 @@ private: std::string address; std::string_view opname; if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) { - AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem->GetRealAddress()), - Visit(gmem->GetBaseAddress())); - address = fmt::format("{}[{}]", GlobalMemoryName(gmem->GetDescriptor()), temporary); - opname = "ATOMB"; + address = GlobalMemoryPointer(*gmem); + opname = "ATOM"; } else if (const auto smem = std::get_if<SmemNode>(&*operation[0])) { address = fmt::format("shared_mem[{}]", Visit(smem->GetAddress())); opname = "ATOMS"; @@ -456,9 +453,13 @@ private: shader_source += '\n'; } - std::string AllocTemporary() { - max_temporaries = std::max(max_temporaries, num_temporaries + 1); - return fmt::format("T{}.x", num_temporaries++); + std::string AllocLongVectorTemporary() { + max_long_temporaries = std::max(max_long_temporaries, num_long_temporaries + 1); + return fmt::format("L{}", num_long_temporaries++); + } + + std::string AllocLongTemporary() { + return fmt::format("{}.x", AllocLongVectorTemporary()); } std::string AllocVectorTemporary() { @@ -466,8 +467,13 @@ private: return fmt::format("T{}", num_temporaries++); } + std::string AllocTemporary() { + return fmt::format("{}.x", AllocVectorTemporary()); + } + void ResetTemporaries() noexcept { num_temporaries = 0; + num_long_temporaries = 0; } const Device& device; @@ -478,6 +484,11 @@ private: std::size_t num_temporaries = 0; std::size_t max_temporaries = 0; + std::size_t num_long_temporaries = 0; + std::size_t max_long_temporaries = 0; + + std::map<GlobalMemoryBase, u32> global_memory_names; + std::string shader_source; static constexpr std::string_view ADD_F32 = "ADD.F32"; @@ -784,6 +795,8 @@ 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} { + DefineGlobalMemory(); + AddLine("TEMP RC;"); AddLine("TEMP FSWZA[4];"); AddLine("TEMP FSWZB[4];"); @@ -829,12 +842,20 @@ std::string_view HeaderStageName(ShaderType stage) { } } +void ARBDecompiler::DefineGlobalMemory() { + u32 binding = 0; + for (const auto& pair : ir.GetGlobalMemory()) { + const GlobalMemoryBase base = pair.first; + global_memory_names.emplace(base, binding); + ++binding; + } +} + void ARBDecompiler::DeclareHeader() { AddLine("!!NV{}5.0", HeaderStageName(stage)); // Enabling this allows us to cheat on some instructions like TXL with SHADOWARRAY2D AddLine("OPTION NV_internal;"); AddLine("OPTION NV_gpu_program_fp64;"); - AddLine("OPTION NV_shader_storage_buffer;"); AddLine("OPTION NV_shader_thread_group;"); if (ir.UsesWarps() && device.HasWarpIntrinsics()) { AddLine("OPTION NV_shader_thread_shuffle;"); @@ -892,11 +913,19 @@ void ARBDecompiler::DeclareCompute() { const ComputeInfo& info = registry.GetComputeInfo(); AddLine("GROUP_SIZE {} {} {};", info.workgroup_size[0], info.workgroup_size[1], info.workgroup_size[2]); - if (info.shared_memory_size_in_words > 0) { - const u32 size_in_bytes = info.shared_memory_size_in_words * 4; - AddLine("SHARED_MEMORY {};", size_in_bytes); - AddLine("SHARED shared_mem[] = {{program.sharedmem}};"); + if (info.shared_memory_size_in_words == 0) { + return; + } + const u32 limit = device.GetMaxComputeSharedMemorySize(); + u32 size_in_bytes = info.shared_memory_size_in_words * 4; + if (size_in_bytes > limit) { + LOG_ERROR(Render_OpenGL, "Shared memory size {} is clamped to host's limit {}", + size_in_bytes, limit); + size_in_bytes = limit; } + + AddLine("SHARED_MEMORY {};", size_in_bytes); + AddLine("SHARED shared_mem[] = {{program.sharedmem}};"); } void ARBDecompiler::DeclareInputAttributes() { @@ -951,11 +980,10 @@ void ARBDecompiler::DeclareLocalMemory() { } void ARBDecompiler::DeclareGlobalMemory() { - u32 binding = 0; // device.GetBaseBindings(stage).shader_storage_buffer; - for (const auto& pair : ir.GetGlobalMemory()) { - const auto& base = pair.first; - AddLine("STORAGE {}[] = {{ program.storage[{}] }};", GlobalMemoryName(base), binding); - ++binding; + const std::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); } } @@ -977,6 +1005,9 @@ void ARBDecompiler::DeclareTemporaries() { for (std::size_t i = 0; i < max_temporaries; ++i) { AddLine("TEMP T{};", i); } + for (std::size_t i = 0; i < max_long_temporaries; ++i) { + AddLine("LONG TEMP L{};", i); + } } void ARBDecompiler::DeclarePredicates() { @@ -1260,13 +1291,6 @@ std::string ARBDecompiler::Visit(const Node& node) { return "{0, 0, 0, 0}.x"; } - const auto buffer_index = [this, &abuf]() -> std::string { - if (stage != ShaderType::Geometry) { - return ""; - } - return fmt::format("[{}]", Visit(abuf->GetBuffer())); - }; - const Attribute::Index index = abuf->GetIndex(); const u32 element = abuf->GetElement(); const char swizzle = Swizzle(element); @@ -1339,10 +1363,7 @@ std::string ARBDecompiler::Visit(const Node& node) { if (const auto gmem = std::get_if<GmemNode>(&*node)) { std::string temporary = AllocTemporary(); - AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem->GetRealAddress()), - Visit(gmem->GetBaseAddress())); - AddLine("LDB.U32 {}, {}[{}];", temporary, GlobalMemoryName(gmem->GetDescriptor()), - temporary); + AddLine("LOAD.U32 {}, {};", temporary, GlobalMemoryPointer(*gmem)); return temporary; } @@ -1375,7 +1396,7 @@ std::string ARBDecompiler::Visit(const Node& node) { return {}; } - if (const auto cmt = std::get_if<CommentNode>(&*node)) { + if ([[maybe_unused]] const auto cmt = std::get_if<CommentNode>(&*node)) { // Uncommenting this will generate invalid code. GLASM lacks comments. // AddLine("// {}", cmt->GetText()); return {}; @@ -1419,6 +1440,22 @@ std::string ARBDecompiler::BuildAoffi(Operation operation) { return fmt::format(", offset({})", temporary); } +std::string ARBDecompiler::GlobalMemoryPointer(const GmemNode& gmem) { + 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("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); + return fmt::format("{}.x", pointer); +} + void ARBDecompiler::Exit() { if (stage != ShaderType::Fragment) { AddLine("RET;"); @@ -1515,11 +1552,7 @@ std::string ARBDecompiler::Assign(Operation operation) { ResetTemporaries(); return {}; } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { - const std::string temporary = AllocTemporary(); - AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem->GetRealAddress()), - Visit(gmem->GetBaseAddress())); - AddLine("STB.U32 {}, {}[{}];", Visit(src), GlobalMemoryName(gmem->GetDescriptor()), - temporary); + AddLine("STORE.U32 {}, {};", Visit(src), GlobalMemoryPointer(*gmem)); ResetTemporaries(); return {}; } else { @@ -1671,7 +1704,7 @@ std::string ARBDecompiler::HCastFloat(Operation operation) { } std::string ARBDecompiler::HUnpack(Operation operation) { - const std::string operand = Visit(operation[0]); + std::string operand = Visit(operation[0]); switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { case Tegra::Shader::HalfType::H0_H1: return operand; @@ -2021,7 +2054,7 @@ std::string ARBDecompiler::InvocationId(Operation) { std::string ARBDecompiler::YNegate(Operation) { LOG_WARNING(Render_OpenGL, "(STUBBED)"); - const std::string temporary = AllocTemporary(); + std::string temporary = AllocTemporary(); AddLine("MOV.F {}, 1;", temporary); return temporary; } @@ -2044,10 +2077,6 @@ std::string ARBDecompiler::ShuffleIndexed(Operation operation) { } std::string ARBDecompiler::Barrier(Operation) { - if (!ir.IsDecompiled()) { - LOG_ERROR(Render_OpenGL, "BAR used but shader is not decompiled"); - return {}; - } AddLine("BAR;"); return {}; } diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index e461e4c70..b1c4cd62f 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -26,7 +26,7 @@ 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.HasVertexBufferUnifiedMemory()) { + if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) { glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_WRITE); glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address); } @@ -59,9 +59,10 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size)); } -OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, +OGLBufferCache::OGLBufferCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory, const Device& device_, std::size_t stream_size) - : GenericBufferCache{rasterizer, system, + : GenericBufferCache{rasterizer, gpu_memory, cpu_memory, std::make_unique<OGLStreamBuffer>(device_, stream_size, true)}, device{device_} { if (!device.HasFastBufferSubData()) { diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 88fdc0536..f75b32e31 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -52,7 +52,8 @@ private: using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>; class OGLBufferCache final : public GenericBufferCache { public: - explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, + explicit OGLBufferCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory, const Device& device, std::size_t stream_size); ~OGLBufferCache(); diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index c1f20f0ab..a94e4f72e 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -193,7 +193,6 @@ bool IsASTCSupported() { Device::Device() : max_uniform_buffers{BuildMaxUniformBuffers()}, base_bindings{BuildBaseBindings()} { 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(); @@ -212,6 +211,7 @@ Device::Device() shader_storage_alignment = GetInteger<std::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); has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && GLAD_GL_NV_shader_thread_shuffle; has_shader_ballot = GLAD_GL_ARB_shader_ballot; @@ -233,6 +233,8 @@ Device::Device() GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 && GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2; + use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue(); + 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); @@ -248,6 +250,7 @@ Device::Device(std::nullptr_t) { shader_storage_alignment = 4; max_vertex_attributes = 16; max_varyings = 15; + max_compute_shared_memory_size = 0x10000; has_warp_intrinsics = true; has_shader_ballot = true; has_vertex_viewport_layer = true; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index e1d811966..8a4b6b9fc 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -52,6 +52,10 @@ public: return max_varyings; } + u32 GetMaxComputeSharedMemorySize() const { + return max_compute_shared_memory_size; + } + bool HasWarpIntrinsics() const { return has_warp_intrinsics; } @@ -104,6 +108,10 @@ public: return use_assembly_shaders; } + bool UseAsynchronousShaders() const { + return use_asynchronous_shaders; + } + private: static bool TestVariableAoffi(); static bool TestPreciseBug(); @@ -114,6 +122,7 @@ private: std::size_t shader_storage_alignment{}; u32 max_vertex_attributes{}; u32 max_varyings{}; + u32 max_compute_shared_memory_size{}; bool has_warp_intrinsics{}; bool has_shader_ballot{}; bool has_vertex_viewport_layer{}; @@ -127,6 +136,7 @@ private: bool has_fast_buffer_sub_data{}; bool has_nv_viewport_array2{}; bool use_assembly_shaders{}; + bool use_asynchronous_shaders{}; }; } // 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 ec5421afa..b532fdcc2 100644 --- a/src/video_core/renderer_opengl/gl_fence_manager.cpp +++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp @@ -4,16 +4,17 @@ #include "common/assert.h" +#include <glad/glad.h> + #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_fence_manager.h" namespace OpenGL { -GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) - : VideoCommon::FenceBase(payload, is_stubbed), sync_object{} {} +GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {} GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed) - : VideoCommon::FenceBase(address, payload, is_stubbed), sync_object{} {} + : FenceBase(address, payload, is_stubbed) {} GLInnerFence::~GLInnerFence() = default; @@ -44,11 +45,10 @@ void GLInnerFence::Wait() { glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED); } -FenceManagerOpenGL::FenceManagerOpenGL(Core::System& system, - VideoCore::RasterizerInterface& rasterizer, +FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu, TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache, QueryCache& query_cache) - : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, 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 c917b3343..da1dcdace 100644 --- a/src/video_core/renderer_opengl/gl_fence_manager.h +++ b/src/video_core/renderer_opengl/gl_fence_manager.h @@ -5,7 +5,6 @@ #pragma once #include <memory> -#include <glad/glad.h> #include "common/common_types.h" #include "video_core/fence_manager.h" @@ -38,9 +37,9 @@ using GenericFenceManager = 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, + TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache, + QueryCache& query_cache); protected: Fence CreateFence(u32 value, bool is_stubbed) override; diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp index d7ba57aca..1a3d9720e 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.cpp +++ b/src/video_core/renderer_opengl/gl_query_cache.cpp @@ -30,12 +30,11 @@ 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) + : VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter>( + rasterizer, maxwell3d, gpu_memory), + gl_rasterizer{rasterizer} {} QueryCache::~QueryCache() = default; @@ -90,6 +89,8 @@ u64 HostCounter::BlockingQuery() const { 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() = default; + CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept : VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {} diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h index d8e7052a1..82cac51ee 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,6 +41,7 @@ public: private: RasterizerOpenGL& gl_rasterizer; + std::array<std::vector<OGLQuery>, VideoCore::NumQueryTypes> query_pools; }; class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { @@ -62,10 +64,12 @@ 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; + ~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 e960a0ef1..bbb2eb17c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -139,17 +139,33 @@ 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; + } + if (num_entries % 2 == 1) { + pointers[num_entries] = 0; + } + const GLsizei num_vectors = static_cast<GLsizei>((num_entries + 1) / 2); + glProgramLocalParametersI4uivNV(target, 0, num_vectors, + reinterpret_cast<const GLuint*>(pointers)); +} + } // 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} { +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(*this, maxwell3d, gpu_memory, device, state_tracker), + shader_cache(*this, emu_window, gpu, maxwell3d, kepler_compute, gpu_memory, device), + query_cache(*this, maxwell3d, gpu_memory), + buffer_cache(*this, gpu_memory, cpu_memory, device, STREAM_BUFFER_SIZE), + fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache), + async_shaders(emu_window) { CheckExtensions(); unified_uniform_buffer.Create(); @@ -162,6 +178,10 @@ RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWind nullptr, 0); } } + + if (device.UseAsynchronousShaders()) { + async_shaders.AllocateWorkers(); + } } RasterizerOpenGL::~RasterizerOpenGL() { @@ -179,8 +199,7 @@ void RasterizerOpenGL::CheckExtensions() { } void RasterizerOpenGL::SetupVertexFormat() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::VertexFormats]) { return; } @@ -200,7 +219,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. @@ -224,8 +243,7 @@ void RasterizerOpenGL::SetupVertexFormat() { } void RasterizerOpenGL::SetupVertexBuffer() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + auto& flags = maxwell3d.dirty.flags; if (!flags[Dirty::VertexBuffers]) { return; } @@ -236,7 +254,7 @@ void RasterizerOpenGL::SetupVertexBuffer() { const bool use_unified_memory = device.HasVertexBufferUnifiedMemory(); // Upload all guest vertex arrays sequentially to our buffer - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) { if (!flags[Dirty::VertexBuffer0 + index]) { continue; @@ -273,14 +291,13 @@ void RasterizerOpenGL::SetupVertexBuffer() { } void RasterizerOpenGL::SetupVertexInstances() { - auto& gpu = system.GPU().Maxwell3D(); - auto& flags = gpu.dirty.flags; + 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; @@ -296,7 +313,7 @@ void RasterizerOpenGL::SetupVertexInstances() { GLintptr RasterizerOpenGL::SetupIndexBuffer() { MICROPROFILE_SCOPE(OpenGL_Index); - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = 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); @@ -305,16 +322,14 @@ GLintptr RasterizerOpenGL::SetupIndexBuffer() { void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { MICROPROFILE_SCOPE(OpenGL_Shader); - auto& gpu = system.GPU().Maxwell3D(); - std::size_t num_ssbos = 0; u32 clip_distances = 0; 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); @@ -329,31 +344,15 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { } // Currently this stages are not supported in the OpenGL backend. - // Todo(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL - if (program == Maxwell::ShaderProgram::TesselationControl) { - continue; - } else if (program == Maxwell::ShaderProgram::TesselationEval) { + // TODO(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL + if (program == Maxwell::ShaderProgram::TesselationControl || + program == Maxwell::ShaderProgram::TesselationEval) { continue; } - Shader* const shader = shader_cache.GetStageProgram(program); - - if (device.UseAssemblyShaders()) { - // Check for ARB limitation. We only have 16 SSBOs per context state. To workaround this - // all stages share the same bindings. - const std::size_t num_stage_ssbos = shader->GetEntries().global_memory_entries.size(); - ASSERT_MSG(num_stage_ssbos == 0 || num_ssbos == 0, "SSBOs on more than one stage"); - num_ssbos += num_stage_ssbos; - } - - // 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); + Shader* const shader = shader_cache.GetStageProgram(program, async_shaders); - const GLuint program_handle = shader->GetHandle(); + const GLuint program_handle = shader->IsBuilt() ? shader->GetHandle() : 0; switch (program) { case Maxwell::ShaderProgram::VertexA: case Maxwell::ShaderProgram::VertexB: @@ -370,6 +369,13 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { shader_config.enable.Value(), shader_config.offset); } + // 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); + // Workaround for Intel drivers. // When a clip distance is enabled but not set in the shader it crops parts of the screen // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the @@ -384,11 +390,11 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { } SyncClipEnabled(clip_distances); - gpu.dirty.flags[Dirty::Shaders] = false; + maxwell3d.dirty.flags[Dirty::Shaders] = false; } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; std::size_t size = 0; for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { @@ -406,34 +412,27 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { } 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()); + return static_cast<std::size_t>(maxwell3d.regs.index_array.count) * + static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes()); } -void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, +void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { - shader_cache.LoadDiskCache(stop_loading, callback); -} - -void RasterizerOpenGL::SetupDirtyFlags() { - state_tracker.Initialize(); + shader_cache.LoadDiskCache(title_id, stop_loading, callback); } void RasterizerOpenGL::ConfigureFramebuffers() { MICROPROFILE_SCOPE(OpenGL_Framebuffer); - auto& gpu = system.GPU().Maxwell3D(); - if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) { + if (!maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets]) { return; } - gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false; + maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets] = false; texture_cache.GuardRenderTargets(true); View depth_surface = texture_cache.GetDepthBufferSurface(true); - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0); // Bind the framebuffer surfaces @@ -465,8 +464,7 @@ void RasterizerOpenGL::ConfigureFramebuffers() { } void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) { - auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; texture_cache.GuardRenderTargets(true); View color_surface; @@ -516,12 +514,11 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_de } void RasterizerOpenGL::Clear() { - const auto& gpu = system.GPU().Maxwell3D(); - if (!gpu.ShouldExecute()) { + if (!maxwell3d.ShouldExecute()) { return; } - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.regs; bool use_color{}; bool use_depth{}; bool use_stencil{}; @@ -586,7 +583,6 @@ void RasterizerOpenGL::Clear() { void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { MICROPROFILE_SCOPE(OpenGL_Drawing); - auto& gpu = system.GPU().Maxwell3D(); query_cache.UpdateCounters(); @@ -634,7 +630,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { if (invalidated) { // When the stream buffer has been invalidated, we have to consider vertex buffers as dirty - auto& dirty = gpu.dirty.flags; + auto& dirty = maxwell3d.dirty.flags; dirty[Dirty::VertexBuffers] = true; for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) { dirty[index] = true; @@ -655,7 +651,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { // Setup emulation uniform buffer. if (!device.UseAssemblyShaders()) { MaxwellUniformData ubo; - ubo.SetFromRegs(gpu); + ubo.SetFromRegs(maxwell3d); const auto info = buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment()); glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset, @@ -664,7 +660,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { // Setup shaders and their used resources. texture_cache.GuardSamplers(true); - const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology); + const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology); SetupShaders(primitive_mode); texture_cache.GuardSamplers(false); @@ -681,14 +677,14 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { 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 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* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset); - const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format); + const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format); if (num_instances == 1 && base_instance == 0 && base_vertex == 0) { glDrawElements(primitive_mode, num_vertices, format, offset); } else if (num_instances == 1 && base_instance == 0) { @@ -707,8 +703,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) { @@ -723,7 +719,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { ++num_queued_commands; - system.GPU().TickWork(); + gpu.TickWork(); } void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { @@ -731,6 +727,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { current_cbuf = 0; auto kernel = shader_cache.GetComputeKernel(code_addr); + program_manager.BindCompute(kernel->GetHandle()); + SetupComputeTextures(kernel); SetupComputeImages(kernel); @@ -744,7 +742,7 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { buffer_cache.Unmap(); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + const auto& launch_desc = kepler_compute.launch_description; program_manager.BindCompute(kernel->GetHandle()); glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); ++num_queued_commands; @@ -807,17 +805,14 @@ void RasterizerOpenGL::SyncGuestHost() { } 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; @@ -826,7 +821,6 @@ void RasterizerOpenGL::SignalSyncPoint(u32 value) { } void RasterizerOpenGL::ReleaseFences() { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { return; } @@ -912,7 +906,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV}; MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& stages = system.GPU().Maxwell3D().state.shader_stages; + const auto& stages = maxwell3d.state.shader_stages; const auto& shader_stage = stages[stage_index]; const auto& entries = shader->GetEntries(); const bool use_unified = entries.use_unified_uniforms; @@ -937,7 +931,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) { MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + const auto& launch_desc = kepler_compute.launch_description; const auto& entries = kernel->GetEntries(); const bool use_unified = entries.use_unified_uniforms; @@ -1005,45 +999,66 @@ void RasterizerOpenGL::SetupConstBuffer(GLenum stage, u32 binding, } void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader) { - auto& gpu{system.GPU()}; - auto& memory_manager{gpu.MemoryManager()}; - const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]}; + 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, + }; + + const auto& cbufs{maxwell3d.state.shader_stages[stage_index]}; + const auto& entries{shader->GetEntries().global_memory_entries}; - u32 binding = - device.UseAssemblyShaders() ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer; - for (const auto& entry : 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); + const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)}; + const u32 size{gpu_memory.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& cbufs{kepler_compute.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 : kernel->GetEntries().global_memory_entries) { - const auto addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset}; - const auto gpu_addr{memory_manager.Read<u64>(addr)}; - const auto size{memory_manager.Read<u32>(addr + 8)}; - SetupGlobalMemory(binding++, entry, gpu_addr, size); + for (const auto& entry : entries) { + const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset}; + const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)}; + const u32 size{gpu_memory.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()); } } void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, - GPUVAddr gpu_addr, std::size_t size) { - const auto alignment{device.GetShaderStorageBufferAlignment()}; + 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); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, info.handle, info.offset, - static_cast<GLsizeiptr>(size)); + if (device.UseAssemblyShaders()) { + *pointer = info.address + info.offset; + } else { + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, info.handle, info.offset, + static_cast<GLsizeiptr>(size)); + } } 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; for (const auto& entry : shader->GetEntries().samplers) { const auto shader_type = static_cast<ShaderType>(stage_index); @@ -1056,11 +1071,10 @@ void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) { MICROPROFILE_SCOPE(OpenGL_Texture); - const auto& compute = system.GPU().KeplerCompute(); u32 binding = 0; 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); + const auto texture = GetTextureInfo(kepler_compute, entry, ShaderType::Compute, i); SetupTexture(binding++, texture, entry); } } @@ -1084,20 +1098,18 @@ void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextu } void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) { - const auto& maxwell3d = system.GPU().Maxwell3D(); u32 binding = device.GetBaseBindings(stage_index).image; for (const auto& entry : shader->GetEntries().images) { - const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index); + const auto shader_type = static_cast<ShaderType>(stage_index); const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic; SetupImage(binding++, tic, entry); } } void RasterizerOpenGL::SetupComputeImages(Shader* shader) { - const auto& compute = system.GPU().KeplerCompute(); u32 binding = 0; for (const auto& entry : shader->GetEntries().images) { - const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic; + const auto tic = GetTextureInfo(kepler_compute, entry, ShaderType::Compute).tic; SetupImage(binding++, tic, entry); } } @@ -1117,9 +1129,8 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t } 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]; @@ -1191,25 +1202,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; } @@ -1225,9 +1234,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; @@ -1242,26 +1250,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); @@ -1279,14 +1285,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), @@ -1311,25 +1316,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); @@ -1342,27 +1346,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; } @@ -1371,7 +1374,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; @@ -1396,33 +1399,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; @@ -1479,14 +1479,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)); @@ -1496,14 +1495,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; @@ -1522,16 +1520,15 @@ 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); + oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable); - if (gpu.regs.vp_point_size.enable) { + if (maxwell3d.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; @@ -1539,32 +1536,30 @@ void RasterizerOpenGL::SyncPointState() { // 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)); + glPointSize(std::max(1.0f, maxwell3d.regs.point_size)); glDisable(GL_PROGRAM_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); @@ -1578,14 +1573,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; + const auto& regs = maxwell3d.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"); } @@ -1599,20 +1593,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; @@ -1664,7 +1657,7 @@ 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; } @@ -1707,7 +1700,7 @@ 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; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 4f082592f..f451404b2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -33,10 +33,11 @@ #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 { @@ -54,9 +55,10 @@ struct DrawParameters; 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; @@ -82,15 +84,22 @@ public: 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 { return num_queued_commands > 0; } + VideoCommon::Shader::AsyncShaders& GetAsyncShaders() { + return async_shaders; + } + + const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const { + return async_shaders; + } + private: /// Configures the color and depth framebuffer states. void ConfigureFramebuffers(); @@ -115,9 +124,9 @@ private: /// Configures the current global memory entries to use for the kernel invocation. void SetupComputeGlobalMemory(Shader* kernel); - /// Configures a constant buffer. + /// Configures a global memory buffer. void SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, GPUVAddr gpu_addr, - std::size_t size); + std::size_t size, GLuint64EXT* pointer); /// Configures the current textures to use for the draw command. void SetupDrawTextures(std::size_t stage_index, Shader* shader); @@ -228,7 +237,15 @@ private: 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; ShaderCacheOpenGL shader_cache; @@ -238,10 +255,7 @@ private: 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; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index a787e27d2..0ebcec427 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <string_view> #include <utility> #include <glad/glad.h> #include "common/common_types.h" @@ -82,11 +83,13 @@ void OGLSampler::Release() { handle = 0; } -void OGLShader::Create(const char* source, GLenum type) { - if (handle != 0) +void OGLShader::Create(std::string_view source, GLenum type) { + if (handle != 0) { return; - if (source == nullptr) + } + if (source.empty()) { return; + } MICROPROFILE_SCOPE(OpenGL_ResourceCreation); handle = GLShader::LoadShader(source, type); diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index f8b322227..f48398669 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -4,6 +4,7 @@ #pragma once +#include <string_view> #include <utility> #include <glad/glad.h> #include "common/common_types.h" @@ -127,7 +128,7 @@ public: return *this; } - void Create(const char* source, GLenum type); + void Create(std::string_view source, GLenum type); void Release(); @@ -177,6 +178,12 @@ public: Release(); } + OGLAssemblyProgram& operator=(OGLAssemblyProgram&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); + return *this; + } + /// Deletes the internal OpenGL resource void Release(); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index c6a3bf3a1..bd56bed0c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -22,6 +22,7 @@ #include "video_core/memory_manager.h" #include "video_core/renderer_opengl/gl_arb_decompiler.h" #include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_disk_cache.h" @@ -31,6 +32,7 @@ #include "video_core/shader/registry.h" #include "video_core/shader/shader_ir.h" #include "video_core/shader_cache.h" +#include "video_core/shader_notify.h" namespace OpenGL { @@ -125,7 +127,7 @@ std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) { const VideoCore::GuestDriverProfile guest_profile{entry.texture_handler_size}; const VideoCommon::Shader::SerializedRegistryInfo info{guest_profile, entry.bound_buffer, entry.graphics_info, entry.compute_info}; - const auto registry = std::make_shared<Registry>(entry.type, info); + auto registry = std::make_shared<Registry>(entry.type, info); for (const auto& [address, value] : entry.keys) { const auto [buffer, offset] = address; registry->InsertKey(buffer, offset, value); @@ -140,9 +142,24 @@ std::shared_ptr<Registry> MakeRegistry(const ShaderDiskCacheEntry& entry) { return registry; } +std::unordered_set<GLenum> GetSupportedFormats() { + GLint num_formats; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); + + std::vector<GLint> formats(num_formats); + glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data()); + + std::unordered_set<GLenum> supported_formats; + for (const GLint format : formats) { + supported_formats.insert(static_cast<GLenum>(format)); + } + return supported_formats; +} + +} // Anonymous namespace + ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 unique_identifier, - const ShaderIR& ir, const Registry& registry, - bool hint_retrievable = false) { + const ShaderIR& ir, const Registry& registry, bool hint_retrievable) { const std::string shader_id = MakeShaderID(unique_identifier, shader_type); LOG_INFO(Render_OpenGL, "{}", shader_id); @@ -181,30 +198,17 @@ ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 u return program; } -std::unordered_set<GLenum> GetSupportedFormats() { - GLint num_formats; - glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); - - std::vector<GLint> formats(num_formats); - glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data()); - - std::unordered_set<GLenum> supported_formats; - for (const GLint format : formats) { - supported_formats.insert(static_cast<GLenum>(format)); - } - return supported_formats; -} - -} // Anonymous namespace - Shader::Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry_, ShaderEntries entries_, - ProgramSharedPtr program_) - : registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)} { + ProgramSharedPtr program_, bool is_built) + : registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)}, + is_built(is_built) { handle = program->assembly_program.handle; if (handle == 0) { handle = program->source_program.handle; } - ASSERT(handle != 0); + if (is_built) { + ASSERT(handle != 0); + } } Shader::~Shader() = default; @@ -214,43 +218,78 @@ GLuint Shader::GetHandle() const { return handle; } -std::unique_ptr<Shader> Shader::CreateStageFromMemory(const ShaderParameters& params, - Maxwell::ShaderProgram program_type, - ProgramCode code, ProgramCode code_b) { +bool Shader::IsBuilt() const { + return is_built; +} + +void Shader::AsyncOpenGLBuilt(OGLProgram new_program) { + program->source_program = std::move(new_program); + handle = program->source_program.handle; + is_built = true; +} + +void Shader::AsyncGLASMBuilt(OGLAssemblyProgram new_program) { + program->assembly_program = std::move(new_program); + handle = program->assembly_program.handle; + is_built = true; +} + +std::unique_ptr<Shader> Shader::CreateStageFromMemory( + const ShaderParameters& params, Maxwell::ShaderProgram program_type, ProgramCode code, + ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) { const auto shader_type = GetShaderType(program_type); - const std::size_t size_in_bytes = code.size() * sizeof(u64); - auto registry = std::make_shared<Registry>(shader_type, params.system.GPU().Maxwell3D()); - const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry); - // TODO(Rodrigo): Handle VertexA shaders - // std::optional<ShaderIR> ir_b; - // if (!code_b.empty()) { - // ir_b.emplace(code_b, STAGE_MAIN_OFFSET); - // } - auto program = BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry); + auto& gpu = params.gpu; + gpu.ShaderNotify().MarkSharderBuilding(); + + auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D()); + 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; + // if (!code_b.empty()) { + // ir_b.emplace(code_b, STAGE_MAIN_OFFSET); + // } + auto program = + BuildShader(params.device, shader_type, params.unique_identifier, ir, *registry); + ShaderDiskCacheEntry entry; + entry.type = shader_type; + entry.code = std::move(code); + entry.code_b = std::move(code_b); + entry.unique_identifier = params.unique_identifier; + entry.bound_buffer = registry->GetBoundBuffer(); + entry.graphics_info = registry->GetGraphicsInfo(); + entry.keys = registry->GetKeys(); + entry.bound_samplers = registry->GetBoundSamplers(); + entry.bindless_samplers = registry->GetBindlessSamplers(); + params.disk_cache.SaveEntry(std::move(entry)); + + gpu.ShaderNotify().MarkShaderComplete(); + + return std::unique_ptr<Shader>(new Shader(std::move(registry), + MakeEntries(params.device, ir, shader_type), + std::move(program), true)); + } else { + // Required for entries + const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry); + auto entries = MakeEntries(params.device, ir, shader_type); - ShaderDiskCacheEntry entry; - entry.type = shader_type; - entry.code = std::move(code); - entry.code_b = std::move(code_b); - entry.unique_identifier = params.unique_identifier; - entry.bound_buffer = registry->GetBoundBuffer(); - entry.graphics_info = registry->GetGraphicsInfo(); - entry.keys = registry->GetKeys(); - entry.bound_samplers = registry->GetBoundSamplers(); - entry.bindless_samplers = registry->GetBindlessSamplers(); - params.disk_cache.SaveEntry(std::move(entry)); + async_shaders.QueueOpenGLShader(params.device, shader_type, params.unique_identifier, + std::move(code), std::move(code_b), STAGE_MAIN_OFFSET, + COMPILER_SETTINGS, *registry, cpu_addr); - return std::unique_ptr<Shader>(new Shader( - std::move(registry), MakeEntries(params.device, ir, shader_type), std::move(program))); + auto program = std::make_shared<ProgramHandle>(); + return std::unique_ptr<Shader>( + new Shader(std::move(registry), std::move(entries), std::move(program), false)); + } } std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code) { - const std::size_t size_in_bytes = code.size() * sizeof(u64); + auto& gpu = params.gpu; + gpu.ShaderNotify().MarkSharderBuilding(); - auto& engine = params.system.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); @@ -266,6 +305,8 @@ std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& p entry.bindless_samplers = registry->GetBindlessSamplers(); params.disk_cache.SaveEntry(std::move(entry)); + gpu.ShaderNotify().MarkShaderComplete(); + return std::unique_ptr<Shader>(new Shader(std::move(registry), MakeEntries(params.device, ir, ShaderType::Compute), std::move(program))); @@ -277,15 +318,20 @@ 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_) + : VideoCommon::ShaderCache<Shader>{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; @@ -361,7 +407,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, } }; - const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1ULL)}; + const std::size_t num_workers{std::max(1U, std::thread::hardware_concurrency())}; const std::size_t bucket_size{transferable->size() / num_workers}; std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers); std::vector<std::thread> threads(num_workers); @@ -436,42 +482,77 @@ ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram( return program; } -Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { - if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) { - return last_shaders[static_cast<std::size_t>(program)]; +Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program, + VideoCommon::Shader::AsyncShaders& async_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); + gpu.ShaderNotify().MarkShaderComplete(); + if (shader == nullptr) { + continue; + } + using namespace VideoCommon::Shader; + if (work.backend == AsyncShaders::Backend::OpenGL) { + shader->AsyncOpenGLBuilt(std::move(work.program.opengl)); + } else if (work.backend == AsyncShaders::Backend::GLASM) { + 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; + entry.bound_buffer = registry.GetBoundBuffer(); + entry.graphics_info = registry.GetGraphicsInfo(); + entry.keys = registry.GetKeys(); + entry.bound_samplers = registry.GetBoundSamplers(); + entry.bindless_samplers = registry.GetBindlessSamplers(); + disk_cache.SaveEntry(std::move(entry)); + } + } // 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); if (found == runtime_cache.end()) { - shader = Shader::CreateStageFromMemory(params, program, std::move(code), std::move(code_b)); + shader = Shader::CreateStageFromMemory(params, program, std::move(code), std::move(code_b), + async_shaders, cpu_addr.value_or(0)); } else { shader = Shader::CreateFromCache(params, found->second); } @@ -487,21 +568,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 994aaeaf2..1708af06a 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -25,14 +25,18 @@ #include "video_core/shader/shader_ir.h" #include "video_core/shader_cache.h" -namespace Core { -class System; +namespace Tegra { +class MemoryManager; } namespace Core::Frontend { class EmuWindow; } +namespace VideoCommon::Shader { +class AsyncShaders; +} + namespace OpenGL { class Device; @@ -53,14 +57,20 @@ 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; }; +ProgramSharedPtr BuildShader(const Device& device, Tegra::Engines::ShaderType shader_type, + u64 unique_identifier, const VideoCommon::Shader::ShaderIR& ir, + const VideoCommon::Shader::Registry& registry, + bool hint_retrievable = false); + class Shader final { public: ~Shader(); @@ -68,15 +78,28 @@ public: /// Gets the GL program handle for the shader GLuint GetHandle() const; + bool IsBuilt() const; + /// Gets the shader entries for the shader const ShaderEntries& GetEntries() const { return entries; } - static std::unique_ptr<Shader> CreateStageFromMemory(const ShaderParameters& params, - Maxwell::ShaderProgram program_type, - ProgramCode program_code, - ProgramCode program_code_b); + const VideoCommon::Shader::Registry& GetRegistry() const { + return *registry; + } + + /// Mark a OpenGL shader as built + void AsyncOpenGLBuilt(OGLProgram new_program); + + /// Mark a GLASM shader as built + void AsyncGLASMBuilt(OGLAssemblyProgram new_program); + + static std::unique_ptr<Shader> CreateStageFromMemory( + const ShaderParameters& params, Maxwell::ShaderProgram program_type, + ProgramCode program_code, ProgramCode program_code_b, + VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr); + static std::unique_ptr<Shader> CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code); @@ -85,26 +108,30 @@ public: private: explicit Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry, ShaderEntries entries, - ProgramSharedPtr program); + ProgramSharedPtr program, bool is_built = true); std::shared_ptr<VideoCommon::Shader::Registry> registry; ShaderEntries entries; ProgramSharedPtr program; GLuint handle = 0; + bool is_built{}; }; 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 - Shader* GetStageProgram(Maxwell::ShaderProgram program); + Shader* GetStageProgram(Maxwell::ShaderProgram program, + VideoCommon::Shader::AsyncShaders& async_shaders); /// Gets a compute kernel in the passed address Shader* GetComputeKernel(GPUVAddr code_addr); @@ -114,9 +141,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 2c49aeaac..bbb8fb095 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -602,8 +602,15 @@ private: return; } const auto& info = registry.GetComputeInfo(); - if (const u32 size = info.shared_memory_size_in_words; size > 0) { - code.AddLine("shared uint smem[{}];", size); + if (u32 size = info.shared_memory_size_in_words * 4; size > 0) { + const u32 limit = device.GetMaxComputeSharedMemorySize(); + if (size > limit) { + LOG_ERROR(Render_OpenGL, "Shared memory size {} is clamped to host's limit {}", + size, limit); + size = limit; + } + + code.AddLine("shared uint smem[{}];", size / 4); code.AddNewLine(); } code.AddLine("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", @@ -806,7 +813,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; } @@ -1288,21 +1295,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: @@ -1325,7 +1332,7 @@ private: Type::Float}}; } UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); - return {}; + return std::nullopt; } } @@ -1436,8 +1443,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) { @@ -1912,7 +1921,7 @@ private: Expression Comparison(Operation operation) { static_assert(!unordered || type == Type::Float); - const Expression expr = GenerateBinaryInfix(operation, op, Type::Bool, type, type); + Expression expr = GenerateBinaryInfix(operation, op, Type::Bool, type, type); if constexpr (op.compare("!=") == 0 && type == Type::Float && !unordered) { // GLSL's operator!=(float, float) doesn't seem be ordered. This happens on both AMD's @@ -1952,10 +1961,6 @@ private: return {fmt::format("({} != 0)", carry), Type::Bool}; } - Expression LogicalFIsNan(Operation operation) { - return GenerateUnary(operation, "isnan", Type::Bool, Type::Float); - } - Expression LogicalAssign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; @@ -2771,15 +2776,6 @@ private: return std::min<u32>(device.GetMaxVaryings(), Maxwell::NumVaryings); } - bool IsRenderTargetEnabled(u32 render_target) const { - for (u32 component = 0; component < 4; ++component) { - if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { - return true; - } - } - return false; - } - const Device& device; const ShaderIR& ir; const Registry& registry; 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 2dcc2b0eb..166ee34e1 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -73,7 +73,7 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default; ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; -bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) { +bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { return false; } @@ -144,7 +144,7 @@ bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) { return true; } -bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const { +bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { if (file.WriteObject(static_cast<u32>(type)) != 1 || file.WriteObject(static_cast<u32>(code.size())) != 1 || file.WriteObject(static_cast<u32>(code_b.size())) != 1) { @@ -206,28 +206,32 @@ bool ShaderDiskCacheEntry::Save(FileUtil::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 {}; + return std::nullopt; } - FileUtil::IOFile file(GetTransferablePath(), "rb"); + Common::FS::IOFile file(GetTransferablePath(), "rb"); if (!file.IsOpen()) { LOG_INFO(Render_OpenGL, "No transferable shader cache found"); is_usable = true; - return {}; + return std::nullopt; } u32 version{}; if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); - return {}; + return std::nullopt; } if (version < NativeVersion) { @@ -235,12 +239,12 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran file.Close(); InvalidateTransferable(); is_usable = true; - return {}; + return std::nullopt; } if (version > NativeVersion) { LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version " "of the emulator, skipping"); - return {}; + return std::nullopt; } // Version is valid, load the shaders @@ -249,7 +253,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran ShaderDiskCacheEntry& entry = entries.emplace_back(); if (!entry.Load(file)) { LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); - return {}; + return std::nullopt; } } @@ -262,7 +266,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled() return {}; } - FileUtil::IOFile file(GetPrecompiledPath(), "rb"); + Common::FS::IOFile file(GetPrecompiledPath(), "rb"); if (!file.IsOpen()) { LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); return {}; @@ -279,7 +283,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled() } std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile( - FileUtil::IOFile& file) { + Common::FS::IOFile& file) { // Read compressed file from disk and decompress to virtual precompiled cache file std::vector<u8> compressed(file.GetSize()); file.ReadBytes(compressed.data(), compressed.size()); @@ -290,12 +294,12 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo ShaderCacheVersionHash file_hash{}; if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) { precompiled_cache_virtual_file_offset = 0; - return {}; + return std::nullopt; } if (GetShaderCacheVersionHash() != file_hash) { LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator"); precompiled_cache_virtual_file_offset = 0; - return {}; + return std::nullopt; } std::vector<ShaderDiskCachePrecompiled> entries; @@ -305,19 +309,20 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo if (!LoadObjectFromPrecompiled(entry.unique_identifier) || !LoadObjectFromPrecompiled(entry.binary_format) || !LoadObjectFromPrecompiled(binary_size)) { - return {}; + return std::nullopt; } entry.binary.resize(binary_size); if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) { - return {}; + return std::nullopt; } } - return entries; + + return std::move(entries); } void ShaderDiskCacheOpenGL::InvalidateTransferable() { - if (!FileUtil::Delete(GetTransferablePath())) { + if (!Common::FS::Delete(GetTransferablePath())) { LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", GetTransferablePath()); } @@ -328,7 +333,7 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() { // Clear virtaul precompiled cache file precompiled_cache_virtual_file.Resize(0); - if (!FileUtil::Delete(GetPrecompiledPath())) { + if (!Common::FS::Delete(GetPrecompiledPath())) { LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); } } @@ -344,7 +349,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) { return; } - FileUtil::IOFile file = AppendTransferableFile(); + Common::FS::IOFile file = AppendTransferableFile(); if (!file.IsOpen()) { return; } @@ -386,15 +391,15 @@ void ShaderDiskCacheOpenGL::SavePrecompiled(u64 unique_identifier, GLuint progra } } -FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { +Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { if (!EnsureDirectories()) { return {}; } const auto transferable_path{GetTransferablePath()}; - const bool existed = FileUtil::Exists(transferable_path); + const bool existed = Common::FS::Exists(transferable_path); - FileUtil::IOFile file(transferable_path, "ab"); + Common::FS::IOFile file(transferable_path, "ab"); if (!file.IsOpen()) { LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); return {}; @@ -426,7 +431,7 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() { Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); const auto precompiled_path{GetPrecompiledPath()}; - FileUtil::IOFile file(precompiled_path, "wb"); + Common::FS::IOFile file(precompiled_path, "wb"); if (!file.IsOpen()) { LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); @@ -440,24 +445,24 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() { bool ShaderDiskCacheOpenGL::EnsureDirectories() const { const auto CreateDir = [](const std::string& dir) { - if (!FileUtil::CreateDir(dir)) { + if (!Common::FS::CreateDir(dir)) { LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); return false; } return true; }; - return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) && + return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) && CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && CreateDir(GetPrecompiledDir()); } std::string ShaderDiskCacheOpenGL::GetTransferablePath() const { - return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); + return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); } std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { - return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); + return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); } std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { @@ -469,11 +474,11 @@ std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const { } std::string ShaderDiskCacheOpenGL::GetBaseDir() const { - return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl"; + return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl"; } 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 a79cef0e9..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,11 +21,7 @@ #include "video_core/engines/shader_type.h" #include "video_core/shader/registry.h" -namespace Core { -class System; -} - -namespace FileUtil { +namespace Common::FS { class IOFile; } @@ -38,9 +34,9 @@ struct ShaderDiskCacheEntry { ShaderDiskCacheEntry(); ~ShaderDiskCacheEntry(); - bool Load(FileUtil::IOFile& file); + bool Load(Common::FS::IOFile& file); - bool Save(FileUtil::IOFile& file) const; + bool Save(Common::FS::IOFile& file) const; bool HasProgramA() const { return !code.empty() && !code_b.empty(); @@ -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(); @@ -97,10 +96,10 @@ public: private: /// Loads the transferable cache. Returns empty on failure. std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile( - FileUtil::IOFile& file); + Common::FS::IOFile& file); /// Opens current game's transferable file and write it's header if it doesn't exist - FileUtil::IOFile AppendTransferableFile() const; + Common::FS::IOFile AppendTransferableFile() const; /// Save precompiled header to precompiled_cache_in_memory void SavePrecompiledHeaderToVirtualPrecompiledCache(); @@ -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 8e754fa90..691c6c79b 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -11,8 +11,30 @@ namespace OpenGL { -ProgramManager::ProgramManager(const Device& device) { - use_assembly_programs = device.UseAssemblyShaders(); +namespace { + +void BindProgram(GLenum stage, GLuint current, GLuint old, bool& enabled) { + if (current == old) { + return; + } + if (current == 0) { + if (enabled) { + enabled = false; + glDisable(stage); + } + return; + } + if (!enabled) { + enabled = true; + glEnable(stage); + } + glBindProgramARB(stage, current); +} + +} // Anonymous namespace + +ProgramManager::ProgramManager(const Device& device) + : use_assembly_programs{device.UseAssemblyShaders()} { if (use_assembly_programs) { glEnable(GL_COMPUTE_PROGRAM_NV); } else { @@ -33,9 +55,7 @@ void ProgramManager::BindCompute(GLuint program) { } void ProgramManager::BindGraphicsPipeline() { - if (use_assembly_programs) { - UpdateAssemblyPrograms(); - } else { + if (!use_assembly_programs) { UpdateSourcePrograms(); } } @@ -63,32 +83,25 @@ void ProgramManager::RestoreGuestPipeline() { } } -void ProgramManager::UpdateAssemblyPrograms() { - const auto update_state = [](GLenum stage, bool& enabled, GLuint current, GLuint old) { - if (current == old) { - return; - } - if (current == 0) { - if (enabled) { - enabled = false; - glDisable(stage); - } - return; - } - if (!enabled) { - enabled = true; - glEnable(stage); - } - glBindProgramARB(stage, current); - }; +void ProgramManager::UseVertexShader(GLuint program) { + if (use_assembly_programs) { + BindProgram(GL_VERTEX_PROGRAM_NV, program, current_state.vertex, vertex_enabled); + } + current_state.vertex = program; +} - update_state(GL_VERTEX_PROGRAM_NV, vertex_enabled, current_state.vertex, old_state.vertex); - update_state(GL_GEOMETRY_PROGRAM_NV, geometry_enabled, current_state.geometry, - old_state.geometry); - update_state(GL_FRAGMENT_PROGRAM_NV, fragment_enabled, current_state.fragment, - old_state.fragment); +void ProgramManager::UseGeometryShader(GLuint program) { + if (use_assembly_programs) { + BindProgram(GL_GEOMETRY_PROGRAM_NV, program, current_state.vertex, geometry_enabled); + } + current_state.geometry = program; +} - old_state = current_state; +void ProgramManager::UseFragmentShader(GLuint program) { + if (use_assembly_programs) { + BindProgram(GL_FRAGMENT_PROGRAM_NV, program, current_state.vertex, fragment_enabled); + } + current_state.fragment = program; } void ProgramManager::UpdateSourcePrograms() { diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 0f03b4f12..950e0dfcb 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -45,17 +45,9 @@ public: /// Rewinds BindHostPipeline state changes. void RestoreGuestPipeline(); - void UseVertexShader(GLuint program) { - current_state.vertex = program; - } - - void UseGeometryShader(GLuint program) { - current_state.geometry = program; - } - - void UseFragmentShader(GLuint program) { - current_state.fragment = program; - } + void UseVertexShader(GLuint program); + void UseGeometryShader(GLuint program); + void UseFragmentShader(GLuint program); private: struct PipelineState { @@ -64,9 +56,6 @@ private: GLuint fragment = 0; }; - /// Update NV_gpu_program5 programs. - void UpdateAssemblyPrograms(); - /// Update GLSL programs. void UpdateSourcePrograms(); diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 9e74eda0d..4bf0d6090 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <string_view> #include <vector> #include <glad/glad.h> #include "common/assert.h" @@ -11,7 +12,8 @@ namespace OpenGL::GLShader { namespace { -const char* GetStageDebugName(GLenum type) { + +std::string_view StageDebugName(GLenum type) { switch (type) { case GL_VERTEX_SHADER: return "vertex"; @@ -25,12 +27,17 @@ const char* GetStageDebugName(GLenum type) { UNIMPLEMENTED(); return "unknown"; } + } // Anonymous namespace -GLuint LoadShader(const char* source, GLenum type) { - const char* debug_type = GetStageDebugName(type); +GLuint LoadShader(std::string_view source, GLenum type) { + const std::string_view debug_type = StageDebugName(type); const GLuint shader_id = glCreateShader(type); - glShaderSource(shader_id, 1, &source, nullptr); + + const GLchar* source_string = source.data(); + const GLint source_length = static_cast<GLint>(source.size()); + + glShaderSource(shader_id, 1, &source_string, &source_length); LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type); glCompileShader(shader_id); diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index 03b7548c2..1b770532e 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -38,7 +38,7 @@ void LogShaderSource(T... shaders) { * @param source String of the GLSL shader program * @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER) */ -GLuint LoadShader(const char* source, GLenum type); +GLuint LoadShader(std::string_view source, GLenum type); /** * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader) diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp index d24fad3de..6bcf831f2 100644 --- a/src/video_core/renderer_opengl/gl_state_tracker.cpp +++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp @@ -214,10 +214,8 @@ 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); SetupDirtyColorMasks(tables); diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h index 0f823288e..9d127548f 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 { @@ -90,9 +90,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) { @@ -103,7 +101,6 @@ public: } 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; @@ -117,98 +114,81 @@ 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; flags[OpenGL::Dirty::ColorMasks] = true; flags[OpenGL::Dirty::ColorMask0] = 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 index_buffer = 0; }; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index 3655ff629..887995cf4 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -35,7 +35,7 @@ OGLStreamBuffer::OGLStreamBuffer(const Device& device, GLsizeiptr size, bool ver mapped_ptr = static_cast<u8*>( glMapNamedBufferRange(gl_buffer.handle, 0, buffer_size, flags | GL_MAP_FLUSH_EXPLICIT_BIT)); - if (device.HasVertexBufferUnifiedMemory()) { + if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) { glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_ONLY); glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address); } diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 61505879b..a863ef218 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -41,91 +41,103 @@ struct FormatTuple { }; constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{ - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // ABGR8U - {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // ABGR8S - {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, // ABGR8UI - {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, // B5G6R5U - {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10U - {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5U - {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8U - {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, // R8UI - {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16F - {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // RGBA16U - {GL_RGBA16_SNORM, GL_RGBA, GL_SHORT}, // RGBA16S - {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, // RGBA16UI - {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, // R11FG11FB10F - {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, // RGBA32UI - {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}, // DXT1 - {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}, // DXT23 - {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}, // DXT45 - {GL_COMPRESSED_RED_RGTC1}, // DXN1 - {GL_COMPRESSED_RG_RGTC2}, // DXN2UNORM - {GL_COMPRESSED_SIGNED_RG_RGTC2}, // DXN2SNORM - {GL_COMPRESSED_RGBA_BPTC_UNORM}, // BC7U - {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UF16 - {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SF16 - {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4 - {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8 - {GL_RGBA32F, GL_RGBA, GL_FLOAT}, // RGBA32F - {GL_RG32F, GL_RG, GL_FLOAT}, // RG32F - {GL_R32F, GL_RED, GL_FLOAT}, // R32F - {GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F - {GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16U - {GL_R16_SNORM, GL_RED, GL_SHORT}, // R16S - {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16UI - {GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I - {GL_RG16, GL_RG, GL_UNSIGNED_SHORT}, // RG16 - {GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F - {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, // RG16UI - {GL_RG16I, GL_RG_INTEGER, GL_SHORT}, // RG16I - {GL_RG16_SNORM, GL_RG, GL_SHORT}, // RG16S - {GL_RGB32F, GL_RGB, GL_FLOAT}, // RGB32F - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // RGBA8_SRGB - {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // RG8U - {GL_RG8_SNORM, GL_RG, GL_BYTE}, // RG8S - {GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // RG8UI - {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // RG32UI - {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT}, // RGBX16F - {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32UI - {GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I - {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8 - {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5 - {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4 - {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8 + {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 + {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, // A8B8G8R8_UINT + {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // R5G6B5_UNORM + {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, // B5G6R5_UNORM + {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1R5G5B5_UNORM + {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM + {GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM + {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM + {GL_R8_SNORM, GL_RED, GL_BYTE}, // R8_SNORM + {GL_R8I, GL_RED_INTEGER, GL_BYTE}, // R8_SINT + {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, // R8_UINT + {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16A16_FLOAT + {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // R16G16B16A16_UNORM + {GL_RGBA16_SNORM, GL_RGBA, GL_SHORT}, // R16G16B16A16_SNORM + {GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT}, // R16G16B16A16_SINT + {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, // R16G16B16A16_UINT + {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, // B10G11R11_FLOAT + {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, // R32G32B32A32_UINT + {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}, // BC1_RGBA_UNORM + {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}, // BC2_UNORM + {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}, // BC3_UNORM + {GL_COMPRESSED_RED_RGTC1}, // BC4_UNORM + {GL_COMPRESSED_SIGNED_RED_RGTC1}, // BC4_SNORM + {GL_COMPRESSED_RG_RGTC2}, // BC5_UNORM + {GL_COMPRESSED_SIGNED_RG_RGTC2}, // BC5_SNORM + {GL_COMPRESSED_RGBA_BPTC_UNORM}, // BC7_UNORM + {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT + {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT + {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM + {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM + {GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT + {GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT + {GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT + {GL_RG32I, GL_RG_INTEGER, GL_INT}, // R32G32_SINT + {GL_R32F, GL_RED, GL_FLOAT}, // R32_FLOAT + {GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16_FLOAT + {GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16_UNORM + {GL_R16_SNORM, GL_RED, GL_SHORT}, // R16_SNORM + {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16_UINT + {GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16_SINT + {GL_RG16, GL_RG, GL_UNSIGNED_SHORT}, // R16G16_UNORM + {GL_RG16F, GL_RG, GL_HALF_FLOAT}, // R16G16_FLOAT + {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, // R16G16_UINT + {GL_RG16I, GL_RG_INTEGER, GL_SHORT}, // R16G16_SINT + {GL_RG16_SNORM, GL_RG, GL_SHORT}, // R16G16_SNORM + {GL_RGB32F, GL_RGB, GL_FLOAT}, // R32G32B32_FLOAT + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_SRGB + {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // R8G8_UNORM + {GL_RG8_SNORM, GL_RG, GL_BYTE}, // R8G8_SNORM + {GL_RG8I, GL_RG_INTEGER, GL_BYTE}, // R8G8_SINT + {GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, // R8G8_UINT + {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // R32G32_UINT + {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16X16_FLOAT + {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32_UINT + {GL_R32I, GL_RED_INTEGER, GL_INT}, // R32_SINT + {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM + {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}, // DXT1_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // DXT23_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // DXT45_SRGB - {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7U_SRGB - {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // R4G4B4A4U + {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 + {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 + {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 + {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 + {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 + {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 + {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 + {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}, // E5B9G9R9F + {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}, // Z32F - {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // Z16 + {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}, // Z24S8 - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8Z24 - {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // Z32FS8 + {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 }}; const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { @@ -178,10 +190,10 @@ GLint GetSwizzleSource(SwizzleSource source) { GLenum GetComponent(PixelFormat format, bool is_first) { switch (format) { - case PixelFormat::Z24S8: - case PixelFormat::Z32FS8: + case PixelFormat::D24_UNORM_S8_UINT: + case PixelFormat::D32_FLOAT_S8_UINT: return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX; - case PixelFormat::S8Z24: + case PixelFormat::S8_UINT_D24_UNORM: return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT; default: UNREACHABLE(); @@ -391,7 +403,7 @@ void CachedSurface::DecorateSurfaceName() { LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName()); } -void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) { +void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) { LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix); } @@ -482,9 +494,9 @@ GLuint CachedSurfaceView::GetTexture(SwizzleSource x_source, SwizzleSource y_sou std::array swizzle{x_source, y_source, z_source, w_source}; switch (const PixelFormat format = GetSurfaceParams().pixel_format) { - case PixelFormat::Z24S8: - case PixelFormat::Z32FS8: - case PixelFormat::S8Z24: + 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)); @@ -520,10 +532,12 @@ OGLTextureView CachedSurfaceView::CreateTextureView() const { 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} { +TextureCacheOpenGL::TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, + Tegra::MemoryManager& gpu_memory, const Device& device, + StateTracker& state_tracker_) + : TextureCacheBase{rasterizer, maxwell3d, gpu_memory, device.HasASTC()}, state_tracker{ + state_tracker_} { src_framebuffer.Create(); dst_framebuffer.Create(); } diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index bfc4ddf5d..7787134fc 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -90,7 +90,7 @@ public: Tegra::Texture::SwizzleSource z_source, Tegra::Texture::SwizzleSource w_source); - void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); + void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix); void MarkAsModified(u64 tick) { surface.MarkAsModified(true, tick); @@ -129,8 +129,10 @@ private: class TextureCacheOpenGL final : public TextureCacheBase { public: - explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const Device& device, StateTracker& state_tracker); + explicit TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, + Tegra::MemoryManager& gpu_memory, const Device& device, + StateTracker& state_tracker); ~TextureCacheOpenGL(); protected: diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index fe9bd4b5a..a8be2aa37 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -47,6 +47,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 +72,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 +88,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; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index e66cdc083..2ccca1993 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -21,6 +21,8 @@ #include "core/perf_stats.h" #include "core/settings.h" #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" @@ -30,60 +32,6 @@ 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 char VERTEX_SHADER[] = R"( -#version 430 core - -out gl_PerVertex { - vec4 gl_Position; -}; - -layout (location = 0) in vec2 vert_position; -layout (location = 1) in vec2 vert_tex_coord; -layout (location = 0) out vec2 frag_tex_coord; - -// This is a truncated 3x3 matrix for 2D transformations: -// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. -// The third column performs translation. -// The third row could be used for projection, which we don't need in 2D. It hence is assumed to -// implicitly be [0, 0, 1] -layout (location = 0) uniform mat3x2 modelview_matrix; - -void main() { - // Multiply input position by the rotscale part of the matrix and then manually translate by - // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector - // to `vec3(vert_position.xy, 1.0)` - gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); - frag_tex_coord = vert_tex_coord; -} -)"; - -constexpr char FRAGMENT_SHADER[] = R"( -#version 430 core - -layout (location = 0) in vec2 frag_tex_coord; -layout (location = 0) out vec4 color; - -layout (binding = 0) uniform sampler2D color_texture; - -void main() { - color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f); -} -)"; - constexpr GLint PositionLocation = 0; constexpr GLint TexCoordLocation = 1; constexpr GLint ModelViewMatrixLocation = 0; @@ -96,24 +44,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. @@ -197,132 +127,15 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit } // 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::Frontend::EmuWindow& emu_window, Core::System& system, - Core::Frontend::GraphicsContext& context) - : RendererBase{emu_window}, emu_window{emu_window}, system{system}, context{context}, - program_manager{device}, has_debug_tool{HasDebugTool()} {} +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)}, telemetry_session{telemetry_session_}, + emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device} {} 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; @@ -331,79 +144,34 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { PrepareRendertarget(framebuffer); RenderScreenshot(); - Frame* frame; - { - MICROPROFILE_SCOPE(OpenGL_WaitPresent); - - frame = frame_mailbox->GetRenderFrame(); - - // Clean up sync objects before drawing + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + DrawScreen(emu_window.GetFramebufferLayout()); - // 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; - } - - // 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; - } - } + ++m_current_frame; - { - 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(); - } + rasterizer->TickFrame(); render_window.PollEvents(); - if (has_debug_tool) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - Present(0); - context.SwapBuffers(); - } + context->SwapBuffers(); } 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) { @@ -423,7 +191,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf 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)}; + u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)}; rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes); // TODO(Rodrigo): Read this from HLE @@ -453,17 +221,15 @@ 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); // Create shader programs OGLShader vertex_shader; - vertex_shader.Create(VERTEX_SHADER, GL_VERTEX_SHADER); + vertex_shader.Create(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER); OGLShader fragment_shader; - fragment_shader.Create(FRAGMENT_SHADER, GL_FRAGMENT_SHADER); + fragment_shader.Create(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER); vertex_program.Create(true, false, vertex_shader.handle); fragment_program.Create(true, false, fragment_shader.handle); @@ -508,18 +274,18 @@ void RendererOpenGL::AddTelemetryFields() { LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor); LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); - auto& telemetry_session = system.TelemetrySession(); - telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor); - telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model); - telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version); + 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); + rasterizer = std::make_unique<RasterizerOpenGL>(emu_window, gpu, cpu_memory, device, + screen_info, program_manager, state_tracker); } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, @@ -535,12 +301,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, GLint internal_format; switch (framebuffer.pixel_format) { - case Tegra::FramebufferConfig::PixelFormat::ABGR8: + case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM: internal_format = GL_RGBA8; texture.gl_format = GL_RGBA; texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; break; - case Tegra::FramebufferConfig::PixelFormat::RGB565: + case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM: internal_format = GL_RGB565; texture.gl_format = GL_RGB; texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; @@ -682,51 +448,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; @@ -741,7 +462,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); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 8b18d32e6..9ef181f95 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -16,16 +16,25 @@ 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,24 +55,17 @@ 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::Frontend::EmuWindow& emu_window, Core::System& system, - 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; private: /// Initializes the OpenGL state and creates persistent objects. @@ -91,14 +93,13 @@ private: void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); - bool Present(int timeout_ms); - + Core::TelemetrySession& telemetry_session; Core::Frontend::EmuWindow& emu_window; - Core::System& system; - Core::Frontend::GraphicsContext& context; - const Device device; + Core::Memory::Memory& cpu_memory; + Tegra::GPU& gpu; - StateTracker state_tracker{system}; + const Device device; + StateTracker state_tracker{gpu}; // OpenGL object IDs OGLBuffer vertex_buffer; @@ -120,13 +121,8 @@ private: std::vector<u8> gl_framebuffer_data; /// Used for transforming the framebuffer orientation - Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; + 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_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index d1f0ea932..81a39a3b8 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -40,7 +40,6 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { } // Anonymous namespace void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { - const auto& clip = regs.view_volume_clip_control; const std::array enabled_lut = {regs.polygon_offset_point_enable, regs.polygon_offset_line_enable, regs.polygon_offset_fill_enable}; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index d7f1ae89f..d22de1d81 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -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={}", static_cast<u32>(wrap_mode)); + return {}; } - UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode)); - return {}; } VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) { @@ -117,90 +118,101 @@ struct FormatTuple { VkFormat format; ///< Vulkan format int usage = 0; ///< Describes image format usage } constexpr tex_format_tuples[] = { - {VK_FORMAT_A8B8G8R8_UNORM_PACK32, Attachable | Storage}, // ABGR8U - {VK_FORMAT_A8B8G8R8_SNORM_PACK32, Attachable | Storage}, // ABGR8S - {VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage}, // ABGR8UI - {VK_FORMAT_B5G6R5_UNORM_PACK16}, // B5G6R5U - {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10U - {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5U (flipped with swizzle) - {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8U - {VK_FORMAT_R8_UINT, Attachable | Storage}, // R8UI - {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // RGBA16F - {VK_FORMAT_R16G16B16A16_UNORM, Attachable | Storage}, // RGBA16U - {VK_FORMAT_R16G16B16A16_SNORM, Attachable | Storage}, // RGBA16S - {VK_FORMAT_R16G16B16A16_UINT, Attachable | Storage}, // RGBA16UI - {VK_FORMAT_B10G11R11_UFLOAT_PACK32, Attachable | Storage}, // R11FG11FB10F - {VK_FORMAT_R32G32B32A32_UINT, Attachable | Storage}, // RGBA32UI - {VK_FORMAT_BC1_RGBA_UNORM_BLOCK}, // DXT1 - {VK_FORMAT_BC2_UNORM_BLOCK}, // DXT23 - {VK_FORMAT_BC3_UNORM_BLOCK}, // DXT45 - {VK_FORMAT_BC4_UNORM_BLOCK}, // DXN1 - {VK_FORMAT_BC5_UNORM_BLOCK}, // DXN2UNORM - {VK_FORMAT_BC5_SNORM_BLOCK}, // DXN2SNORM - {VK_FORMAT_BC7_UNORM_BLOCK}, // BC7U - {VK_FORMAT_BC6H_UFLOAT_BLOCK}, // BC6H_UF16 - {VK_FORMAT_BC6H_SFLOAT_BLOCK}, // BC6H_SF16 - {VK_FORMAT_ASTC_4x4_UNORM_BLOCK}, // ASTC_2D_4X4 - {VK_FORMAT_B8G8R8A8_UNORM, Attachable}, // BGRA8 - {VK_FORMAT_R32G32B32A32_SFLOAT, Attachable | Storage}, // RGBA32F - {VK_FORMAT_R32G32_SFLOAT, Attachable | Storage}, // RG32F - {VK_FORMAT_R32_SFLOAT, Attachable | Storage}, // R32F - {VK_FORMAT_R16_SFLOAT, Attachable | Storage}, // R16F - {VK_FORMAT_R16_UNORM, Attachable | Storage}, // R16U - {VK_FORMAT_UNDEFINED}, // R16S - {VK_FORMAT_R16_UINT, Attachable | Storage}, // R16UI - {VK_FORMAT_UNDEFINED}, // R16I - {VK_FORMAT_R16G16_UNORM, Attachable | Storage}, // RG16 - {VK_FORMAT_R16G16_SFLOAT, Attachable | Storage}, // RG16F - {VK_FORMAT_UNDEFINED}, // RG16UI - {VK_FORMAT_UNDEFINED}, // RG16I - {VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // RG16S - {VK_FORMAT_UNDEFINED}, // RGB32F - {VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // RGBA8_SRGB - {VK_FORMAT_R8G8_UNORM, Attachable | Storage}, // RG8U - {VK_FORMAT_R8G8_SNORM, Attachable | Storage}, // RG8S - {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // RG8UI - {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // RG32UI - {VK_FORMAT_UNDEFINED}, // RGBX16F - {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32UI - {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32I - {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8 - {VK_FORMAT_UNDEFINED}, // ASTC_2D_8X5 - {VK_FORMAT_UNDEFINED}, // ASTC_2D_5X4 - {VK_FORMAT_B8G8R8A8_SRGB, Attachable}, // BGRA8_SRGB - {VK_FORMAT_BC1_RGBA_SRGB_BLOCK}, // DXT1_SRGB - {VK_FORMAT_BC2_SRGB_BLOCK}, // DXT23_SRGB - {VK_FORMAT_BC3_SRGB_BLOCK}, // DXT45_SRGB - {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7U_SRGB - {VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // R4G4B4A4U - {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB - {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB - {VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB - {VK_FORMAT_ASTC_5x4_SRGB_BLOCK}, // ASTC_2D_5X4_SRGB - {VK_FORMAT_ASTC_5x5_UNORM_BLOCK}, // ASTC_2D_5X5 - {VK_FORMAT_ASTC_5x5_SRGB_BLOCK}, // ASTC_2D_5X5_SRGB - {VK_FORMAT_ASTC_10x8_UNORM_BLOCK}, // ASTC_2D_10X8 - {VK_FORMAT_ASTC_10x8_SRGB_BLOCK}, // ASTC_2D_10X8_SRGB - {VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6 - {VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB - {VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10 - {VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB - {VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12 - {VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB - {VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6 - {VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB - {VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5 - {VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB - {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9F + {VK_FORMAT_A8B8G8R8_UNORM_PACK32, Attachable | Storage}, // A8B8G8R8_UNORM + {VK_FORMAT_A8B8G8R8_SNORM_PACK32, Attachable | Storage}, // A8B8G8R8_SNORM + {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_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM + {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM + {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT + {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) + {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM + {VK_FORMAT_R8_SNORM, Attachable | Storage}, // R8_SNORM + {VK_FORMAT_R8_SINT, Attachable | Storage}, // R8_SINT + {VK_FORMAT_R8_UINT, Attachable | Storage}, // R8_UINT + {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16A16_FLOAT + {VK_FORMAT_R16G16B16A16_UNORM, Attachable | Storage}, // R16G16B16A16_UNORM + {VK_FORMAT_R16G16B16A16_SNORM, Attachable | Storage}, // R16G16B16A16_SNORM + {VK_FORMAT_R16G16B16A16_SINT, Attachable | Storage}, // R16G16B16A16_SINT + {VK_FORMAT_R16G16B16A16_UINT, Attachable | Storage}, // R16G16B16A16_UINT + {VK_FORMAT_B10G11R11_UFLOAT_PACK32, Attachable | Storage}, // B10G11R11_FLOAT + {VK_FORMAT_R32G32B32A32_UINT, Attachable | Storage}, // R32G32B32A32_UINT + {VK_FORMAT_BC1_RGBA_UNORM_BLOCK}, // BC1_RGBA_UNORM + {VK_FORMAT_BC2_UNORM_BLOCK}, // BC2_UNORM + {VK_FORMAT_BC3_UNORM_BLOCK}, // BC3_UNORM + {VK_FORMAT_BC4_UNORM_BLOCK}, // BC4_UNORM + {VK_FORMAT_BC4_SNORM_BLOCK}, // BC4_SNORM + {VK_FORMAT_BC5_UNORM_BLOCK}, // BC5_UNORM + {VK_FORMAT_BC5_SNORM_BLOCK}, // BC5_SNORM + {VK_FORMAT_BC7_UNORM_BLOCK}, // BC7_UNORM + {VK_FORMAT_BC6H_UFLOAT_BLOCK}, // BC6H_UFLOAT + {VK_FORMAT_BC6H_SFLOAT_BLOCK}, // BC6H_SFLOAT + {VK_FORMAT_ASTC_4x4_UNORM_BLOCK}, // ASTC_2D_4X4_UNORM + {VK_FORMAT_B8G8R8A8_UNORM, Attachable}, // B8G8R8A8_UNORM + {VK_FORMAT_R32G32B32A32_SFLOAT, Attachable | Storage}, // R32G32B32A32_FLOAT + {VK_FORMAT_R32G32B32A32_SINT, Attachable | Storage}, // R32G32B32A32_SINT + {VK_FORMAT_R32G32_SFLOAT, Attachable | Storage}, // R32G32_FLOAT + {VK_FORMAT_R32G32_SINT, Attachable | Storage}, // R32G32_SINT + {VK_FORMAT_R32_SFLOAT, Attachable | Storage}, // R32_FLOAT + {VK_FORMAT_R16_SFLOAT, Attachable | Storage}, // R16_FLOAT + {VK_FORMAT_R16_UNORM, Attachable | Storage}, // R16_UNORM + {VK_FORMAT_UNDEFINED}, // R16_SNORM + {VK_FORMAT_R16_UINT, Attachable | Storage}, // R16_UINT + {VK_FORMAT_UNDEFINED}, // R16_SINT + {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_SNORM, Attachable | Storage}, // R16G16_SNORM + {VK_FORMAT_UNDEFINED}, // R32G32B32_FLOAT + {VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // A8B8G8R8_SRGB + {VK_FORMAT_R8G8_UNORM, Attachable | Storage}, // R8G8_UNORM + {VK_FORMAT_R8G8_SNORM, Attachable | Storage}, // R8G8_SNORM + {VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT + {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT + {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT + {VK_FORMAT_UNDEFINED}, // R16G16B16X16_FLOAT + {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT + {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT + {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM + {VK_FORMAT_UNDEFINED}, // ASTC_2D_8X5_UNORM + {VK_FORMAT_UNDEFINED}, // ASTC_2D_5X4_UNORM + {VK_FORMAT_B8G8R8A8_SRGB, Attachable}, // B8G8R8A8_SRGB + {VK_FORMAT_BC1_RGBA_SRGB_BLOCK}, // BC1_RGBA_SRGB + {VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB + {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB + {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB + {VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // A4B4G4R4_UNORM + {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB + {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB + {VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB + {VK_FORMAT_ASTC_5x4_SRGB_BLOCK}, // ASTC_2D_5X4_SRGB + {VK_FORMAT_ASTC_5x5_UNORM_BLOCK}, // ASTC_2D_5X5_UNORM + {VK_FORMAT_ASTC_5x5_SRGB_BLOCK}, // ASTC_2D_5X5_SRGB + {VK_FORMAT_ASTC_10x8_UNORM_BLOCK}, // ASTC_2D_10X8_UNORM + {VK_FORMAT_ASTC_10x8_SRGB_BLOCK}, // ASTC_2D_10X8_SRGB + {VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6_UNORM + {VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB + {VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10_UNORM + {VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB + {VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12_UNORM + {VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB + {VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6_UNORM + {VK_FORMAT_ASTC_8x6_SRGB_BLOCK}, // ASTC_2D_8X6_SRGB + {VK_FORMAT_ASTC_6x5_UNORM_BLOCK}, // ASTC_2D_6X5_UNORM + {VK_FORMAT_ASTC_6x5_SRGB_BLOCK}, // ASTC_2D_6X5_SRGB + {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT // Depth formats - {VK_FORMAT_D32_SFLOAT, Attachable}, // Z32F - {VK_FORMAT_D16_UNORM, Attachable}, // Z16 + {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT + {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM // DepthStencil formats - {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // Z24S8 - {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // S8Z24 (emulated) - {VK_FORMAT_D32_SFLOAT_S8_UINT, Attachable}, // Z32FS8 + {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // D24_UNORM_S8_UINT + {VK_FORMAT_D24_UNORM_S8_UINT, Attachable}, // S8_UINT_D24_UNORM (emulated) + {VK_FORMAT_D32_SFLOAT_S8_UINT, Attachable}, // D32_FLOAT_S8_UINT }; static_assert(std::size(tex_format_tuples) == VideoCore::Surface::MaxPixelFormat); @@ -221,7 +233,7 @@ FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFo return {VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true}; } - // Use ABGR8 on hardware that doesn't support ASTC natively + // 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 @@ -287,9 +299,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={}", static_cast<u32>(topology)); + return {}; } - UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology)); - return {}; } VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { @@ -314,6 +327,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: @@ -336,6 +351,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: @@ -358,6 +375,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: @@ -380,6 +399,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: @@ -410,6 +431,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: @@ -440,6 +463,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: @@ -460,6 +485,8 @@ 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; } diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp index 435c8c1b8..5b01020ec 100644 --- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp +++ b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp @@ -65,10 +65,10 @@ bool NsightAftermathTracker::Initialize() { return false; } - dump_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "gpucrash"; + dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash"; - (void)FileUtil::DeleteDirRecursively(dump_dir); - if (!FileUtil::CreateDir(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; } @@ -106,7 +106,7 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const { return; } - FileUtil::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); + Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); if (!file.IsOpen()) { LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); return; @@ -156,12 +156,12 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump, }(); std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); - if (FileUtil::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { + if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { LOG_ERROR(Render_Vulkan, "Failed to write dump file"); return; } const std::string_view json_view(json.data(), json.size()); - if (FileUtil::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { + if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { LOG_ERROR(Render_Vulkan, "Failed to write JSON"); return; } @@ -180,7 +180,7 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_ const std::string path = fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]); - FileUtil::IOFile file(path, "wb"); + Common::FS::IOFile file(path, "wb"); if (!file.IsOpen()) { LOG_ERROR(Render_Vulkan, "Failed to create file {}", path); return; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2258479f5..715182b3b 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -25,9 +25,9 @@ #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_master_semaphore.h" #include "video_core/renderer_vulkan/vk_memory_manager.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" @@ -56,7 +56,7 @@ VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* data, [[maybe_unused]] void* user_data) { - const char* message{data->pMessage}; + const char* const message{data->pMessage}; if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { LOG_CRITICAL(Render_Vulkan, "{}", message); @@ -78,7 +78,7 @@ Common::DynamicLibrary OpenVulkanLibrary() { if (!libvulkan_env || !library.Open(libvulkan_env)) { // Use the libvulkan.dylib from the application bundle. const std::string filename = - FileUtil::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; + Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; library.Open(filename.c_str()); } #else @@ -86,7 +86,7 @@ Common::DynamicLibrary OpenVulkanLibrary() { 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()); + (void)library.Open(filename.c_str()); } #endif return library; @@ -237,8 +237,12 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext } // Anonymous namespace -RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system) - : RendererBase(window), system{system} {} +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) + : RendererBase{emu_window, std::move(context)}, telemetry_session{telemetry_session_}, + cpu_memory{cpu_memory_}, gpu{gpu_} {} RendererVulkan::~RendererVulkan() { ShutDown(); @@ -265,11 +269,11 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { scheduler->WaitWorker(); swapchain->AcquireNextImage(); - const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated); + const VkSemaphore render_semaphore = blit_screen->Draw(*framebuffer, use_accelerated); - scheduler->Flush(false, render_semaphore); + scheduler->Flush(render_semaphore); - if (swapchain->Present(render_semaphore, fence)) { + if (swapchain->Present(render_semaphore)) { blit_screen->Recreate(); } @@ -279,11 +283,6 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { 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, @@ -296,23 +295,21 @@ bool RendererVulkan::Init() { memory_manager = std::make_unique<VKMemoryManager>(*device); - resource_manager = std::make_unique<VKResourceManager>(*device); + state_tracker = std::make_unique<StateTracker>(gpu); + + scheduler = std::make_unique<VKScheduler>(*device, *state_tracker); const auto& framebuffer = render_window.GetFramebufferLayout(); - swapchain = std::make_unique<VKSwapchain>(*surface, *device); + swapchain = std::make_unique<VKSwapchain>(*surface, *device, *scheduler); 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); + rasterizer = std::make_unique<RasterizerVulkan>(render_window, gpu, gpu.MemoryManager(), + cpu_memory, screen_info, *device, + *memory_manager, *state_tracker, *scheduler); - blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device, - *resource_manager, *memory_manager, *swapchain, - *scheduler, screen_info); + blit_screen = + std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device, + *memory_manager, *swapchain, *scheduler, screen_info); return true; } @@ -330,7 +327,6 @@ void RendererVulkan::ShutDown() { scheduler.reset(); swapchain.reset(); memory_manager.reset(); - resource_manager.reset(); device.reset(); } @@ -438,8 +434,7 @@ void RendererVulkan::Report() const { LOG_INFO(Render_Vulkan, "Device: {}", model_name); LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version); - auto& telemetry_session = system.TelemetrySession(); - constexpr auto field = 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); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 522b5bff8..49a4141ec 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -14,7 +14,15 @@ #include "video_core/renderer_vulkan/wrapper.h" namespace Core { -class System; +class TelemetrySession; +} + +namespace Core::Memory { +class Memory; +} + +namespace Tegra { +class GPU; } namespace Vulkan { @@ -22,9 +30,7 @@ namespace Vulkan { class StateTracker; class VKBlitScreen; class VKDevice; -class VKFence; class VKMemoryManager; -class VKResourceManager; class VKSwapchain; class VKScheduler; class VKImage; @@ -38,13 +44,15 @@ struct VKScreenInfo { class RendererVulkan final : public VideoCore::RendererBase { public: - explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); + 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(); @@ -57,7 +65,9 @@ private: void Report() const; - Core::System& system; + Core::TelemetrySession& telemetry_session; + Core::Memory::Memory& cpu_memory; + Tegra::GPU& gpu; Common::DynamicLibrary library; vk::InstanceDispatch dld; @@ -69,11 +79,10 @@ private: 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<VKSwapchain> swapchain; std::unique_ptr<VKBlitScreen> blit_screen; }; diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 866813465..b5b60309e 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -12,11 +12,9 @@ #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" @@ -24,8 +22,8 @@ #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_master_semaphore.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_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_swapchain.h" @@ -187,9 +185,9 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { switch (framebuffer.pixel_format) { - case Tegra::FramebufferConfig::PixelFormat::ABGR8: + case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM: return VK_FORMAT_A8B8G8R8_UNORM_PACK32; - case Tegra::FramebufferConfig::PixelFormat::RGB565: + case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM: return VK_FORMAT_R5G6B5_UNORM_PACK16; default: UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", @@ -210,17 +208,15 @@ 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_, + VideoCore::RasterizerInterface& rasterizer_, const VKDevice& device_, + VKMemoryManager& memory_manager_, VKSwapchain& swapchain_, + VKScheduler& scheduler_, const VKScreenInfo& screen_info_) + : cpu_memory{cpu_memory_}, render_window{render_window_}, rasterizer{rasterizer_}, + device{device_}, memory_manager{memory_manager_}, swapchain{swapchain_}, + scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_} { + resource_ticks.resize(image_count); CreateStaticResources(); CreateDynamicResources(); @@ -232,15 +228,16 @@ 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()); + + scheduler.Wait(resource_ticks[image_index]); + resource_ticks[image_index] = scheduler.CurrentTick(); VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get(); @@ -259,7 +256,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon 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); + const auto host_ptr = cpu_memory.GetPointer(framebuffer_addr); rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer)); // TODO(Rodrigo): Read this from HLE @@ -343,7 +340,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon cmdbuf.EndRenderPass(); }); - return {scheduler.GetFence(), *semaphores[image_index]}; + return *semaphores[image_index]; } void VKBlitScreen::CreateStaticResources() { @@ -696,6 +693,7 @@ void VKBlitScreen::CreateFramebuffers() { .flags = 0, .renderPass = *renderpass, .attachmentCount = 1, + .pAttachments = nullptr, .width = size.width, .height = size.height, .layers = 1, @@ -710,7 +708,7 @@ 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(); diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index 243640fab..8f2839214 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" namespace Core { class System; } +namespace Core::Memory { +class Memory; +} + namespace Core::Frontend { class EmuWindow; } @@ -30,26 +32,26 @@ class RasterizerInterface; namespace Vulkan { struct ScreenInfo; + class RasterizerVulkan; class VKDevice; -class VKFence; class VKImage; class VKScheduler; class VKSwapchain; class VKBlitScreen final { public: - explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window, + explicit VKBlitScreen(Core::Memory::Memory& cpu_memory, + 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); + VKMemoryManager& 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,11 +83,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; VKSwapchain& swapchain; VKScheduler& scheduler; @@ -106,7 +107,7 @@ private: vk::Buffer buffer; VKMemoryCommit 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; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 2be38d419..d9d3da9ea 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -39,16 +39,17 @@ std::unique_ptr<VKStreamBuffer> CreateStreamBuffer(const VKDevice& device, VKSch Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler_, VKStagingBufferPool& staging_pool_, VAddr cpu_addr, std::size_t size) - : VideoCommon::BufferBlock{cpu_addr, size}, scheduler{scheduler_}, staging_pool{staging_pool_} { - VkBufferCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.size = static_cast<VkDeviceSize>(size); - ci.usage = BUFFER_USAGE | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.queueFamilyIndexCount = 0; - ci.pQueueFamilyIndices = nullptr; + : BufferBlock{cpu_addr, size}, scheduler{scheduler_}, staging_pool{staging_pool_} { + const VkBufferCreateInfo ci{ + .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, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; buffer.handle = device.GetLogical().CreateBuffer(ci); buffer.commit = memory_manager.Commit(buffer.handle, false); @@ -66,16 +67,17 @@ void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) { scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) { cmdbuf.CopyBuffer(staging, handle, VkBufferCopy{0, offset, size}); - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = UPLOAD_ACCESS_BARRIERS; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = handle; - barrier.offset = offset; - barrier.size = 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, {}); }); @@ -87,16 +89,17 @@ void Buffer::Download(std::size_t offset, std::size_t size, u8* data) { const VkBuffer handle = Handle(); scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) { - VkBufferMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = handle; - barrier.offset = offset; - barrier.size = size; + 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 | @@ -142,14 +145,15 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst }); } -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} {} +VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory, + const VKDevice& device_, VKMemoryManager& memory_manager_, + VKScheduler& scheduler_, VKStagingBufferPool& staging_pool_) + : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, gpu_memory, cpu_memory, + CreateStreamBuffer(device_, + scheduler_)}, + device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{ + staging_pool_} {} VKBufferCache::~VKBufferCache() = default; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 991ee451c..7fb5ceedf 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -13,10 +13,6 @@ #include "video_core/renderer_vulkan/vk_stream_buffer.h" #include "video_core/renderer_vulkan/wrapper.h" -namespace Core { -class System; -} - namespace Vulkan { class VKDevice; @@ -53,7 +49,8 @@ private: class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> { public: - explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, + explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory, const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler, VKStagingBufferPool& staging_pool); ~VKBufferCache(); 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..6339f4fe0 --- /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/renderer_vulkan/vk_device.h" +#include "video_core/renderer_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 VKDevice& 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..b9cb3fb5d --- /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/renderer_vulkan/wrapper.h" + +namespace Vulkan { + +class MasterSemaphore; +class VKDevice; + +class CommandPool final : public ResourcePool { +public: + explicit CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device); + ~CommandPool() override; + + void Allocate(size_t begin, size_t end) override; + + VkCommandBuffer Commit(); + +private: + struct Pool; + + const VKDevice& 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 da71e710c..9637c6059 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -112,35 +112,36 @@ constexpr u8 quad_array[] = { 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}; + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, +}; VkDescriptorSetLayoutBinding BuildQuadArrayPassDescriptorSetLayoutBinding() { - VkDescriptorSetLayoutBinding binding; - binding.binding = 0; - binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - binding.descriptorCount = 1; - binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - binding.pImmutableSamplers = nullptr; - return binding; + return { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }; } VkDescriptorUpdateTemplateEntryKHR BuildQuadArrayPassDescriptorUpdateTemplateEntry() { - VkDescriptorUpdateTemplateEntryKHR entry; - entry.dstBinding = 0; - entry.dstArrayElement = 0; - entry.descriptorCount = 1; - entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - entry.offset = 0; - entry.stride = sizeof(DescriptorUpdateEntry); - return entry; + return { + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = 0, + .stride = sizeof(DescriptorUpdateEntry), + }; } VkPushConstantRange BuildComputePushConstantRange(std::size_t size) { - VkPushConstantRange range; - range.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - range.offset = 0; - range.size = static_cast<u32>(size); - return range; + return { + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .offset = 0, + .size = static_cast<u32>(size), + }; } // Uint8 SPIR-V module. Generated from the "shaders/" directory. @@ -218,7 +219,8 @@ constexpr u8 uint8_pass[] = { 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}; + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, +}; // Quad indexed SPIR-V module. Generated from the "shaders/" directory. constexpr u8 QUAD_INDEXED_SPV[] = { @@ -341,32 +343,37 @@ constexpr u8 QUAD_INDEXED_SPV[] = { 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}; + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, +}; std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBindings() { - std::array<VkDescriptorSetLayoutBinding, 2> bindings; - bindings[0].binding = 0; - bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[0].descriptorCount = 1; - bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - bindings[0].pImmutableSamplers = nullptr; - bindings[1].binding = 1; - bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[1].descriptorCount = 1; - bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - bindings[1].pImmutableSamplers = nullptr; - return bindings; + return {{ + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + { + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + }}; } VkDescriptorUpdateTemplateEntryKHR BuildInputOutputDescriptorUpdateTemplate() { - VkDescriptorUpdateTemplateEntryKHR entry; - entry.dstBinding = 0; - entry.dstArrayElement = 0; - entry.descriptorCount = 2; - entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - entry.offset = 0; - entry.stride = sizeof(DescriptorUpdateEntry); - return entry; + return { + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 2, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = 0, + .stride = sizeof(DescriptorUpdateEntry), + }; } } // Anonymous namespace @@ -376,37 +383,37 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates, vk::Span<VkPushConstantRange> push_constants, std::size_t code_size, const u8* code) { - VkDescriptorSetLayoutCreateInfo descriptor_layout_ci; - descriptor_layout_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptor_layout_ci.pNext = nullptr; - descriptor_layout_ci.flags = 0; - descriptor_layout_ci.bindingCount = bindings.size(); - descriptor_layout_ci.pBindings = bindings.data(); - descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout(descriptor_layout_ci); - - VkPipelineLayoutCreateInfo pipeline_layout_ci; - pipeline_layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipeline_layout_ci.pNext = nullptr; - pipeline_layout_ci.flags = 0; - pipeline_layout_ci.setLayoutCount = 1; - pipeline_layout_ci.pSetLayouts = descriptor_set_layout.address(); - pipeline_layout_ci.pushConstantRangeCount = push_constants.size(); - pipeline_layout_ci.pPushConstantRanges = push_constants.data(); - layout = device.GetLogical().CreatePipelineLayout(pipeline_layout_ci); + descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = bindings.size(), + .pBindings = bindings.data(), + }); + + layout = device.GetLogical().CreatePipelineLayout({ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = descriptor_set_layout.address(), + .pushConstantRangeCount = push_constants.size(), + .pPushConstantRanges = push_constants.data(), + }); if (!templates.empty()) { - VkDescriptorUpdateTemplateCreateInfoKHR template_ci; - template_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR; - template_ci.pNext = nullptr; - template_ci.flags = 0; - template_ci.descriptorUpdateEntryCount = templates.size(); - template_ci.pDescriptorUpdateEntries = templates.data(); - template_ci.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR; - template_ci.descriptorSetLayout = *descriptor_set_layout; - template_ci.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - template_ci.pipelineLayout = *layout; - template_ci.set = 0; - descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR(template_ci); + descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .descriptorUpdateEntryCount = templates.size(), + .pDescriptorUpdateEntries = templates.data(), + .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR, + .descriptorSetLayout = *descriptor_set_layout, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pipelineLayout = *layout, + .set = 0, + }); descriptor_allocator.emplace(descriptor_pool, *descriptor_set_layout); } @@ -414,42 +421,42 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto auto code_copy = std::make_unique<u32[]>(code_size / sizeof(u32) + 1); std::memcpy(code_copy.get(), code, code_size); - VkShaderModuleCreateInfo module_ci; - module_ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - module_ci.pNext = nullptr; - module_ci.flags = 0; - module_ci.codeSize = code_size; - module_ci.pCode = code_copy.get(); - module = device.GetLogical().CreateShaderModule(module_ci); - - VkComputePipelineCreateInfo pipeline_ci; - pipeline_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; - pipeline_ci.pNext = nullptr; - pipeline_ci.flags = 0; - pipeline_ci.layout = *layout; - pipeline_ci.basePipelineHandle = nullptr; - pipeline_ci.basePipelineIndex = 0; - - VkPipelineShaderStageCreateInfo& stage_ci = pipeline_ci.stage; - stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stage_ci.pNext = nullptr; - stage_ci.flags = 0; - stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT; - stage_ci.module = *module; - stage_ci.pName = "main"; - stage_ci.pSpecializationInfo = nullptr; - - pipeline = device.GetLogical().CreateComputePipeline(pipeline_ci); + module = device.GetLogical().CreateShaderModule({ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .codeSize = code_size, + .pCode = code_copy.get(), + }); + + pipeline = device.GetLogical().CreateComputePipeline({ + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = *module, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + .layout = *layout, + .basePipelineHandle = nullptr, + .basePipelineIndex = 0, + }); } 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; } @@ -473,7 +480,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 update_descriptor_queue.Acquire(); update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size); - const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); + const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue); scheduler.RequestOutsideRenderPassOperationContext(); @@ -516,13 +523,13 @@ 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)); + const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16)); auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); 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()); + const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set, @@ -585,7 +592,7 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble( 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()); + const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set, diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 230b526bc..acc94f27e 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -15,7 +15,6 @@ namespace Vulkan { class VKDevice; -class VKFence; class VKScheduler; class VKStagingBufferPool; class VKUpdateDescriptorQueue; @@ -30,8 +29,7 @@ public: ~VKComputePass(); protected: - VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue, - VKFence& fence); + VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue); vk::DescriptorUpdateTemplateKHR descriptor_template; vk::PipelineLayout layout; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 281bf9ac3..9be72dc9b 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -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; } @@ -43,12 +43,13 @@ vk::DescriptorSetLayout VKComputePipeline::CreateDescriptorSetLayout() const { const auto add_bindings = [&](VkDescriptorType descriptor_type, std::size_t num_entries) { // TODO(Rodrigo): Maybe make individual bindings here? for (u32 bindpoint = 0; bindpoint < static_cast<u32>(num_entries); ++bindpoint) { - VkDescriptorSetLayoutBinding& entry = bindings.emplace_back(); - entry.binding = binding++; - entry.descriptorType = descriptor_type; - entry.descriptorCount = 1; - entry.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - entry.pImmutableSamplers = nullptr; + bindings.push_back({ + .binding = binding++, + .descriptorType = descriptor_type, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }); } }; add_bindings(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, entries.const_buffers.size()); @@ -58,25 +59,25 @@ vk::DescriptorSetLayout VKComputePipeline::CreateDescriptorSetLayout() const { add_bindings(VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, entries.storage_texels.size()); add_bindings(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, entries.images.size()); - VkDescriptorSetLayoutCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.bindingCount = static_cast<u32>(bindings.size()); - ci.pBindings = bindings.data(); - return device.GetLogical().CreateDescriptorSetLayout(ci); + return device.GetLogical().CreateDescriptorSetLayout({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = static_cast<u32>(bindings.size()), + .pBindings = bindings.data(), + }); } vk::PipelineLayout VKComputePipeline::CreatePipelineLayout() const { - VkPipelineLayoutCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.setLayoutCount = 1; - ci.pSetLayouts = descriptor_set_layout.address(); - ci.pushConstantRangeCount = 0; - ci.pPushConstantRanges = nullptr; - return device.GetLogical().CreatePipelineLayout(ci); + return device.GetLogical().CreatePipelineLayout({ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = descriptor_set_layout.address(), + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }); } vk::DescriptorUpdateTemplateKHR VKComputePipeline::CreateDescriptorUpdateTemplate() const { @@ -89,59 +90,63 @@ vk::DescriptorUpdateTemplateKHR VKComputePipeline::CreateDescriptorUpdateTemplat return {}; } - VkDescriptorUpdateTemplateCreateInfoKHR ci; - ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR; - ci.pNext = nullptr; - ci.flags = 0; - ci.descriptorUpdateEntryCount = static_cast<u32>(template_entries.size()); - ci.pDescriptorUpdateEntries = template_entries.data(); - ci.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR; - ci.descriptorSetLayout = *descriptor_set_layout; - ci.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - ci.pipelineLayout = *layout; - ci.set = DESCRIPTOR_SET; - return device.GetLogical().CreateDescriptorUpdateTemplateKHR(ci); + return device.GetLogical().CreateDescriptorUpdateTemplateKHR({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .descriptorUpdateEntryCount = static_cast<u32>(template_entries.size()), + .pDescriptorUpdateEntries = template_entries.data(), + .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR, + .descriptorSetLayout = *descriptor_set_layout, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pipelineLayout = *layout, + .set = DESCRIPTOR_SET, + }); } vk::ShaderModule VKComputePipeline::CreateShaderModule(const std::vector<u32>& code) const { device.SaveShader(code); - VkShaderModuleCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.codeSize = code.size() * sizeof(u32); - ci.pCode = code.data(); - return device.GetLogical().CreateShaderModule(ci); + return device.GetLogical().CreateShaderModule({ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .codeSize = code.size() * sizeof(u32), + .pCode = code.data(), + }); } vk::Pipeline VKComputePipeline::CreatePipeline() const { - VkComputePipelineCreateInfo ci; - VkPipelineShaderStageCreateInfo& stage_ci = ci.stage; - stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stage_ci.pNext = nullptr; - stage_ci.flags = 0; - stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT; - stage_ci.module = *shader_module; - stage_ci.pName = "main"; - stage_ci.pSpecializationInfo = nullptr; - - VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci; - subgroup_size_ci.sType = - VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT; - subgroup_size_ci.pNext = nullptr; - subgroup_size_ci.requiredSubgroupSize = GuestWarpSize; + + VkComputePipelineCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = *shader_module, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + .layout = *layout, + .basePipelineHandle = nullptr, + .basePipelineIndex = 0, + }; + + const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, + .pNext = nullptr, + .requiredSubgroupSize = GuestWarpSize, + }; if (entries.uses_warps && device.IsGuestWarpSizeSupported(VK_SHADER_STAGE_COMPUTE_BIT)) { - stage_ci.pNext = &subgroup_size_ci; + ci.stage.pNext = &subgroup_size_ci; } - ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.layout = *layout; - ci.basePipelineHandle = nullptr; - ci.basePipelineIndex = 0; return device.GetLogical().CreateComputePipeline(ci); } diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp index 9259b618d..f38e089d5 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -7,7 +7,8 @@ #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/vk_resource_pool.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/wrapper.h" namespace Vulkan { @@ -15,14 +16,15 @@ 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 VKDevice& device_, VKScheduler& scheduler) + : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()}, active_pool{ + AllocateNewPool()} {} VKDescriptorPool::~VKDescriptorPool() = default; @@ -43,27 +46,30 @@ vk::DescriptorPool* VKDescriptorPool::AllocateNewPool() { {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, num_sets * 64}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sets * 64}, {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, num_sets * 64}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, num_sets * 40}}; - - VkDescriptorPoolCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - ci.maxSets = num_sets; - ci.poolSizeCount = static_cast<u32>(std::size(pool_sizes)); - ci.pPoolSizes = std::data(pool_sizes); + {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, num_sets * 40}, + }; + + const VkDescriptorPoolCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = num_sets, + .poolSizeCount = static_cast<u32>(std::size(pool_sizes)), + .pPoolSizes = std::data(pool_sizes), + }; return &pools.emplace_back(device.GetLogical().CreateDescriptorPool(ci)); } vk::DescriptorSets VKDescriptorPool::AllocateDescriptors(VkDescriptorSetLayout layout, std::size_t count) { const std::vector layout_copies(count, layout); - VkDescriptorSetAllocateInfo ai; - ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - ai.pNext = nullptr; - ai.descriptorPool = **active_pool; - ai.descriptorSetCount = static_cast<u32>(count); - ai.pSetLayouts = layout_copies.data(); + VkDescriptorSetAllocateInfo ai{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = nullptr, + .descriptorPool = **active_pool, + .descriptorSetCount = static_cast<u32>(count), + .pSetLayouts = layout_copies.data(), + }; vk::DescriptorSets sets = active_pool->Allocate(ai); if (!sets.IsOutOfPoolMemory()) { diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h index 9efa66bef..544f32a20 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/vk_resource_pool.h" #include "video_core/renderer_vulkan/wrapper.h" namespace Vulkan { +class VKDevice; 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 VKDevice& 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; + MasterSemaphore& master_semaphore; std::vector<vk::DescriptorPool> pools; vk::DescriptorPool* active_pool; diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index 9226e591c..3d8d3213d 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp @@ -42,6 +42,7 @@ constexpr std::array REQUIRED_EXTENSIONS{ 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_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME, VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME, @@ -84,14 +85,19 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_UINT_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32, + VK_FORMAT_A8B8G8R8_SINT_PACK32, VK_FORMAT_A8B8G8R8_SRGB_PACK32, VK_FORMAT_B5G6R5_UNORM_PACK16, VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UINT_PACK32, VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32_SFLOAT, + VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_UINT, + VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_UNORM, @@ -103,8 +109,11 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SNORM, + VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8_UNORM, + VK_FORMAT_R8_SNORM, + VK_FORMAT_R8_SINT, VK_FORMAT_R8_UINT, VK_FORMAT_B10G11R11_UFLOAT_PACK32, VK_FORMAT_R32_SFLOAT, @@ -124,6 +133,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties( VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, + VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, @@ -241,6 +251,13 @@ bool VKDevice::Create() { .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, .pNext = nullptr, @@ -373,6 +390,8 @@ bool VKDevice::Create() { graphics_queue = logical.GetQueue(graphics_family); present_queue = logical.GetQueue(present_family); + + use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue(); return true; } @@ -757,14 +776,15 @@ std::vector<VkDeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const queue_cis.reserve(unique_queue_families.size()); for (const u32 queue_family : unique_queue_families) { - queue_cis.push_back({ + auto& ci = queue_cis.emplace_back(VkDeviceQueueCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueFamilyIndex = queue_family, .queueCount = 1, - .pQueuePriorities = &QUEUE_PRIORITY, + .pQueuePriorities = nullptr, }); + ci.pQueuePriorities = &QUEUE_PRIORITY; } return queue_cis; diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h index ae5c21baa..26a233db1 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/renderer_vulkan/vk_device.h @@ -122,6 +122,11 @@ public: return properties.limits.maxPushConstantsSize; } + /// Returns the maximum size for shared memory. + u32 GetMaxComputeSharedMemorySize() const { + return properties.limits.maxComputeSharedMemorySize; + } + /// Returns true if ASTC is natively supported. bool IsOptimalAstcSupported() const { return is_optimal_astc_supported; @@ -197,6 +202,11 @@ public: return reported_extensions; } + /// Returns true if the setting for async shader compilation is enabled. + bool UseAsynchronousShaders() const { + return use_asynchronous_shaders; + } + /// Checks if the physical device is suitable. static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface); @@ -247,6 +257,9 @@ private: bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. + // Asynchronous Graphics Pipeline setting + bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline + // Telemetry parameters std::string vendor_name; ///< Device's driver name. std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions. diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp index a02be5487..5babbdd0b 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp @@ -30,7 +30,7 @@ void InnerFence::Queue() { ASSERT(!event); event = device.GetLogical().CreateEvent(); - ticks = scheduler.Ticks(); + ticks = scheduler.CurrentTick(); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([event = *event](vk::CommandBuffer cmdbuf) { @@ -52,7 +52,7 @@ void InnerFence::Wait() { } ASSERT(event); - if (ticks >= scheduler.Ticks()) { + if (ticks >= scheduler.CurrentTick()) { scheduler.Flush(); } while (!IsEventSignalled()) { @@ -71,12 +71,12 @@ bool InnerFence::IsEventSignalled() const { } } -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, + Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache, + VKBufferCache& buffer_cache, VKQueryCache& query_cache, + const VKDevice& device_, VKScheduler& scheduler_) + : GenericFenceManager(rasterizer, gpu, texture_cache, buffer_cache, query_cache), + device{device_}, scheduler{scheduler_} {} Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) { return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed); diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h index 043fe7947..1547d6d30 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.h +++ b/src/video_core/renderer_vulkan/vk_fence_manager.h @@ -55,10 +55,10 @@ using GenericFenceManager = 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, + Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache, + VKBufferCache& buffer_cache, VKQueryCache& query_cache, + const VKDevice& device, VKScheduler& scheduler); protected: Fence CreateFence(u32 value, bool is_stubbed) override; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index aaf930b90..a4b9e7ef5 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -78,15 +78,14 @@ VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& sche const GraphicsPipelineCacheKey& key, vk::Span<VkDescriptorSetLayoutBinding> bindings, const SPIRVProgram& program) - : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()}, + : 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(key.renderpass_params)}, pipeline{CreatePipeline( - key.renderpass_params, - program)} {} + renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)}, + pipeline{CreatePipeline(cache_key.renderpass_params, program)} {} VKGraphicsPipeline::~VKGraphicsPipeline() = default; @@ -94,7 +93,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; } @@ -181,7 +180,7 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules( vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, const SPIRVProgram& program) const { - const auto& state = fixed_state; + const auto& state = cache_key.fixed_state; const auto& viewport_swizzles = state.viewport_swizzles; FixedPipelineState::DynamicState dynamic; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index a1d699a6c..58aa35efd 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -19,7 +19,27 @@ namespace Vulkan { using Maxwell = Tegra::Engines::Maxwell3D::Regs; -struct GraphicsPipelineCacheKey; +struct GraphicsPipelineCacheKey { + RenderPassParams renderpass_params; + u32 padding; + std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; + FixedPipelineState fixed_state; + + std::size_t Hash() const noexcept; + + bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept; + + bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { + return !operator==(rhs); + } + + std::size_t Size() const noexcept { + return sizeof(renderpass_params) + sizeof(padding) + 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 VKDescriptorPool; class VKDevice; @@ -54,6 +74,10 @@ public: return renderpass; } + GraphicsPipelineCacheKey GetCacheKey() const { + return cache_key; + } + private: vk::DescriptorSetLayout CreateDescriptorSetLayout( vk::Span<VkDescriptorSetLayoutBinding> bindings) const; @@ -70,7 +94,7 @@ private: const VKDevice& device; VKScheduler& scheduler; - const FixedPipelineState fixed_state; + const GraphicsPipelineCacheKey cache_key; const u64 hash; vk::DescriptorSetLayout descriptor_set_layout; diff --git a/src/video_core/renderer_vulkan/vk_image.cpp b/src/video_core/renderer_vulkan/vk_image.cpp index 9bceb3861..1c418ea17 100644 --- a/src/video_core/renderer_vulkan/vk_image.cpp +++ b/src/video_core/renderer_vulkan/vk_image.cpp @@ -102,21 +102,29 @@ bool VKImage::HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num void VKImage::CreatePresentView() { // Image type has to be 2D to be presented. - VkImageViewCreateInfo image_view_ci; - image_view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - image_view_ci.pNext = nullptr; - image_view_ci.flags = 0; - image_view_ci.image = *image; - image_view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D; - image_view_ci.format = format; - image_view_ci.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, - VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; - image_view_ci.subresourceRange.aspectMask = aspect_mask; - image_view_ci.subresourceRange.baseMipLevel = 0; - image_view_ci.subresourceRange.levelCount = 1; - image_view_ci.subresourceRange.baseArrayLayer = 0; - image_view_ci.subresourceRange.layerCount = 1; - present_view = device.GetLogical().CreateImageView(image_view_ci); + 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 { 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..ae26e558d --- /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_device.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/renderer_vulkan/wrapper.h" + +namespace Vulkan { + +using namespace std::chrono_literals; + +MasterSemaphore::MasterSemaphore(const VKDevice& 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..0e93706d7 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.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 <atomic> +#include <thread> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/wrapper.h" + +namespace Vulkan { + +class VKDevice; + +class MasterSemaphore { +public: + explicit MasterSemaphore(const VKDevice& device); + ~MasterSemaphore(); + + /// Returns the current logical tick. + [[nodiscard]] u64 CurrentTick() const noexcept { + return current_tick; + } + + /// 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 >= tick; + } + + /// Advance to the logical tick. + void NextTick() noexcept { + ++current_tick; + } + + /// Refresh the known GPU tick + void Refresh() { + gpu_tick = semaphore.GetCounter(); + } + + /// 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 index b4c650a63..24c8960ac 100644 --- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_memory_manager.cpp @@ -178,13 +178,12 @@ bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 t }(); // Try to allocate found type. - VkMemoryAllocateInfo memory_ai; - memory_ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - memory_ai.pNext = nullptr; - memory_ai.allocationSize = size; - memory_ai.memoryTypeIndex = type; - - vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory(memory_ai); + 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; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3da835324..5c038f4bc 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -28,6 +28,7 @@ #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" namespace Vulkan { @@ -88,12 +89,13 @@ void AddBindings(std::vector<VkDescriptorSetLayoutBinding>& bindings, u32& bindi // Combined image samplers can be arrayed. count = container[i].size; } - VkDescriptorSetLayoutBinding& entry = bindings.emplace_back(); - entry.binding = binding++; - entry.descriptorType = descriptor_type; - entry.descriptorCount = count; - entry.stageFlags = stage_flags; - entry.pImmutableSamplers = nullptr; + bindings.push_back({ + .binding = binding++, + .descriptorType = descriptor_type, + .descriptorCount = count, + .stageFlags = stage_flags, + .pImmutableSamplers = nullptr, + }); } } @@ -133,64 +135,56 @@ 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, Tegra::Engines::ShaderType stage, + GPUVAddr gpu_addr_, VAddr cpu_addr, VideoCommon::Shader::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 VKDevice& device_, + VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_, + VKUpdateDescriptorQueue& update_descriptor_queue_, + VKRenderPassCache& renderpass_cache_) + : 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_}, renderpass_cache{renderpass_cache_} {} 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) { @@ -204,24 +198,43 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() { return last_shaders = shaders; } -VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) { +VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline( + const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) { MICROPROFILE_SCOPE(Vulkan_PipelineCache); if (last_graphics_pipeline && last_graphics_key == key) { - return *last_graphics_pipeline; + return last_graphics_pipeline; } last_graphics_key = key; + 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) { + 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); + } + last_graphics_pipeline = pair->second.get(); + return last_graphics_pipeline; + } + const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); auto& entry = pair->second; if (is_cache_miss) { + gpu.ShaderNotify().MarkSharderBuilding(); LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); - const auto [program, bindings] = DecompileShaders(key); + 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); + gpu.ShaderNotify().MarkShaderComplete(); } - return *(last_graphics_pipeline = entry.get()); + last_graphics_pipeline = entry.get(); + return last_graphics_pipeline; } VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) { @@ -234,22 +247,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) { @@ -259,10 +271,15 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach } } - Specialization specialization; - specialization.workgroup_size = key.workgroup_size; - specialization.shared_memory_size = key.shared_memory_size; - + const Specialization specialization{ + .base_binding = 0, + .workgroup_size = key.workgroup_size, + .shared_memory_size = key.shared_memory_size, + .point_size = std::nullopt, + .enabled_attributes = {}, + .attribute_types = {}, + .ndc_minus_one_to_one = false, + }; const SPIRVShader spirv_shader{Decompile(device, shader->GetIR(), ShaderType::Compute, shader->GetRegistry(), specialization), shader->GetEntries()}; @@ -271,6 +288,12 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach return *entry; } +void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) { + gpu.ShaderNotify().MarkShaderComplete(); + std::unique_lock lock{pipeline_cache}; + graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline); +} + void VKPipelineCache::OnShaderRemoval(Shader* shader) { bool finished = false; const auto Finish = [&] { @@ -306,11 +329,7 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) { } std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> -VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { - const auto& fixed_state = key.fixed_state; - auto& memory_manager = system.GPU().MemoryManager(); - const auto& gpu = system.GPU().Maxwell3D(); - +VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { Specialization specialization; if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points || device.IsExtExtendedDynamicStateSupported()) { @@ -333,12 +352,12 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { 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 @@ -370,13 +389,14 @@ void AddEntry(std::vector<VkDescriptorUpdateTemplateEntry>& template_entries, u3 if constexpr (descriptor_type == COMBINED_IMAGE_SAMPLER) { for (u32 i = 0; i < count; ++i) { const u32 num_samplers = container[i].size; - VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back(); - entry.dstBinding = binding; - entry.dstArrayElement = 0; - entry.descriptorCount = num_samplers; - entry.descriptorType = descriptor_type; - entry.offset = offset; - entry.stride = entry_size; + template_entries.push_back({ + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = num_samplers, + .descriptorType = descriptor_type, + .offset = offset, + .stride = entry_size, + }); ++binding; offset += num_samplers * entry_size; @@ -389,22 +409,24 @@ void AddEntry(std::vector<VkDescriptorUpdateTemplateEntry>& template_entries, u3 // Nvidia has a bug where updating multiple texels at once causes the driver to crash. // Note: Fixed in driver Windows 443.24, Linux 440.66.15 for (u32 i = 0; i < count; ++i) { - VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back(); - entry.dstBinding = binding + i; - entry.dstArrayElement = 0; - entry.descriptorCount = 1; - entry.descriptorType = descriptor_type; - entry.offset = static_cast<std::size_t>(offset + i * entry_size); - entry.stride = entry_size; + template_entries.push_back({ + .dstBinding = binding + i, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = descriptor_type, + .offset = static_cast<std::size_t>(offset + i * entry_size), + .stride = entry_size, + }); } } else if (count > 0) { - VkDescriptorUpdateTemplateEntry& entry = template_entries.emplace_back(); - entry.dstBinding = binding; - entry.dstArrayElement = 0; - entry.descriptorCount = count; - entry.descriptorType = descriptor_type; - entry.offset = offset; - entry.stride = entry_size; + template_entries.push_back({ + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = count, + .descriptorType = descriptor_type, + .offset = offset, + .stride = entry_size, + }); } offset += count * entry_size; binding += count; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 0a3fe65fb..e558e6658 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -22,6 +22,7 @@ #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" @@ -37,34 +38,11 @@ class RasterizerVulkan; class VKComputePipeline; class VKDescriptorPool; class VKDevice; -class VKFence; class VKScheduler; class VKUpdateDescriptorQueue; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -struct GraphicsPipelineCacheKey { - RenderPassParams renderpass_params; - u32 padding; - std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; - FixedPipelineState fixed_state; - - std::size_t Hash() const noexcept; - - bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept; - - bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { - return !operator==(rhs); - } - - std::size_t Size() const noexcept { - return sizeof(renderpass_params) + sizeof(padding) + 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>); - struct ComputePipelineCacheKey { GPUVAddr shader; u32 shared_memory_size; @@ -106,7 +84,8 @@ namespace Vulkan { class Shader { public: - explicit Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, + 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(); @@ -118,22 +97,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; @@ -143,27 +119,36 @@ private: class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> { public: - explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer, - const VKDevice& device, VKScheduler& scheduler, - VKDescriptorPool& descriptor_pool, + explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu, + Tegra::Engines::Maxwell3D& maxwell3d, + Tegra::Engines::KeplerCompute& kepler_compute, + Tegra::MemoryManager& gpu_memory, const VKDevice& device, + VKScheduler& scheduler, VKDescriptorPool& descriptor_pool, VKUpdateDescriptorQueue& update_descriptor_queue, VKRenderPassCache& renderpass_cache); ~VKPipelineCache() override; std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders(); - VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key); + VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key, + VideoCommon::Shader::AsyncShaders& async_shaders); VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); + void EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline); + protected: void OnShaderRemoval(Shader* shader) final; private: std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders( - const GraphicsPipelineCacheKey& key); + const FixedPipelineState& fixed_state); + + Tegra::GPU& gpu; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + Tegra::MemoryManager& gpu_memory; - Core::System& system; const VKDevice& device; VKScheduler& scheduler; VKDescriptorPool& descriptor_pool; @@ -178,6 +163,7 @@ private: GraphicsPipelineCacheKey last_graphics_key; VKGraphicsPipeline* last_graphics_pipeline = nullptr; + std::mutex pipeline_cache; std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>> graphics_cache; std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index bc91c48cc..ee2d871e3 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -9,35 +9,33 @@ #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" 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 VKDevice& 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,14 +45,14 @@ std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) { void QueryPool::Allocate(std::size_t begin, std::size_t end) { usage.resize(end); - VkQueryPoolCreateInfo query_pool_ci; - query_pool_ci.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; - query_pool_ci.pNext = nullptr; - query_pool_ci.flags = 0; - query_pool_ci.queryType = GetTarget(type); - query_pool_ci.queryCount = static_cast<u32>(end - begin); - query_pool_ci.pipelineStatistics = 0; - pools.push_back(device->GetLogical().CreateQueryPool(query_pool_ci)); + pools.push_back(device.GetLogical().CreateQueryPool({ + .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queryType = GetTarget(type), + .queryCount = static_cast<u32>(end - begin), + .pipelineStatistics = 0, + })); } void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) { @@ -68,30 +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, +VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory, 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)); + : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, + HostCounter>{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) + QueryType type) : VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache}, - type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} { + type{type}, query{cache.AllocateQuery(type)}, tick{cache.Scheduler().CurrentTick()} { const vk::Device* logical = &cache.Device().GetLogical(); cache.Scheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) { logical->ResetQueryPoolEXT(query.first, query.second, 1); @@ -109,7 +116,7 @@ void HostCounter::EndQuery() { } u64 HostCounter::BlockingQuery() const { - if (ticks >= cache.Scheduler().Ticks()) { + if (tick >= cache.Scheduler().CurrentTick()) { cache.Scheduler().Flush(); } u64 data; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index 40119e6d3..2e57fb75d 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h @@ -11,7 +11,7 @@ #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/vk_resource_pool.h" #include "video_core/renderer_vulkan/wrapper.h" namespace VideoCore { @@ -28,14 +28,12 @@ class VKScheduler; using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>; -class QueryPool final : public VKFencedPool { +class QueryPool final : public ResourcePool { public: - explicit QueryPool(); + explicit QueryPool(const VKDevice& 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,18 +43,18 @@ protected: private: static constexpr std::size_t GROW_STEP = 512; - const VKDevice* device = nullptr; - VideoCore::QueryType type = {}; + const VKDevice& 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, + explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory, const VKDevice& device, VKScheduler& scheduler); ~VKQueryCache(); @@ -75,6 +73,7 @@ public: private: const VKDevice& device; VKScheduler& scheduler; + std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; }; class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> { @@ -91,7 +90,7 @@ 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> { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 7625871c2..f3c2483c8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -14,6 +14,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "core/core.h" #include "core/settings.h" #include "video_core/engines/kepler_compute.h" @@ -30,7 +31,6 @@ #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" @@ -64,20 +64,22 @@ VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::si 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; - viewport.x = src.translate_x - src.scale_x; - viewport.y = src.translate_y - src.scale_y; - viewport.width = width != 0.0f ? width : 1.0f; - viewport.height = height != 0.0f ? height : 1.0f; + VkViewport viewport{ + .x = src.translate_x - src.scale_x, + .y = src.translate_y - src.scale_y, + .width = width != 0.0f ? width : 1.0f, + .height = height != 0.0f ? height : 1.0f, + .minDepth = src.translate_z - src.scale_z * reduce_z, + .maxDepth = src.translate_z + src.scale_z, + }; - const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; - viewport.minDepth = src.translate_z - src.scale_z * reduce_z; - viewport.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; } @@ -378,28 +380,32 @@ void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const { } } -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), +RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_, + Tegra::MemoryManager& gpu_memory_, + Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info_, + const VKDevice& device_, VKMemoryManager& memory_manager_, + 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_manager(memory_manager_), state_tracker(state_tracker_), + scheduler(scheduler_), staging_pool(device, memory_manager, scheduler), + descriptor_pool(device, scheduler_), 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().CreateEvent()} { + texture_cache(*this, maxwell3d, gpu_memory, device, memory_manager, scheduler, staging_pool), + pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler, + descriptor_pool, update_descriptor_queue, renderpass_cache), + buffer_cache(*this, gpu_memory, cpu_memory, device, memory_manager, scheduler, staging_pool), + sampler_cache(device), query_cache(*this, maxwell3d, gpu_memory, device, scheduler), + fence_manager(*this, gpu, gpu_memory, 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(); + } } RasterizerVulkan::~RasterizerVulkan() = default; @@ -407,13 +413,13 @@ RasterizerVulkan::~RasterizerVulkan() = default; void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { MICROPROFILE_SCOPE(Vulkan_Drawing); + SCOPE_EXIT({ gpu.TickWork(); }); FlushWork(); query_cache.UpdateCounters(); - const auto& gpu = system.GPU().Maxwell3D(); GraphicsPipelineCacheKey key; - key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); + key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported()); buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed)); @@ -437,10 +443,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { key.renderpass_params = GetRenderPassParams(texceptions); key.padding = 0; - auto& pipeline = pipeline_cache.GetGraphicsPipeline(key); - scheduler.BindGraphicsPipeline(pipeline.GetHandle()); + auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, 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 renderpass = pipeline->GetRenderPass(); const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); scheduler.RequestRenderpass(renderpass, framebuffer, render_area); @@ -450,8 +461,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { BeginTransformFeedback(); - const auto pipeline_layout = pipeline.GetLayout(); - const auto descriptor_set = pipeline.CommitDescriptorSet(); + const auto pipeline_layout = pipeline->GetLayout(); + const auto 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, @@ -461,15 +472,12 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { }); EndTransformFeedback(); - - system.GPU().TickWork(); } void RasterizerVulkan::Clear() { MICROPROFILE_SCOPE(Vulkan_Clearing); - const auto& gpu = system.GPU().Maxwell3D(); - if (!system.GPU().Maxwell3D().ShouldExecute()) { + if (!maxwell3d.ShouldExecute()) { return; } @@ -478,7 +486,7 @@ void RasterizerVulkan::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; @@ -508,10 +516,11 @@ void RasterizerVulkan::Clear() { const u32 color_attachment = regs.clear_buffers.RT; scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { - VkClearAttachment attachment; - attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - attachment.colorAttachment = color_attachment; - attachment.clearValue = clear_value; + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = color_attachment, + .clearValue = clear_value, + }; cmdbuf.ClearAttachments(attachment, clear_rect); }); } @@ -529,10 +538,6 @@ void RasterizerVulkan::Clear() { scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { - VkClearValue clear_value; - clear_value.depthStencil.depth = clear_depth; - clear_value.depthStencil.stencil = clear_stencil; - VkClearAttachment attachment; attachment.aspectMask = aspect_flags; attachment.colorAttachment = 0; @@ -550,14 +555,17 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) { query_cache.UpdateCounters(); - const auto& launch_desc = system.GPU().KeplerCompute().launch_description; - ComputePipelineCacheKey key; - key.shader = code_addr; - key.shared_memory_size = launch_desc.shared_alloc; - key.workgroup_size = {launch_desc.block_dim_x, launch_desc.block_dim_y, - launch_desc.block_dim_z}; - - auto& pipeline = pipeline_cache.GetComputePipeline(key); + 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, + }, + }); // Compute dispatches can't be executed inside a renderpass scheduler.RequestOutsideRenderPassOperationContext(); @@ -643,16 +651,14 @@ void RasterizerVulkan::SyncGuestHost() { } 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; @@ -661,7 +667,6 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) { } void RasterizerVulkan::ReleaseFences() { - auto& gpu{system.GPU()}; if (!gpu.IsAsync()) { return; } @@ -739,10 +744,6 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config, return true; } -void RasterizerVulkan::SetupDirtyFlags() { - state_tracker.Initialize(); -} - void RasterizerVulkan::FlushWork() { static constexpr u32 DRAWS_TO_DISPATCH = 4096; @@ -766,10 +767,9 @@ void RasterizerVulkan::FlushWork() { 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 auto& regs = maxwell3d.regs; + auto& dirty = maxwell3d.dirty.flags; const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets]; dirty[VideoCommon::Dirty::RenderTargets] = false; @@ -813,8 +813,13 @@ bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachmen std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers( VkRenderPass renderpass) { - FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(), - std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()}; + 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) { @@ -827,7 +832,7 @@ std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers( return true; }; - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = 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])) { @@ -841,17 +846,17 @@ std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers( const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key); auto& framebuffer = fbentry->second; if (is_cache_miss) { - VkFramebufferCreateInfo framebuffer_ci; - framebuffer_ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebuffer_ci.pNext = nullptr; - framebuffer_ci.flags = 0; - framebuffer_ci.renderPass = key.renderpass; - framebuffer_ci.attachmentCount = static_cast<u32>(key.views.size()); - framebuffer_ci.pAttachments = key.views.data(); - framebuffer_ci.width = key.width; - framebuffer_ci.height = key.height; - framebuffer_ci.layers = key.layers; - framebuffer = device.GetLogical().CreateFramebuffer(framebuffer_ci); + 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}}; @@ -863,13 +868,12 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt bool is_instanced) { MICROPROFILE_SCOPE(Vulkan_Geometry); - const auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; + const auto& regs = maxwell3d.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 num_instances = is_instanced ? maxwell3d.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; @@ -930,7 +934,7 @@ void RasterizerVulkan::SetupImageTransitions( } void RasterizerVulkan::UpdateDynamicStates() { - auto& regs = system.GPU().Maxwell3D().regs; + auto& regs = maxwell3d.regs; UpdateViewportsState(regs); UpdateScissorsState(regs); UpdateDepthBias(regs); @@ -951,7 +955,7 @@ void RasterizerVulkan::UpdateDynamicStates() { } void RasterizerVulkan::BeginTransformFeedback() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } @@ -983,7 +987,7 @@ void RasterizerVulkan::BeginTransformFeedback() { } void RasterizerVulkan::EndTransformFeedback() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; if (regs.tfb_enabled == 0) { return; } @@ -996,7 +1000,7 @@ void RasterizerVulkan::EndTransformFeedback() { } void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { const auto& vertex_array = regs.vertex_array[index]; @@ -1022,7 +1026,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar if (params.num_vertices == 0) { return; } - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; switch (regs.draw.topology) { case Maxwell::PrimitiveTopology::Quads: { if (!params.is_indexed) { @@ -1070,8 +1074,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar 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]; + const auto& shader_stage = maxwell3d.state.shader_stages[stage]; for (const auto& entry : entries.const_buffers) { SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]); } @@ -1079,8 +1082,7 @@ void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, s 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]}; + const auto& cbufs{maxwell3d.state.shader_stages[stage]}; for (const auto& entry : entries.global_buffers) { const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset(); @@ -1090,19 +1092,17 @@ void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); for (const auto& entry : entries.uniform_texels) { - const auto image = GetTextureInfo(gpu, entry, stage).tic; + const auto image = GetTextureInfo(maxwell3d, entry, stage).tic; SetupUniformTexels(image, entry); } } void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); for (const auto& entry : entries.samplers) { for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(gpu, entry, stage, i); + const auto texture = GetTextureInfo(maxwell3d, entry, stage, i); SetupTexture(texture, entry); } } @@ -1110,25 +1110,23 @@ void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std:: void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().Maxwell3D(); for (const auto& entry : entries.storage_texels) { - const auto image = GetTextureInfo(gpu, entry, stage).tic; + const auto image = GetTextureInfo(maxwell3d, entry, stage).tic; SetupStorageTexel(image, entry); } } void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) { MICROPROFILE_SCOPE(Vulkan_Images); - const auto& gpu = system.GPU().Maxwell3D(); for (const auto& entry : entries.images) { - const auto tic = GetTextureInfo(gpu, entry, stage).tic; + const auto tic = GetTextureInfo(maxwell3d, 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; + const auto& launch_desc = kepler_compute.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(); @@ -1142,7 +1140,7 @@ void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) { void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) { MICROPROFILE_SCOPE(Vulkan_GlobalBuffers); - const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config}; + const auto& cbufs{kepler_compute.launch_description.const_buffer_config}; for (const auto& entry : entries.global_buffers) { const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()}; SetupGlobalBuffer(entry, addr); @@ -1151,19 +1149,17 @@ void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) { void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); for (const auto& entry : entries.uniform_texels) { - const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; + const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic; SetupUniformTexels(image, entry); } } void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); for (const auto& entry : entries.samplers) { for (std::size_t i = 0; i < entry.size; ++i) { - const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i); + const auto texture = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex, i); SetupTexture(texture, entry); } } @@ -1171,18 +1167,16 @@ void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) { void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) { MICROPROFILE_SCOPE(Vulkan_Textures); - const auto& gpu = system.GPU().KeplerCompute(); for (const auto& entry : entries.storage_texels) { - const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; + const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic; SetupStorageTexel(image, entry); } } void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) { MICROPROFILE_SCOPE(Vulkan_Images); - const auto& gpu = system.GPU().KeplerCompute(); for (const auto& entry : entries.images) { - const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; + const auto tic = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic; SetupImage(tic, entry); } } @@ -1206,9 +1200,8 @@ void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry, } 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); + const u64 actual_addr = gpu_memory.Read<u64>(address); + const u32 size = gpu_memory.Read<u32>(address + 8); if (size == 0) { // Sometimes global memory pointers don't have a proper size. Upload a dummy entry @@ -1426,10 +1419,10 @@ void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) { } void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) { - if (!state_tracker.TouchPrimitiveTopology()) { + const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value(); + if (!state_tracker.ChangePrimitiveTopology(primitive_topology)) { return; } - const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value(); scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) { cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology)); }); @@ -1491,7 +1484,7 @@ std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const { } std::size_t RasterizerVulkan::CalculateVertexArraysSize() const { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; std::size_t size = 0; for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { @@ -1506,9 +1499,8 @@ std::size_t RasterizerVulkan::CalculateVertexArraysSize() const { } 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()); + return static_cast<std::size_t>(maxwell3d.regs.index_array.count) * + static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes()); } std::size_t RasterizerVulkan::CalculateConstBufferSize( @@ -1523,7 +1515,7 @@ std::size_t RasterizerVulkan::CalculateConstBufferSize( } RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& regs = maxwell3d.regs; const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count); RenderPassParams params; @@ -1553,17 +1545,17 @@ VkBuffer RasterizerVulkan::DefaultBuffer() { return *default_buffer; } - VkBufferCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.size = DEFAULT_BUFFER_SIZE; - ci.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.queueFamilyIndexCount = 0; - ci.pQueueFamilyIndices = nullptr; - default_buffer = device.GetLogical().CreateBuffer(ci); + 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(); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 923178b0b..b47c8fc13 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -25,13 +25,13 @@ #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" namespace Core { class System; @@ -105,10 +105,11 @@ struct ImageView { class RasterizerVulkan final : public VideoCore::RasterizerAccelerated { public: - explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window, + explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu, + Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, - StateTracker& state_tracker, VKScheduler& scheduler); + VKMemoryManager& memory_manager, StateTracker& state_tracker, + VKScheduler& scheduler); ~RasterizerVulkan() override; void Draw(bool is_indexed, bool is_instanced) override; @@ -134,7 +135,14 @@ public: 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; + } + + const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const { + return async_shaders; + } /// Maximum supported size that a constbuffer can have in bytes. static constexpr std::size_t MaxConstbufferSize = 0x10000; @@ -270,11 +278,13 @@ private: VkBuffer DefaultBuffer(); - Core::System& system; - Core::Frontend::EmuWindow& render_window; + Tegra::GPU& gpu; + Tegra::MemoryManager& gpu_memory; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::Engines::KeplerCompute& kepler_compute; + VKScreenInfo& screen_info; const VKDevice& device; - VKResourceManager& resource_manager; VKMemoryManager& memory_manager; StateTracker& state_tracker; VKScheduler& scheduler; @@ -291,12 +301,13 @@ private: 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; diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp index 3f71d005e..80284cf92 100644 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -39,10 +39,14 @@ VkRenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) { 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); - const std::size_t num_attachments = static_cast<std::size_t>(params.num_color_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); @@ -54,20 +58,22 @@ vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& param const VkImageLayout color_layout = ((params.texceptions >> rt) & 1) != 0 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkAttachmentDescription& descriptor = descriptors.emplace_back(); - descriptor.flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT; - descriptor.format = format.format; - descriptor.samples = VK_SAMPLE_COUNT_1_BIT; - descriptor.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - descriptor.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - descriptor.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - descriptor.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - descriptor.initialLayout = color_layout; - descriptor.finalLayout = color_layout; - - VkAttachmentReference& reference = color_references.emplace_back(); - reference.attachment = static_cast<u32>(rt); - reference.layout = color_layout; + 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; @@ -82,32 +88,36 @@ vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& param const VkImageLayout zeta_layout = params.zeta_texception != 0 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkAttachmentDescription& descriptor = descriptors.emplace_back(); - descriptor.flags = 0; - descriptor.format = format.format; - descriptor.samples = VK_SAMPLE_COUNT_1_BIT; - descriptor.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - descriptor.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - descriptor.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - descriptor.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; - descriptor.initialLayout = zeta_layout; - descriptor.finalLayout = zeta_layout; - - zeta_attachment_ref.attachment = static_cast<u32>(num_attachments); - zeta_attachment_ref.layout = zeta_layout; + 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, + }; } - VkSubpassDescription subpass_description; - subpass_description.flags = 0; - subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass_description.inputAttachmentCount = 0; - subpass_description.pInputAttachments = nullptr; - subpass_description.colorAttachmentCount = static_cast<u32>(color_references.size()); - subpass_description.pColorAttachments = color_references.data(); - subpass_description.pResolveAttachments = nullptr; - subpass_description.pDepthStencilAttachment = has_zeta ? &zeta_attachment_ref : nullptr; - subpass_description.preserveAttachmentCount = 0; - subpass_description.pPreserveAttachments = nullptr; + 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; @@ -122,26 +132,27 @@ vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& param stage |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; } - VkSubpassDependency subpass_dependency; - subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - subpass_dependency.dstSubpass = 0; - subpass_dependency.srcStageMask = stage; - subpass_dependency.dstStageMask = stage; - subpass_dependency.srcAccessMask = 0; - subpass_dependency.dstAccessMask = access; - subpass_dependency.dependencyFlags = 0; - - VkRenderPassCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.attachmentCount = static_cast<u32>(descriptors.size()); - ci.pAttachments = descriptors.data(); - ci.subpassCount = 1; - ci.pSubpasses = &subpass_description; - ci.dependencyCount = 1; - ci.pDependencies = &subpass_dependency; - return device.GetLogical().CreateRenderPass(ci); + 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_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp deleted file mode 100644 index dc06f545a..000000000 --- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp +++ /dev/null @@ -1,312 +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; - -VkFenceCreateInfo BuildFenceCreateInfo() { - VkFenceCreateInfo fence_ci; - fence_ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fence_ci.pNext = nullptr; - fence_ci.flags = 0; - return fence_ci; -} - -} // Anonymous namespace - -class CommandBufferPool final : public VKFencedPool { -public: - 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. - VkCommandPoolCreateInfo command_pool_ci; - command_pool_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - command_pool_ci.pNext = nullptr; - command_pool_ci.flags = - VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - command_pool_ci.queueFamilyIndex = device.GetGraphicsFamily(); - - Pool& pool = pools.emplace_back(); - pool.handle = device.GetLogical().CreateCommandPool(command_pool_ci); - 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..ee274ac59 --- /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 auto search = [this](size_t begin, size_t end) -> std::optional<size_t> { + for (size_t iterator = begin; iterator < end; ++iterator) { + if (master_semaphore.IsFree(ticks[iterator])) { + ticks[iterator] = master_semaphore.CurrentTick(); + return iterator; + } + } + return {}; + }; + // Try to find a free resource from the hinted position to the end. + auto found = search(free_iterator, ticks.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 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. + free_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..a018c7ec2 --- /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 free_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 index 616eacc36..b068888f9 100644 --- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp @@ -44,32 +44,36 @@ vk::Sampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) c const bool arbitrary_borders = device.IsExtCustomBorderColorSupported(); const std::array color = tsc.GetBorderColor(); - VkSamplerCustomBorderColorCreateInfoEXT border; - border.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT; - border.pNext = nullptr; - border.format = VK_FORMAT_UNDEFINED; + 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)); - VkSamplerCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - ci.pNext = arbitrary_borders ? &border : nullptr; - ci.flags = 0; - ci.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter); - ci.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter); - ci.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter); - ci.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter); - ci.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter); - ci.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter); - ci.mipLodBias = tsc.GetLodBias(); - ci.anisotropyEnable = tsc.GetMaxAnisotropy() > 1.0f ? VK_TRUE : VK_FALSE; - ci.maxAnisotropy = tsc.GetMaxAnisotropy(); - ci.compareEnable = tsc.depth_compare_enabled; - ci.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func); - ci.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.GetMinLod(); - ci.maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.GetMaxLod(); - ci.borderColor = arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color); - ci.unnormalizedCoordinates = VK_FALSE; - return device.GetLogical().CreateSampler(ci); + 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 { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 56524e6f3..1a483dc71 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -10,9 +10,10 @@ #include "common/microprofile.h" #include "common/thread.h" +#include "video_core/renderer_vulkan/vk_command_pool.h" #include "video_core/renderer_vulkan/vk_device.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" @@ -35,10 +36,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 VKDevice& 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 +51,27 @@ VKScheduler::~VKScheduler() { worker_thread.join(); } -void VKScheduler::Flush(bool release_fence, VkSemaphore semaphore) { +u64 VKScheduler::CurrentTick() const noexcept { + return master_semaphore->CurrentTick(); +} + +bool VKScheduler::IsFree(u64 tick) const noexcept { + return master_semaphore->IsFree(tick); +} + +void VKScheduler::Wait(u64 tick) { + master_semaphore->Wait(tick); +} + +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(); } @@ -100,16 +108,19 @@ void VKScheduler::RequestRenderpass(VkRenderPass renderpass, VkFramebuffer frame state.framebuffer = framebuffer; state.render_area = render_area; - 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 = render_area; - renderpass_bi.clearValueCount = 0; - renderpass_bi.pClearValues = nullptr; + 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) { @@ -157,17 +168,38 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) { current_cmdbuf.End(); - VkSubmitInfo submit_info; - submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submit_info.pNext = nullptr; - submit_info.waitSemaphoreCount = 0; - submit_info.pWaitSemaphores = nullptr; - submit_info.pWaitDstStageMask = nullptr; - submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = current_cmdbuf.address(); - submit_info.signalSemaphoreCount = semaphore ? 1 : 0; - submit_info.pSignalSemaphores = &semaphore; - switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info, *current_fence)) { + 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 = &timeline_si, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &timeline_semaphore, + .pWaitDstStageMask = &wait_stage_mask, + .commandBufferCount = 1, + .pCommandBuffers = current_cmdbuf.address(), + .signalSemaphoreCount = num_signal_semaphores, + .pSignalSemaphores = signal_semaphores.data(), + }; + switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) { case VK_SUCCESS: break; case VK_ERROR_DEVICE_LOST: @@ -179,21 +211,15 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) { } void VKScheduler::AllocateNewContext() { - ++ticks; - - VkCommandBufferBeginInfo cmdbuf_bi; - cmdbuf_bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - cmdbuf_bi.pNext = nullptr; - cmdbuf_bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - cmdbuf_bi.pInheritanceInfo = nullptr; - 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.Begin(cmdbuf_bi); + current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader()); + current_cmdbuf.Begin({ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr, + }); // Enable counters once again. These are disabled when a command buffer is finished. if (query_cache) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 970a65566..7be8a19f0 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -16,42 +16,33 @@ namespace Vulkan { +class CommandPool; +class MasterSemaphore; 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 VKDevice& device, StateTracker& state_tracker); ~VKScheduler(); + /// Returns the current command buffer tick. + [[nodiscard]] u64 CurrentTick() const noexcept; + + /// Returns true when a tick has been triggered by the GPU. + [[nodiscard]] bool IsFree(u64 tick) const noexcept; + + /// Waits for the given tick to trigger on the GPU. + void Wait(u64 tick); + /// 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. @@ -86,14 +77,9 @@ public: (void)chunk->Record(command); } - /// Gets a reference to the current fence. - VKFenceView GetFence() const { - return current_fence; - } - - /// Returns the current command buffer tick. - u64 Ticks() const { - return ticks; + /// Returns the master timeline semaphore. + [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept { + return *master_semaphore; } private: @@ -171,6 +157,13 @@ private: std::array<u8, 0x8000> data{}; }; + struct State { + VkRenderPass renderpass = nullptr; + VkFramebuffer framebuffer = nullptr; + VkExtent2D render_area = {0, 0}; + VkPipeline graphics_pipeline = nullptr; + }; + void WorkerThread(); void SubmitExecution(VkSemaphore semaphore); @@ -186,30 +179,23 @@ private: void AcquireNewChunk(); const VKDevice& device; - VKResourceManager& resource_manager; 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; 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 97429cc59..cd7d7a4e4 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -685,13 +685,19 @@ private: } t_smem_uint = TypePointer(spv::StorageClass::Workgroup, t_uint); - const u32 smem_size = specialization.shared_memory_size; + u32 smem_size = specialization.shared_memory_size * 4; if (smem_size == 0) { // Avoid declaring an empty array. return; } - const auto element_count = static_cast<u32>(Common::AlignUp(smem_size, 4) / 4); - const Id type_array = TypeArray(t_uint, Constant(t_uint, element_count)); + const u32 limit = device.GetMaxComputeSharedMemorySize(); + if (smem_size > limit) { + LOG_ERROR(Render_Vulkan, "Shared memory size {} is clamped to host's limit {}", + smem_size, limit); + smem_size = limit; + } + + const Id type_array = TypeArray(t_uint, Constant(t_uint, smem_size / 4)); const Id type_pointer = TypePointer(spv::StorageClass::Workgroup, type_array); Name(type_pointer, "SharedMemory"); @@ -700,9 +706,9 @@ private: } void DeclareInternalFlags() { - constexpr std::array names = {"zero", "sign", "carry", "overflow"}; + static constexpr std::array names{"zero", "sign", "carry", "overflow"}; + for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) { - const auto flag_code = static_cast<InternalFlag>(flag); const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false); internal_flags[flag] = AddGlobalVariable(Name(id, names[flag])); } @@ -2798,7 +2804,6 @@ private: std::map<GlobalMemoryBase, Id> global_buffers; std::map<u32, TexelBuffer> uniform_texels; std::map<u32, SampledImage> sampled_images; - std::map<u32, TexelBuffer> storage_texels; std::map<u32, StorageImage> images; std::array<Id, Maxwell::NumRenderTargets> frag_colors{}; diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index 112df9c71..c1a218d76 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -19,13 +19,13 @@ vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, cons const auto data = std::make_unique<u32[]>(code_size / sizeof(u32)); std::memcpy(data.get(), code_data, code_size); - VkShaderModuleCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.codeSize = code_size; - ci.pCode = data.get(); - return device.GetLogical().CreateShaderModule(ci); + return device.GetLogical().CreateShaderModule({ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .codeSize = code_size, + .pCode = data.get(), + }); } } // 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 45c180221..2fd3b7f39 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -10,36 +10,18 @@ #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" namespace Vulkan { -VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, - u64 last_epoch) - : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {} +VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer_) + : buffer{std::move(buffer_)} {} -VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept { - buffer = std::move(rhs.buffer); - watch = std::move(rhs.watch); - last_epoch = rhs.last_epoch; -} - -VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default; - -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; -} - -VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler) - : device{device}, memory_manager{memory_manager}, scheduler{scheduler} {} +VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device_, VKMemoryManager& memory_manager_, + VKScheduler& scheduler_) + : device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_} {} VKStagingBufferPool::~VKStagingBufferPool() = default; @@ -51,7 +33,6 @@ VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visib } void VKStagingBufferPool::TickFrame() { - ++epoch; current_delete_level = (current_delete_level + 1) % NumLevels; ReleaseCache(true); @@ -59,11 +40,12 @@ void VKStagingBufferPool::TickFrame() { } 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; + for (StagingBuffer& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) { + if (!scheduler.IsFree(entry.tick)) { + continue; } + entry.tick = scheduler.CurrentTick(); + return &*entry.buffer; } return nullptr; } @@ -71,24 +53,25 @@ VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_ VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) { const u32 log2 = Common::Log2Ceil64(size); - VkBufferCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.size = 1ULL << log2; - ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_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; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.queueFamilyIndexCount = 0; - ci.pQueueFamilyIndices = nullptr; - auto buffer = std::make_unique<VKBuffer>(); - buffer->handle = device.GetLogical().CreateBuffer(ci); + buffer->handle = device.GetLogical().CreateBuffer({ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = 1ULL << log2, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_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, + }); 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; + std::vector<StagingBuffer>& entries = GetCache(host_visible)[log2].entries; + StagingBuffer& entry = entries.emplace_back(std::move(buffer)); + entry.tick = scheduler.CurrentTick(); + return *entry.buffer; } VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) { @@ -110,9 +93,8 @@ u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t lo auto& entries = staging.entries; const std::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); 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..2dd5049ac 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -10,13 +10,11 @@ #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" namespace Vulkan { class VKDevice; -class VKFenceWatch; class VKScheduler; struct VKBuffer final { @@ -36,16 +34,10 @@ public: 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; + explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer); std::unique_ptr<VKBuffer> buffer; - VKFenceWatch watch; - u64 last_epoch = 0; + u64 tick = 0; }; struct StagingBuffers final { @@ -73,8 +65,6 @@ private: StagingBuffersCache host_staging_buffers; StagingBuffersCache device_staging_buffers; - u64 epoch = 0; - std::size_t current_delete_level = 0; }; diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index 9151d9fb1..5d2c4a796 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -42,7 +42,6 @@ Flags MakeInvalidationFlags() { flags[DepthWriteEnable] = true; flags[DepthCompareOp] = true; flags[FrontFace] = true; - flags[PrimitiveTopology] = true; flags[StencilOp] = true; flags[StencilTestEnable] = true; return flags; @@ -112,10 +111,6 @@ void SetupDirtyFrontFace(Tables& tables) { table[OFF(screen_y_control)] = FrontFace; } -void SetupDirtyPrimitiveTopology(Tables& tables) { - tables[0][OFF(draw.topology)] = PrimitiveTopology; -} - void SetupDirtyStencilOp(Tables& tables) { auto& table = tables[0]; table[OFF(stencil_front_op_fail)] = StencilOp; @@ -137,12 +132,9 @@ void SetupDirtyStencilTestEnable(Tables& tables) { } // Anonymous namespace -StateTracker::StateTracker(Core::System& system) - : system{system}, invalidation_flags{MakeInvalidationFlags()} {} - -void StateTracker::Initialize() { - auto& dirty = system.GPU().Maxwell3D().dirty; - auto& tables = dirty.tables; +StateTracker::StateTracker(Tegra::GPU& gpu) + : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} { + auto& tables = gpu.Maxwell3D().dirty.tables; SetupDirtyRenderTargets(tables); SetupDirtyViewports(tables); SetupDirtyScissors(tables); @@ -156,13 +148,8 @@ void StateTracker::Initialize() { SetupDirtyDepthWriteEnable(tables); SetupDirtyDepthCompareOp(tables); SetupDirtyFrontFace(tables); - SetupDirtyPrimitiveTopology(tables); SetupDirtyStencilOp(tables); SetupDirtyStencilTestEnable(tables); } -void StateTracker::InvalidateCommandBufferState() { - system.GPU().Maxwell3D().dirty.flags |= invalidation_flags; -} - } // 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 54ca0d6c6..1de789e57 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -32,7 +32,6 @@ enum : u8 { DepthWriteEnable, DepthCompareOp, FrontFace, - PrimitiveTopology, StencilOp, StencilTestEnable, @@ -43,12 +42,15 @@ static_assert(Last <= std::numeric_limits<u8>::max()); } // namespace Dirty class StateTracker { -public: - explicit StateTracker(Core::System& system); + using Maxwell = Tegra::Engines::Maxwell3D::Regs; - void Initialize(); +public: + explicit StateTracker(Tegra::GPU& gpu); - void InvalidateCommandBufferState(); + void InvalidateCommandBufferState() { + flags |= invalidation_flags; + current_topology = INVALID_TOPOLOGY; + } bool TouchViewports() { return Exchange(Dirty::Viewports, false); @@ -102,10 +104,6 @@ public: return Exchange(Dirty::FrontFace, false); } - bool TouchPrimitiveTopology() { - return Exchange(Dirty::PrimitiveTopology, false); - } - bool TouchStencilOp() { return Exchange(Dirty::StencilOp, false); } @@ -114,16 +112,24 @@ public: return Exchange(Dirty::StencilTestEnable, false); } + bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) { + const bool has_changed = current_topology != new_topology; + current_topology = new_topology; + return has_changed; + } + 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; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 2d28a6c47..1b59612b9 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -11,7 +11,6 @@ #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" @@ -57,9 +56,9 @@ u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties, } // Anonymous namespace -VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler, +VKStreamBuffer::VKStreamBuffer(const VKDevice& device_, VKScheduler& scheduler_, VkBufferUsageFlags usage) - : device{device}, scheduler{scheduler} { + : device{device_}, scheduler{scheduler_} { CreateBuffers(usage); ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); @@ -111,7 +110,7 @@ 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) { @@ -121,31 +120,29 @@ void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) { // 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; - - VkBufferCreateInfo buffer_ci; - buffer_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buffer_ci.pNext = nullptr; - buffer_ci.flags = 0; - buffer_ci.size = std::min(PREFERRED_STREAM_BUFFER_SIZE, allocable_size); - buffer_ci.usage = usage; - buffer_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - buffer_ci.queueFamilyIndexCount = 0; - buffer_ci.pQueueFamilyIndices = nullptr; - - buffer = device.GetLogical().CreateBuffer(buffer_ci); + // 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, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }); const auto requirements = device.GetLogical().GetBufferMemoryRequirements(*buffer); const u32 required_flags = requirements.memoryTypeBits; stream_buffer_size = static_cast<u64>(requirements.size); - VkMemoryAllocateInfo memory_ai; - memory_ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - memory_ai.pNext = nullptr; - memory_ai.allocationSize = requirements.size; - memory_ai.memoryTypeIndex = GetMemoryType(memory_properties, required_flags); - - memory = device.GetLogical().AllocateMemory(memory_ai); + memory = device.GetLogical().AllocateMemory({ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = nullptr, + .allocationSize = requirements.size, + .memoryTypeIndex = GetMemoryType(memory_properties, required_flags), + }); buffer.BindMemory(*memory, 0); } @@ -160,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..5e15ad78f 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.h +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h @@ -14,7 +14,6 @@ namespace Vulkan { class VKDevice; -class VKFence; class VKFenceWatch; class VKScheduler; @@ -44,8 +43,8 @@ public: } private: - struct Watch final { - VKFenceWatch fence; + struct Watch { + u64 tick{}; u64 upper_bound{}; }; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index bffd8f32a..9636a7c65 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -12,7 +12,7 @@ #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" @@ -56,8 +56,8 @@ 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 VKDevice& device_, VKScheduler& scheduler_) + : surface{surface_}, device{device_}, scheduler{scheduler_} {} VKSwapchain::~VKSwapchain() = default; @@ -75,35 +75,33 @@ 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()}; bool recreated = false; - VkPresentInfoKHR present_info; - present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - present_info.pNext = nullptr; - present_info.waitSemaphoreCount = render_semaphore ? 2U : 1U; - present_info.pWaitSemaphores = semaphores.data(); - present_info.swapchainCount = 1; - present_info.pSwapchains = swapchain.address(); - present_info.pImageIndices = &image_index; - present_info.pResults = nullptr; + const VkPresentInfoKHR present_info{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreCount = render_semaphore ? 2U : 1U, + .pWaitSemaphores = semaphores.data(), + .swapchainCount = 1, + .pSwapchains = swapchain.address(), + .pImageIndices = &image_index, + .pResults = nullptr, + }; switch (const VkResult result = present_queue.Present(present_info)) { case VK_SUCCESS: @@ -122,8 +120,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; } @@ -147,24 +144,26 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, requested_image_count = capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR swapchain_ci; - swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - swapchain_ci.pNext = nullptr; - swapchain_ci.flags = 0; - swapchain_ci.surface = surface; - swapchain_ci.minImageCount = requested_image_count; - swapchain_ci.imageFormat = surface_format.format; - swapchain_ci.imageColorSpace = surface_format.colorSpace; - swapchain_ci.imageArrayLayers = 1; - swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - swapchain_ci.queueFamilyIndexCount = 0; - swapchain_ci.pQueueFamilyIndices = nullptr; - swapchain_ci.preTransform = capabilities.currentTransform; - swapchain_ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - swapchain_ci.presentMode = present_mode; - swapchain_ci.clipped = VK_FALSE; - swapchain_ci.oldSwapchain = nullptr; + VkSwapchainCreateInfoKHR swapchain_ci{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .surface = surface, + .minImageCount = requested_image_count, + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = {}, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .preTransform = capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_FALSE, + .oldSwapchain = nullptr, + }; const u32 graphics_family{device.GetGraphicsFamily()}; const u32 present_family{device.GetPresentFamily()}; @@ -173,8 +172,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size()); swapchain_ci.pQueueFamilyIndices = queue_indices.data(); - } else { - swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } // Request the size again to reduce the possibility of a TOCTOU race condition. @@ -200,20 +197,29 @@ void VKSwapchain::CreateSemaphores() { } void VKSwapchain::CreateImageViews() { - VkImageViewCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - // ci.image - ci.viewType = VK_IMAGE_VIEW_TYPE_2D; - ci.format = image_format; - ci.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, - VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; - ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - ci.subresourceRange.baseMipLevel = 0; - ci.subresourceRange.levelCount = 1; - ci.subresourceRange.baseArrayLayer = 0; - ci.subresourceRange.layerCount = 1; + VkImageViewCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = {}, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = image_format, + .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, + }, + }; image_views.resize(image_count); for (std::size_t i = 0; i < image_count; i++) { diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index a35d61345..6b39befdf 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -16,11 +16,11 @@ struct FramebufferLayout; namespace Vulkan { class VKDevice; -class VKFence; +class VKScheduler; class VKSwapchain { public: - explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device); + explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device, VKScheduler& scheduler); ~VKSwapchain(); /// Creates (or recreates) the swapchain with a given size. @@ -31,7 +31,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; @@ -74,6 +74,7 @@ private: const VkSurfaceKHR surface; const VKDevice& device; + VKScheduler& scheduler; vk::SwapchainKHR swapchain; @@ -81,7 +82,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 bd93dcf20..f2c8f2ae1 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -95,17 +95,18 @@ VkImageViewType GetImageViewType(SurfaceTarget target) { 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 - VkBufferCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.size = static_cast<VkDeviceSize>(host_memory_size); - ci.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; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.queueFamilyIndexCount = 0; - ci.pQueueFamilyIndices = nullptr; - return device.GetLogical().CreateBuffer(ci); + return device.GetLogical().CreateBuffer({ + .sType = VK_STRUCTURE_TYPE_BUFFER_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, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }); } VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device, @@ -113,15 +114,16 @@ VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device, std::size_t host_memory_size) { ASSERT(params.IsBuffer()); - VkBufferViewCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.buffer = buffer; - ci.format = MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format; - ci.offset = 0; - ci.range = static_cast<VkDeviceSize>(host_memory_size); - return ci; + 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), + }; } VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceParams& params) { @@ -130,23 +132,24 @@ VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceP const auto [format, attachable, storage] = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.pixel_format); - VkImageCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.imageType = SurfaceTargetToImage(params.target); - ci.format = format; - ci.mipLevels = params.num_levels; - ci.arrayLayers = static_cast<u32>(params.GetNumLayers()); - ci.samples = VK_SAMPLE_COUNT_1_BIT; - ci.tiling = VK_IMAGE_TILING_OPTIMAL; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.queueFamilyIndexCount = 0; - ci.pQueueFamilyIndices = nullptr; - ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - ci.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + VkImageCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_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, + .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; @@ -185,12 +188,10 @@ u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::Swizzl } // Anonymous namespace -CachedSurface::CachedSurface(Core::System& system, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, +CachedSurface::CachedSurface(const VKDevice& device, 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}, + : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} { if (params.IsBuffer()) { buffer = CreateBuffer(device, params, host_memory_size); @@ -233,7 +234,7 @@ void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) { void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { UNIMPLEMENTED_IF(params.IsBuffer()); - if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5U) { + if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) { LOG_WARNING(Render_Vulkan, "A1B5G5R5 flushing is stubbed"); } @@ -321,22 +322,25 @@ void CachedSurface::UploadImage(const std::vector<u8>& staging_buffer) { } VkBufferImageCopy CachedSurface::GetBufferImageCopy(u32 level) const { - VkBufferImageCopy copy; - copy.bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted); - copy.bufferRowLength = 0; - copy.bufferImageHeight = 0; - copy.imageSubresource.aspectMask = image->GetAspectMask(); - copy.imageSubresource.mipLevel = level; - copy.imageSubresource.baseArrayLayer = 0; - copy.imageSubresource.layerCount = static_cast<u32>(params.GetNumLayers()); - copy.imageOffset.x = 0; - copy.imageOffset.y = 0; - copy.imageOffset.z = 0; - copy.imageExtent.width = params.GetMipWidth(level); - copy.imageExtent.height = params.GetMipHeight(level); - copy.imageExtent.depth = - params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1; - return copy; + return { + .bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted), + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = + { + .aspectMask = image->GetAspectMask(), + .mipLevel = level, + .baseArrayLayer = 0, + .layerCount = static_cast<u32>(params.GetNumLayers()), + }, + .imageOffset = {.x = 0, .y = 0, .z = 0}, + .imageExtent = + { + .width = params.GetMipWidth(level), + .height = params.GetMipHeight(level), + .depth = params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1U, + }, + }; } VkImageSubresourceRange CachedSurface::GetImageSubresourceRange() const { @@ -380,7 +384,7 @@ VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSourc 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::A1B5G5R5U) { + 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]); } @@ -392,11 +396,11 @@ VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSourc 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::Z24S8: - case VideoCore::Surface::PixelFormat::Z32FS8: + 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::S8Z24: + case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM: aspect = is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; break; default: @@ -416,20 +420,29 @@ VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSourc ASSERT(num_slices == params.depth); } - VkImageViewCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.image = surface.GetImageHandle(); - ci.viewType = image_view_type; - ci.format = surface.GetImage().GetFormat(); - ci.components = {swizzle[0], swizzle[1], swizzle[2], swizzle[3]}; - ci.subresourceRange.aspectMask = aspect; - ci.subresourceRange.baseMipLevel = base_level; - ci.subresourceRange.levelCount = num_levels; - ci.subresourceRange.baseArrayLayer = base_layer; - ci.subresourceRange.layerCount = num_layers; - image_view = device.GetLogical().CreateImageView(ci); + 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], + }, + .subresourceRange = + { + .aspectMask = aspect, + .baseMipLevel = base_level, + .levelCount = num_levels, + .baseArrayLayer = base_layer, + .layerCount = num_layers, + }, + }); return last_image_view = *image_view; } @@ -439,17 +452,29 @@ VkImageView CachedSurfaceView::GetAttachment() { return *render_target; } - VkImageViewCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.image = surface.GetImageHandle(); - ci.format = surface.GetImage().GetFormat(); - ci.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, - VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; - ci.subresourceRange.aspectMask = aspect_mask; - ci.subresourceRange.baseMipLevel = base_level; - ci.subresourceRange.levelCount = num_levels; + 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, + }, + .subresourceRange = + { + .aspectMask = aspect_mask, + .baseMipLevel = base_level, + .levelCount = num_levels, + .baseArrayLayer = 0, + .layerCount = 0, + }, + }; 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; @@ -463,19 +488,20 @@ VkImageView CachedSurfaceView::GetAttachment() { 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(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, + Tegra::MemoryManager& gpu_memory, const VKDevice& device_, + VKMemoryManager& memory_manager_, VKScheduler& scheduler_, + VKStagingBufferPool& staging_pool_) + : TextureCache(rasterizer, maxwell3d, gpu_memory, device_.IsOptimalAstcSupported()), + device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{ + staging_pool_} {} VKTextureCache::~VKTextureCache() = default; 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); + return std::make_shared<CachedSurface>(device, memory_manager, scheduler, staging_pool, + gpu_addr, params); } void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface, @@ -502,24 +528,40 @@ void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VkImageCopy copy; - copy.srcSubresource.aspectMask = src_surface->GetAspectMask(); - copy.srcSubresource.mipLevel = copy_params.source_level; - copy.srcSubresource.baseArrayLayer = copy_params.source_z; - copy.srcSubresource.layerCount = num_layers; - copy.srcOffset.x = copy_params.source_x; - copy.srcOffset.y = copy_params.source_y; - copy.srcOffset.z = 0; - copy.dstSubresource.aspectMask = dst_surface->GetAspectMask(); - copy.dstSubresource.mipLevel = copy_params.dest_level; - copy.dstSubresource.baseArrayLayer = dst_base_layer; - copy.dstSubresource.layerCount = num_layers; - copy.dstOffset.x = copy_params.dest_x; - copy.dstOffset.y = copy_params.dest_y; - copy.dstOffset.z = dst_offset_z; - copy.extent.width = copy_params.width; - copy.extent.height = copy_params.height; - copy.extent.depth = extent_z; + 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, + }, + }; const VkImage src_image = src_surface->GetImageHandle(); const VkImage dst_image = dst_surface->GetImageHandle(); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 807e26c8a..39202feba 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -15,10 +15,6 @@ #include "video_core/texture_cache/surface_base.h" #include "video_core/texture_cache/texture_cache.h" -namespace Core { -class System; -} - namespace VideoCore { class RasterizerInterface; } @@ -27,7 +23,6 @@ namespace Vulkan { class RasterizerVulkan; class VKDevice; -class VKResourceManager; class VKScheduler; class VKStagingBufferPool; @@ -45,8 +40,7 @@ class CachedSurface final : public VideoCommon::SurfaceBase<View> { friend CachedSurfaceView; public: - explicit CachedSurface(Core::System& system, const VKDevice& device, - VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + explicit CachedSurface(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler, VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr, const SurfaceParams& params); ~CachedSurface(); @@ -101,9 +95,7 @@ private: VkImageSubresourceRange GetImageSubresourceRange() const; - Core::System& system; const VKDevice& device; - VKResourceManager& resource_manager; VKMemoryManager& memory_manager; VKScheduler& scheduler; VKStagingBufferPool& staging_pool; @@ -201,10 +193,10 @@ private: class VKTextureCache final : public TextureCacheBase { public: - explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - const VKDevice& device, VKResourceManager& resource_manager, - VKMemoryManager& memory_manager, VKScheduler& scheduler, - VKStagingBufferPool& staging_pool); + explicit VKTextureCache(VideoCore::RasterizerInterface& rasterizer, + Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory, + const VKDevice& device, VKMemoryManager& memory_manager, + VKScheduler& scheduler, VKStagingBufferPool& staging_pool); ~VKTextureCache(); private: @@ -219,7 +211,6 @@ private: void BufferCopy(Surface& src_surface, Surface& dst_surface) override; const VKDevice& device; - VKResourceManager& resource_manager; VKMemoryManager& memory_manager; VKScheduler& scheduler; VKStagingBufferPool& staging_pool; diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp index 051298cc8..2598440fb 100644 --- a/src/video_core/renderer_vulkan/wrapper.cpp +++ b/src/video_core/renderer_vulkan/wrapper.cpp @@ -6,6 +6,7 @@ #include <exception> #include <memory> #include <optional> +#include <string_view> #include <utility> #include <vector> @@ -17,21 +18,42 @@ 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> @@ -148,6 +170,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetFenceStatus); X(vkGetImageMemoryRequirements); X(vkGetQueryPoolResults); + X(vkGetSemaphoreCounterValueKHR); X(vkMapMemory); X(vkQueueSubmit); X(vkResetFences); @@ -156,6 +179,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkUpdateDescriptorSetWithTemplateKHR); X(vkUpdateDescriptorSets); X(vkWaitForFences); + X(vkWaitSemaphoresKHR); #undef X } @@ -262,6 +286,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"; } @@ -377,24 +417,26 @@ VkResult Free(VkDevice device, VkCommandPool handle, Span<VkCommandBuffer> buffe Instance Instance::Create(Span<const char*> layers, Span<const char*> extensions, InstanceDispatch& dld) noexcept { - VkApplicationInfo application_info; - application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - application_info.pNext = nullptr; - application_info.pApplicationName = "yuzu Emulator"; - application_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); - application_info.pEngineName = "yuzu Emulator"; - application_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); - application_info.apiVersion = VK_API_VERSION_1_1; - - VkInstanceCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; - ci.pApplicationInfo = &application_info; - ci.enabledLayerCount = layers.size(); - ci.ppEnabledLayerNames = layers.data(); - ci.enabledExtensionCount = extensions.size(); - ci.ppEnabledExtensionNames = extensions.data(); + static constexpr 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, + }; + + const VkInstanceCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .pApplicationInfo = &application_info, + .enabledLayerCount = layers.size(), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = extensions.size(), + .ppEnabledExtensionNames = extensions.data(), + }; VkInstance instance; if (dld.vkCreateInstance(&ci, nullptr, &instance) != VK_SUCCESS) { @@ -425,19 +467,20 @@ std::optional<std::vector<VkPhysicalDevice>> Instance::EnumeratePhysicalDevices( DebugCallback Instance::TryCreateDebugCallback( PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept { - VkDebugUtilsMessengerCreateInfoEXT ci; - ci.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - ci.pNext = nullptr; - ci.flags = 0; - ci.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; - ci.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; - ci.pfnUserCallback = callback; - ci.pUserData = nullptr; + 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) { @@ -468,12 +511,13 @@ DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) c } CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLevel level) const { - VkCommandBufferAllocateInfo ai; - ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - ai.pNext = nullptr; - ai.commandPool = handle; - ai.level = level; - ai.commandBufferCount = static_cast<u32>(num_buffers); + const VkCommandBufferAllocateInfo ai{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = handle, + .level = level, + .commandBufferCount = static_cast<u32>(num_buffers), + }; std::unique_ptr buffers = std::make_unique<VkCommandBuffer[]>(num_buffers); switch (const VkResult result = dld->vkAllocateCommandBuffers(owner, &ai, buffers.get())) { @@ -497,17 +541,18 @@ std::vector<VkImage> SwapchainKHR::GetImages() const { Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci, Span<const char*> enabled_extensions, const void* next, DeviceDispatch& dld) noexcept { - VkDeviceCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - ci.pNext = next; - ci.flags = 0; - ci.queueCreateInfoCount = queues_ci.size(); - ci.pQueueCreateInfos = queues_ci.data(); - ci.enabledLayerCount = 0; - ci.ppEnabledLayerNames = nullptr; - ci.enabledExtensionCount = enabled_extensions.size(); - ci.ppEnabledExtensionNames = enabled_extensions.data(); - ci.pEnabledFeatures = nullptr; + const VkDeviceCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = next, + .flags = 0, + .queueCreateInfoCount = queues_ci.size(), + .pQueueCreateInfos = queues_ci.data(), + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, + .enabledExtensionCount = enabled_extensions.size(), + .ppEnabledExtensionNames = enabled_extensions.data(), + .pEnabledFeatures = nullptr, + }; VkDevice device; if (dld.vkCreateDevice(physical_device, &ci, nullptr, &device) != VK_SUCCESS) { @@ -548,11 +593,15 @@ ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const { } Semaphore Device::CreateSemaphore() const { - VkSemaphoreCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; + static constexpr VkSemaphoreCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .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); @@ -639,10 +688,12 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons } Event Device::CreateEvent() const { - VkEventCreateInfo ci; - ci.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO; - ci.pNext = nullptr; - ci.flags = 0; + static constexpr VkEventCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }; + VkEvent object; Check(dld->vkCreateEvent(handle, &ci, nullptr, &object)); return Event(object, handle, *dld); @@ -778,7 +829,7 @@ std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProp VK_SUCCESS) { return std::nullopt; } - return properties; + return std::move(properties); } std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties( diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h index 71daac9d7..234e01693 100644 --- a/src/video_core/renderer_vulkan/wrapper.h +++ b/src/video_core/renderer_vulkan/wrapper.h @@ -267,6 +267,7 @@ struct DeviceDispatch : public InstanceDispatch { PFN_vkGetFenceStatus vkGetFenceStatus; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; PFN_vkGetQueryPoolResults vkGetQueryPoolResults; + PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR; PFN_vkMapMemory vkMapMemory; PFN_vkQueueSubmit vkQueueSubmit; PFN_vkResetFences vkResetFences; @@ -275,6 +276,7 @@ struct DeviceDispatch : public InstanceDispatch { PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR; PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; PFN_vkWaitForFences vkWaitForFences; + PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR; }; /// Loads instance agnostic function pointers. @@ -550,7 +552,6 @@ 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>; @@ -582,7 +583,8 @@ public: /// Construct a queue handle. 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); } @@ -674,6 +676,44 @@ public: } }; +class Semaphore : public Handle<VkSemaphore, VkDevice, DeviceDispatch> { + using Handle<VkSemaphore, VkDevice, DeviceDispatch>::Handle; + +public: + [[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; @@ -694,6 +734,8 @@ public: Semaphore CreateSemaphore() const; + Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const; + Fence CreateFence(const VkFenceCreateInfo& ci) const; DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const; @@ -756,8 +798,8 @@ public: } VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size, - void* data, VkDeviceSize stride, VkQueryResultFlags flags) const - noexcept { + void* data, VkDeviceSize stride, + VkQueryResultFlags flags) const noexcept { return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride, flags); } @@ -849,8 +891,8 @@ public: dld->vkCmdBindPipeline(handle, bind_point, pipeline); } - void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) const - noexcept { + void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset, + VkIndexType index_type) const noexcept { dld->vkCmdBindIndexBuffer(handle, buffer, offset, index_type); } @@ -863,8 +905,8 @@ public: BindVertexBuffers(binding, 1, &buffer, &offset); } - void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex, u32 first_instance) const - noexcept { + void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex, + u32 first_instance) const noexcept { dld->vkCmdDraw(handle, vertex_count, instance_count, first_vertex, first_instance); } @@ -874,15 +916,15 @@ public: first_instance); } - void ClearAttachments(Span<VkClearAttachment> attachments, Span<VkClearRect> rects) const - noexcept { + void ClearAttachments(Span<VkClearAttachment> attachments, + Span<VkClearRect> rects) const noexcept { dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(), rects.data()); } void BlitImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image, - VkImageLayout dst_layout, Span<VkImageBlit> regions, VkFilter filter) const - noexcept { + VkImageLayout dst_layout, Span<VkImageBlit> regions, + VkFilter filter) const noexcept { dld->vkCmdBlitImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(), regions.data(), filter); } @@ -907,8 +949,8 @@ public: regions.data()); } - void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, Span<VkBufferCopy> regions) const - noexcept { + void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, + Span<VkBufferCopy> regions) const noexcept { dld->vkCmdCopyBuffer(handle, src_buffer, dst_buffer, regions.size(), regions.data()); } @@ -924,8 +966,8 @@ public: regions.data()); } - void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, u32 data) const - noexcept { + void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, + u32 data) const noexcept { dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data); } diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h index cca13bcde..8e5a22ab3 100644 --- a/src/video_core/shader/ast.h +++ b/src/video_core/shader/ast.h @@ -199,55 +199,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); } } diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp new file mode 100644 index 000000000..aabd62c5c --- /dev/null +++ b/src/video_core/shader/async_shaders.cpp @@ -0,0 +1,221 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <condition_variable> +#include <mutex> +#include <thread> +#include <vector> +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_shader_cache.h" +#include "video_core/shader/async_shaders.h" + +namespace VideoCommon::Shader { + +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); + + // If we already have workers queued, ignore + if (num_workers == worker_threads.size()) { + return; + } + + // If workers already exist, clear them + if (!worker_threads.empty()) { + FreeWorkers(); + } + + // 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())); + } +} + +void AsyncShaders::FreeWorkers() { + // Mark all threads to quit + is_thread_exiting.store(true); + cv.notify_all(); + for (auto& thread : worker_threads) { + thread.join(); + } + // Clear our shared contexts + context_list.clear(); + + // Clear our worker threads + worker_threads.clear(); +} + +void AsyncShaders::KillWorkers() { + is_thread_exiting.store(true); + for (auto& thread : worker_threads) { + thread.detach(); + } + // Clear our shared contexts + context_list.clear(); + + // Clear our worker threads + worker_threads.clear(); +} + +bool AsyncShaders::HasWorkQueued() const { + return !pending_queue.empty(); +} + +bool AsyncShaders::HasCompletedWork() const { + std::shared_lock lock{completed_mutex}; + return !finished_work.empty(); +} + +bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const { + const auto& regs = gpu.Maxwell3D().regs; + + // If something is using depth, we can assume that games are not rendering anything which will + // be used one time. + if (regs.zeta_enable) { + return true; + } + + // If games are using a small index count, we can assume these are full screen quads. Usually + // these shaders are only used once for building textures so we can assume they can't be built + // async + if (regs.index_array.count <= 6 || regs.vertex_buffer.count <= 6) { + return false; + } + + return true; +} + +std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() { + 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())); + finished_work.clear(); + } + return results; +} + +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{ + .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL, + .device = &device, + .shader_type = shader_type, + .uid = uid, + .code = std::move(code), + .code_b = std::move(code_b), + .main_offset = main_offset, + .compiler_settings = compiler_settings, + .registry = registry, + .cpu_address = cpu_addr, + }; + std::unique_lock lock(queue_mutex); + pending_queue.push(std::move(params)); + cv.notify_one(); +} + +void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, + const Vulkan::VKDevice& 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{ + .backend = Backend::Vulkan, + .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, + .key = key, + }; + + std::unique_lock lock(queue_mutex); + pending_queue.push(std::move(params)); + cv.notify_one(); +} + +void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) { + while (!is_thread_exiting.load(std::memory_order_relaxed)) { + std::unique_lock lock{queue_mutex}; + cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; }); + if (is_thread_exiting) { + return; + } + + // Partial lock to allow all threads to read at the same time + if (!HasWorkQueued()) { + continue; + } + // Another thread beat us, just unlock and wait for the next load + if (pending_queue.empty()) { + continue; + } + + // Pull work from queue + WorkerParams work = std::move(pending_queue.front()); + pending_queue.pop(); + lock.unlock(); + + if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) { + const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry); + const auto scope = context->Acquire(); + auto program = + OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry); + Result result{}; + result.backend = work.backend; + result.cpu_address = work.cpu_address; + result.uid = work.uid; + result.code = std::move(work.code); + result.code_b = std::move(work.code_b); + result.shader_type = work.shader_type; + + if (work.backend == Backend::OpenGL) { + result.program.opengl = std::move(program->source_program); + } else if (work.backend == Backend::GLASM) { + result.program.glasm = std::move(program->assembly_program); + } + + { + std::unique_lock complete_lock(completed_mutex); + finished_work.push_back(std::move(result)); + } + } 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.pp_cache->EmplacePipeline(std::move(pipeline)); + } + } +} + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h new file mode 100644 index 000000000..7a99e1dc5 --- /dev/null +++ b/src/video_core/shader/async_shaders.h @@ -0,0 +1,147 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <condition_variable> +#include <memory> +#include <shared_mutex> +#include <thread> + +// This header includes both Vulkan and OpenGL headers, this has to be fixed +// Unfortunately, including OpenGL will include Windows.h that defines macros that can cause issues. +// Forcefully include glad early and undefine macros +#include <glad/glad.h> +#ifdef CreateEvent +#undef CreateEvent +#endif +#ifdef CreateSemaphore +#undef CreateSemaphore +#endif + +#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" + +namespace Core::Frontend { +class EmuWindow; +class GraphicsContext; +} // namespace Core::Frontend + +namespace Tegra { +class GPU; +} + +namespace Vulkan { +class VKPipelineCache; +} + +namespace VideoCommon::Shader { + +class AsyncShaders { +public: + enum class Backend { + OpenGL, + GLASM, + Vulkan, + }; + + struct ResultPrograms { + OpenGL::OGLProgram opengl; + OpenGL::OGLAssemblyProgram glasm; + }; + + struct Result { + u64 uid; + VAddr cpu_address; + Backend backend; + ResultPrograms program; + std::vector<u64> code; + std::vector<u64> code_b; + Tegra::Engines::ShaderType shader_type; + }; + + explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window); + ~AsyncShaders(); + + /// Start up shader worker threads + void AllocateWorkers(); + + /// Clear the shader queue and kill all worker threads + void FreeWorkers(); + + // Force end all threads + void KillWorkers(); + + /// Check to see if any shaders have actually been compiled + [[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 + [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const; + + /// Pulls completed compiled shaders + [[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, + CompilerSettings compiler_settings, const Registry& registry, + VAddr cpu_addr); + + void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& 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); + +private: + void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); + + /// Check our worker queue to see if we have any work queued already + [[nodiscard]] bool HasWorkQueued() const; + + struct WorkerParams { + Backend backend; + // For OGL + const OpenGL::Device* device; + Tegra::Engines::ShaderType shader_type; + u64 uid; + std::vector<u64> code; + std::vector<u64> code_b; + u32 main_offset; + CompilerSettings compiler_settings; + std::optional<Registry> registry; + VAddr cpu_address; + + // For Vulkan + Vulkan::VKPipelineCache* pp_cache; + const Vulkan::VKDevice* 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; + }; + + std::condition_variable cv; + 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<Result> finished_work; + Core::Frontend::EmuWindow& emu_window; +}; + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp index 8d86020f6..4c8971615 100644 --- a/src/video_core/shader/control_flow.cpp +++ b/src/video_core/shader/control_flow.cpp @@ -187,24 +187,26 @@ std::optional<std::pair<BufferInfo, u64>> TrackLDC(const CFGRebuildState& state, std::optional<u64> TrackSHLRegister(const CFGRebuildState& state, u32& pos, u64 ldc_tracked_register) { - return TrackInstruction<u64>(state, pos, - [ldc_tracked_register](auto instr, const auto& opcode) { - return opcode.GetId() == OpCode::Id::SHL_IMM && - instr.gpr0.Value() == ldc_tracked_register; - }, - [](auto instr, const auto&) { return instr.gpr8.Value(); }); + return TrackInstruction<u64>( + state, pos, + [ldc_tracked_register](auto instr, const auto& opcode) { + return opcode.GetId() == OpCode::Id::SHL_IMM && + instr.gpr0.Value() == ldc_tracked_register; + }, + [](auto instr, const auto&) { return instr.gpr8.Value(); }); } std::optional<u32> TrackIMNMXValue(const CFGRebuildState& state, u32& pos, u64 shl_tracked_register) { - return TrackInstruction<u32>(state, pos, - [shl_tracked_register](auto instr, const auto& opcode) { - return opcode.GetId() == OpCode::Id::IMNMX_IMM && - instr.gpr0.Value() == shl_tracked_register; - }, - [](auto instr, const auto&) { - return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1); - }); + return TrackInstruction<u32>( + state, pos, + [shl_tracked_register](auto instr, const auto& opcode) { + return opcode.GetId() == OpCode::Id::IMNMX_IMM && + instr.gpr0.Value() == shl_tracked_register; + }, + [](auto instr, const auto&) { + return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1); + }); } std::optional<BranchIndirectInfo> TrackBranchIndirectInfo(const CFGRebuildState& state, u32 pos) { @@ -545,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(); @@ -565,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); } @@ -592,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; } @@ -603,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); @@ -617,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); 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 a041519b7..73155966f 100644 --- a/src/video_core/shader/decode/arithmetic_integer.cpp +++ b/src/video_core/shader/decode/arithmetic_integer.cpp @@ -98,12 +98,12 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { op_b = GetOperandAbsNegInteger(op_b, false, instr.iadd3.neg_b, true); op_c = GetOperandAbsNegInteger(op_c, false, instr.iadd3.neg_c, true); - const Node value = [&]() { - const Node add_ab = Operation(OperationCode::IAdd, NO_PRECISE, op_a, op_b); + const Node value = [&] { + Node add_ab = Operation(OperationCode::IAdd, NO_PRECISE, op_a, op_b); if (opcode->get().GetId() != OpCode::Id::IADD3_R) { return Operation(OperationCode::IAdd, NO_PRECISE, add_ab, op_c); } - const Node shifted = [&]() { + const Node shifted = [&] { switch (instr.iadd3.mode) { case Tegra::Shader::IAdd3Mode::RightShift: // TODO(tech4me): According to diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp index 73880db0e..2a30aab2b 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,14 +61,14 @@ 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: @@ -84,8 +87,8 @@ 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: diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index 07778dc3e..618d309d2 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -31,11 +31,11 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor, std::size_t component) { const TextureFormat format{descriptor.format}; switch (format) { - case TextureFormat::R16_G16_B16_A16: - case TextureFormat::R32_G32_B32_A32: - case TextureFormat::R32_G32_B32: - case TextureFormat::R32_G32: - case TextureFormat::R16_G16: + case TextureFormat::R16G16B16A16: + case TextureFormat::R32G32B32A32: + case TextureFormat::R32G32B32: + case TextureFormat::R32G32: + case TextureFormat::R16G16: case TextureFormat::R32: case TextureFormat::R16: case TextureFormat::R8: @@ -97,7 +97,7 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor, break; case TextureFormat::B5G6R5: case TextureFormat::B6G5R5: - case TextureFormat::BF10GF11RF11: + case TextureFormat::B10G11R11: if (component == 0) { return descriptor.b_type; } @@ -108,9 +108,9 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor, return descriptor.r_type; } break; - case TextureFormat::G8R24: - case TextureFormat::G24R8: - case TextureFormat::G8R8: + case TextureFormat::R24G8: + case TextureFormat::R8G24: + case TextureFormat::R8G8: case TextureFormat::G4R4: if (component == 0) { return descriptor.g_type; @@ -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; @@ -137,15 +139,15 @@ bool IsComponentEnabled(std::size_t component_mask, std::size_t component) { u32 GetComponentSize(TextureFormat format, std::size_t component) { switch (format) { - case TextureFormat::R32_G32_B32_A32: + case TextureFormat::R32G32B32A32: return 32; - case TextureFormat::R16_G16_B16_A16: + case TextureFormat::R16G16B16A16: return 16; - case TextureFormat::R32_G32_B32: + case TextureFormat::R32G32B32: return component <= 2 ? 32 : 0; - case TextureFormat::R32_G32: + case TextureFormat::R32G32: return component <= 1 ? 32 : 0; - case TextureFormat::R16_G16: + case TextureFormat::R16G16: return component <= 1 ? 16 : 0; case TextureFormat::R32: return component == 0 ? 32 : 0; @@ -192,7 +194,7 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) { return 6; } return 0; - case TextureFormat::BF10GF11RF11: + case TextureFormat::B10G11R11: if (component == 1 || component == 2) { return 11; } @@ -200,7 +202,7 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) { return 10; } return 0; - case TextureFormat::G8R24: + case TextureFormat::R24G8: if (component == 0) { return 8; } @@ -208,7 +210,7 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) { return 24; } return 0; - case TextureFormat::G24R8: + case TextureFormat::R8G24: if (component == 0) { return 8; } @@ -216,13 +218,14 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) { return 24; } return 0; - case TextureFormat::G8R8: + 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) { @@ -231,25 +234,25 @@ std::size_t GetImageComponentMask(TextureFormat format) { constexpr u8 B = 0b0100; constexpr u8 A = 0b1000; switch (format) { - case TextureFormat::R32_G32_B32_A32: - case TextureFormat::R16_G16_B16_A16: + case TextureFormat::R32G32B32A32: + case TextureFormat::R16G16B16A16: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::A4B4G4R4: case TextureFormat::A5B5G5R1: case TextureFormat::A1B5G5R5: return std::size_t{R | G | B | A}; - case TextureFormat::R32_G32_B32: + case TextureFormat::R32G32B32: case TextureFormat::R32_B24G8: case TextureFormat::B5G6R5: case TextureFormat::B6G5R5: - case TextureFormat::BF10GF11RF11: + case TextureFormat::B10G11R11: return std::size_t{R | G | B}; - case TextureFormat::R32_G32: - case TextureFormat::R16_G16: - case TextureFormat::G8R24: - case TextureFormat::G24R8: - case TextureFormat::G8R8: + case TextureFormat::R32G32: + case TextureFormat::R16G16: + case TextureFormat::R24G8: + case TextureFormat::R8G24: + case TextureFormat::R8G8: case TextureFormat::G4R4: return std::size_t{R | G}; case TextureFormat::R32: @@ -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) { @@ -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; } diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp index 63adbc4a3..e2bba88dd 100644 --- a/src/video_core/shader/decode/memory.cpp +++ b/src/video_core/shader/decode/memory.cpp @@ -386,7 +386,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::RED: { - UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32); + UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32, "type={}", + static_cast<int>(instr.red.type.Value())); const auto [real_address, base_address, descriptor] = TrackGlobalMemory(bb, instr, true, true); if (!real_address || !base_address) { @@ -471,9 +472,9 @@ std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackGlobalMemory(NodeBlock& const auto [base_address, index, offset] = TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size())); - ASSERT_OR_EXECUTE_MSG(base_address != nullptr, - { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); }, - "Global memory tracking failed"); + ASSERT_OR_EXECUTE_MSG( + base_address != nullptr, { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); }, + "Global memory tracking failed"); bb.push_back(Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", index, offset))); diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index c0a8f233f..29a7cfbfe 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -75,8 +75,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { const Node value = [this, instr] { switch (instr.sys20) { case SystemVariable::LaneId: - LOG_WARNING(HW_GPU, "S2R instruction with LaneId is incomplete"); - return Immediate(0U); + return Operation(OperationCode::ThreadId); case SystemVariable::InvocationId: return Operation(OperationCode::InvocationId); case SystemVariable::Ydirection: diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index 29ebf65ba..4e932a4b6 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp @@ -292,33 +292,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 {}", static_cast<int>(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)); @@ -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)}; diff --git a/src/video_core/shader/decode/video.cpp b/src/video_core/shader/decode/video.cpp index 64ba60ea2..1c0957277 100644 --- a/src/video_core/shader/decode/video.cpp +++ b/src/video_core/shader/decode/video.cpp @@ -91,29 +91,28 @@ u32 ShaderIR::DecodeVideo(NodeBlock& bb, u32 pc) { return pc; } -Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed, - Tegra::Shader::VideoType type, u64 byte_height) { +Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed, VideoType type, + u64 byte_height) { if (!is_chunk) { return BitfieldExtract(op, static_cast<u32>(byte_height * 8), 8); } - const Node zero = Immediate(0); switch (type) { - case Tegra::Shader::VideoType::Size16_Low: + case VideoType::Size16_Low: return BitfieldExtract(op, 0, 16); - case Tegra::Shader::VideoType::Size16_High: + case VideoType::Size16_High: return BitfieldExtract(op, 16, 16); - case Tegra::Shader::VideoType::Size32: + case VideoType::Size32: // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when this type is used // (1 * 1 + 0 == 0x5b800000). Until a better explanation is found: abort. UNIMPLEMENTED(); - return zero; - case Tegra::Shader::VideoType::Invalid: + return Immediate(0); + case VideoType::Invalid: UNREACHABLE_MSG("Invalid instruction encoding"); - return zero; + return Immediate(0); default: UNREACHABLE(); - return zero; + return Immediate(0); } } diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp index c83dc6615..233b8fa42 100644 --- a/src/video_core/shader/decode/xmad.cpp +++ b/src/video_core/shader/decode/xmad.cpp @@ -81,20 +81,21 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) { SetTemporary(bb, 0, product); product = GetTemporary(0); - const Node original_c = op_c; + Node original_c = op_c; const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error - op_c = [&]() { + op_c = [&] { switch (set_mode) { case Tegra::Shader::XmadMode::None: return original_c; case Tegra::Shader::XmadMode::CLo: - return BitfieldExtract(original_c, 0, 16); + return BitfieldExtract(std::move(original_c), 0, 16); case Tegra::Shader::XmadMode::CHi: - return BitfieldExtract(original_c, 16, 16); + return BitfieldExtract(std::move(original_c), 16, 16); case Tegra::Shader::XmadMode::CBcc: { - const Node shifted_b = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed_b, - original_b, Immediate(16)); - return SignedOperation(OperationCode::IAdd, is_signed_c, original_c, shifted_b); + Node shifted_b = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed_b, + original_b, Immediate(16)); + return SignedOperation(OperationCode::IAdd, is_signed_c, std::move(original_c), + std::move(shifted_b)); } case Tegra::Shader::XmadMode::CSfu: { const Node comp_a = 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/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 e322c3402..29d794b34 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -112,9 +112,9 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff } Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const { - const Node node = MakeNode<InternalFlagNode>(flag); + Node node = MakeNode<InternalFlagNode>(flag); if (negated) { - return Operation(OperationCode::LogicalNegate, node); + return Operation(OperationCode::LogicalNegate, std::move(node)); } return node; } 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/shader_notify.cpp b/src/video_core/shader_notify.cpp new file mode 100644 index 000000000..c3c71657d --- /dev/null +++ b/src/video_core/shader_notify.cpp @@ -0,0 +1,42 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/shader_notify.h" + +using namespace std::chrono_literals; + +namespace VideoCore { +namespace { +constexpr auto UPDATE_TICK = 32ms; +} + +ShaderNotify::ShaderNotify() = default; +ShaderNotify::~ShaderNotify() = default; + +std::size_t ShaderNotify::GetShadersBuilding() { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - last_update; + if (diff > UPDATE_TICK) { + std::shared_lock lock(mutex); + last_updated_count = accurate_count; + } + return last_updated_count; +} + +std::size_t ShaderNotify::GetShadersBuildingAccurate() { + std::shared_lock lock{mutex}; + return accurate_count; +} + +void ShaderNotify::MarkShaderComplete() { + std::unique_lock lock{mutex}; + accurate_count--; +} + +void ShaderNotify::MarkSharderBuilding() { + std::unique_lock lock{mutex}; + accurate_count++; +} + +} // namespace VideoCore diff --git a/src/video_core/shader_notify.h b/src/video_core/shader_notify.h new file mode 100644 index 000000000..a9c92d179 --- /dev/null +++ b/src/video_core/shader_notify.h @@ -0,0 +1,29 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <chrono> +#include <shared_mutex> +#include "common/common_types.h" + +namespace VideoCore { +class ShaderNotify { +public: + ShaderNotify(); + ~ShaderNotify(); + + std::size_t GetShadersBuilding(); + std::size_t GetShadersBuildingAccurate(); + + void MarkShaderComplete(); + void MarkSharderBuilding(); + +private: + std::size_t last_updated_count{}; + std::size_t accurate_count{}; + std::shared_mutex mutex; + std::chrono::high_resolution_clock::time_point last_update{}; +}; +} // namespace VideoCore diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index bbe93903c..1688267bb 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -74,117 +74,131 @@ bool SurfaceTargetIsArray(SurfaceTarget target) { PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) { switch (format) { - case Tegra::DepthFormat::S8_Z24_UNORM: - return PixelFormat::S8Z24; - case Tegra::DepthFormat::Z24_S8_UNORM: - return PixelFormat::Z24S8; - case Tegra::DepthFormat::Z32_FLOAT: - return PixelFormat::Z32F; - case Tegra::DepthFormat::Z16_UNORM: - return PixelFormat::Z16; - case Tegra::DepthFormat::Z32_S8_X24_FLOAT: - return PixelFormat::Z32FS8; + case Tegra::DepthFormat::S8_UINT_Z24_UNORM: + return PixelFormat::S8_UINT_D24_UNORM; + case Tegra::DepthFormat::D24S8_UNORM: + return PixelFormat::D24_UNORM_S8_UINT; + case Tegra::DepthFormat::D32_FLOAT: + return PixelFormat::D32_FLOAT; + case Tegra::DepthFormat::D16_UNORM: + return PixelFormat::D16_UNORM; + case Tegra::DepthFormat::D32_FLOAT_S8X24_UINT: + return PixelFormat::D32_FLOAT_S8_UINT; default: - LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); - UNREACHABLE(); - return PixelFormat::S8Z24; + UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); + return PixelFormat::S8_UINT_D24_UNORM; } } PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) { switch (format) { - case Tegra::RenderTargetFormat::RGBA8_SRGB: - return PixelFormat::RGBA8_SRGB; - case Tegra::RenderTargetFormat::RGBA8_UNORM: - return PixelFormat::ABGR8U; - case Tegra::RenderTargetFormat::RGBA8_SNORM: - return PixelFormat::ABGR8S; - case Tegra::RenderTargetFormat::RGBA8_UINT: - return PixelFormat::ABGR8UI; - case Tegra::RenderTargetFormat::BGRA8_SRGB: - return PixelFormat::BGRA8_SRGB; - case Tegra::RenderTargetFormat::BGRA8_UNORM: - return PixelFormat::BGRA8; - case Tegra::RenderTargetFormat::RGB10_A2_UNORM: - return PixelFormat::A2B10G10R10U; - case Tegra::RenderTargetFormat::RGBA16_FLOAT: - return PixelFormat::RGBA16F; - case Tegra::RenderTargetFormat::RGBA16_UNORM: - return PixelFormat::RGBA16U; - case Tegra::RenderTargetFormat::RGBA16_SNORM: - return PixelFormat::RGBA16S; - case Tegra::RenderTargetFormat::RGBA16_UINT: - return PixelFormat::RGBA16UI; - case Tegra::RenderTargetFormat::RGBA32_FLOAT: - return PixelFormat::RGBA32F; - case Tegra::RenderTargetFormat::RG32_FLOAT: - return PixelFormat::RG32F; - case Tegra::RenderTargetFormat::R11G11B10_FLOAT: - return PixelFormat::R11FG11FB10F; - case Tegra::RenderTargetFormat::B5G6R5_UNORM: - return PixelFormat::B5G6R5U; - case Tegra::RenderTargetFormat::BGR5A1_UNORM: - return PixelFormat::A1B5G5R5U; - case Tegra::RenderTargetFormat::RGBA32_UINT: - return PixelFormat::RGBA32UI; - case Tegra::RenderTargetFormat::R8_UNORM: - return PixelFormat::R8U; - case Tegra::RenderTargetFormat::R8_UINT: - return PixelFormat::R8UI; - case Tegra::RenderTargetFormat::RG16_FLOAT: - return PixelFormat::RG16F; - case Tegra::RenderTargetFormat::RG16_UINT: - return PixelFormat::RG16UI; - case Tegra::RenderTargetFormat::RG16_SINT: - return PixelFormat::RG16I; - case Tegra::RenderTargetFormat::RG16_UNORM: - return PixelFormat::RG16; - case Tegra::RenderTargetFormat::RG16_SNORM: - return PixelFormat::RG16S; - case Tegra::RenderTargetFormat::RG8_UNORM: - return PixelFormat::RG8U; - case Tegra::RenderTargetFormat::RG8_SNORM: - return PixelFormat::RG8S; - case Tegra::RenderTargetFormat::RG8_UINT: - return PixelFormat::RG8UI; - case Tegra::RenderTargetFormat::R16_FLOAT: - return PixelFormat::R16F; + case Tegra::RenderTargetFormat::R32B32G32A32_FLOAT: + return PixelFormat::R32G32B32A32_FLOAT; + case Tegra::RenderTargetFormat::R32G32B32A32_SINT: + return PixelFormat::R32G32B32A32_SINT; + case Tegra::RenderTargetFormat::R32G32B32A32_UINT: + return PixelFormat::R32G32B32A32_UINT; + case Tegra::RenderTargetFormat::R16G16B16A16_UNORM: + return PixelFormat::R16G16B16A16_UNORM; + case Tegra::RenderTargetFormat::R16G16B16A16_SNORM: + return PixelFormat::R16G16B16A16_SNORM; + case Tegra::RenderTargetFormat::R16G16B16A16_SINT: + return PixelFormat::R16G16B16A16_SINT; + case Tegra::RenderTargetFormat::R16G16B16A16_UINT: + return PixelFormat::R16G16B16A16_UINT; + case Tegra::RenderTargetFormat::R16G16B16A16_FLOAT: + return PixelFormat::R16G16B16A16_FLOAT; + case Tegra::RenderTargetFormat::R32G32_FLOAT: + return PixelFormat::R32G32_FLOAT; + case Tegra::RenderTargetFormat::R32G32_SINT: + return PixelFormat::R32G32_SINT; + case Tegra::RenderTargetFormat::R32G32_UINT: + return PixelFormat::R32G32_UINT; + case Tegra::RenderTargetFormat::R16G16B16X16_FLOAT: + return PixelFormat::R16G16B16X16_FLOAT; + case Tegra::RenderTargetFormat::B8G8R8A8_UNORM: + return PixelFormat::B8G8R8A8_UNORM; + case Tegra::RenderTargetFormat::B8G8R8A8_SRGB: + return PixelFormat::B8G8R8A8_SRGB; + case Tegra::RenderTargetFormat::A2B10G10R10_UNORM: + return PixelFormat::A2B10G10R10_UNORM; + case Tegra::RenderTargetFormat::A2B10G10R10_UINT: + return PixelFormat::A2B10G10R10_UINT; + case Tegra::RenderTargetFormat::A8B8G8R8_UNORM: + return PixelFormat::A8B8G8R8_UNORM; + case Tegra::RenderTargetFormat::A8B8G8R8_SRGB: + return PixelFormat::A8B8G8R8_SRGB; + case Tegra::RenderTargetFormat::A8B8G8R8_SNORM: + return PixelFormat::A8B8G8R8_SNORM; + case Tegra::RenderTargetFormat::A8B8G8R8_SINT: + return PixelFormat::A8B8G8R8_SINT; + case Tegra::RenderTargetFormat::A8B8G8R8_UINT: + return PixelFormat::A8B8G8R8_UINT; + case Tegra::RenderTargetFormat::R16G16_UNORM: + return PixelFormat::R16G16_UNORM; + case Tegra::RenderTargetFormat::R16G16_SNORM: + return PixelFormat::R16G16_SNORM; + case Tegra::RenderTargetFormat::R16G16_SINT: + return PixelFormat::R16G16_SINT; + case Tegra::RenderTargetFormat::R16G16_UINT: + return PixelFormat::R16G16_UINT; + case Tegra::RenderTargetFormat::R16G16_FLOAT: + return PixelFormat::R16G16_FLOAT; + case Tegra::RenderTargetFormat::B10G11R11_FLOAT: + return PixelFormat::B10G11R11_FLOAT; + case Tegra::RenderTargetFormat::R32_SINT: + return PixelFormat::R32_SINT; + case Tegra::RenderTargetFormat::R32_UINT: + return PixelFormat::R32_UINT; + case Tegra::RenderTargetFormat::R32_FLOAT: + return PixelFormat::R32_FLOAT; + case Tegra::RenderTargetFormat::R5G6B5_UNORM: + return PixelFormat::R5G6B5_UNORM; + case Tegra::RenderTargetFormat::A1R5G5B5_UNORM: + return PixelFormat::A1R5G5B5_UNORM; + case Tegra::RenderTargetFormat::R8G8_UNORM: + return PixelFormat::R8G8_UNORM; + case Tegra::RenderTargetFormat::R8G8_SNORM: + return PixelFormat::R8G8_SNORM; + case Tegra::RenderTargetFormat::R8G8_SINT: + return PixelFormat::R8G8_SINT; + case Tegra::RenderTargetFormat::R8G8_UINT: + return PixelFormat::R8G8_UINT; case Tegra::RenderTargetFormat::R16_UNORM: - return PixelFormat::R16U; + return PixelFormat::R16_UNORM; case Tegra::RenderTargetFormat::R16_SNORM: - return PixelFormat::R16S; - case Tegra::RenderTargetFormat::R16_UINT: - return PixelFormat::R16UI; + return PixelFormat::R16_SNORM; case Tegra::RenderTargetFormat::R16_SINT: - return PixelFormat::R16I; - case Tegra::RenderTargetFormat::R32_FLOAT: - return PixelFormat::R32F; - case Tegra::RenderTargetFormat::R32_SINT: - return PixelFormat::R32I; - case Tegra::RenderTargetFormat::R32_UINT: - return PixelFormat::R32UI; - case Tegra::RenderTargetFormat::RG32_UINT: - return PixelFormat::RG32UI; - case Tegra::RenderTargetFormat::RGBX16_FLOAT: - return PixelFormat::RGBX16F; + return PixelFormat::R16_SINT; + case Tegra::RenderTargetFormat::R16_UINT: + return PixelFormat::R16_UINT; + case Tegra::RenderTargetFormat::R16_FLOAT: + return PixelFormat::R16_FLOAT; + case Tegra::RenderTargetFormat::R8_UNORM: + return PixelFormat::R8_UNORM; + case Tegra::RenderTargetFormat::R8_SNORM: + return PixelFormat::R8_SNORM; + case Tegra::RenderTargetFormat::R8_SINT: + return PixelFormat::R8_SINT; + case Tegra::RenderTargetFormat::R8_UINT: + return PixelFormat::R8_UINT; default: - LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); - UNREACHABLE(); - return PixelFormat::RGBA8_SRGB; + UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<int>(format)); + return PixelFormat::A8B8G8R8_UNORM; } } PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) { switch (format) { - case Tegra::FramebufferConfig::PixelFormat::ABGR8: - return PixelFormat::ABGR8U; - case Tegra::FramebufferConfig::PixelFormat::RGB565: - return PixelFormat::B5G6R5U; - case Tegra::FramebufferConfig::PixelFormat::BGRA8: - return PixelFormat::BGRA8; + case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM: + return PixelFormat::A8B8G8R8_UNORM; + case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM: + return PixelFormat::R5G6B5_UNORM; + case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM: + return PixelFormat::B8G8R8A8_UNORM; default: UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); - return PixelFormat::ABGR8U; + return PixelFormat::A8B8G8R8_UNORM; } } @@ -212,27 +226,27 @@ SurfaceType GetFormatType(PixelFormat pixel_format) { bool IsPixelFormatASTC(PixelFormat format) { switch (format) { - case PixelFormat::ASTC_2D_4X4: - case PixelFormat::ASTC_2D_5X4: - case PixelFormat::ASTC_2D_5X5: - case PixelFormat::ASTC_2D_8X8: - case PixelFormat::ASTC_2D_8X5: + case PixelFormat::ASTC_2D_4X4_UNORM: + case PixelFormat::ASTC_2D_5X4_UNORM: + case PixelFormat::ASTC_2D_5X5_UNORM: + case PixelFormat::ASTC_2D_8X8_UNORM: + case PixelFormat::ASTC_2D_8X5_UNORM: case PixelFormat::ASTC_2D_4X4_SRGB: case PixelFormat::ASTC_2D_5X4_SRGB: case PixelFormat::ASTC_2D_5X5_SRGB: case PixelFormat::ASTC_2D_8X8_SRGB: case PixelFormat::ASTC_2D_8X5_SRGB: - case PixelFormat::ASTC_2D_10X8: + case PixelFormat::ASTC_2D_10X8_UNORM: case PixelFormat::ASTC_2D_10X8_SRGB: - case PixelFormat::ASTC_2D_6X6: + case PixelFormat::ASTC_2D_6X6_UNORM: case PixelFormat::ASTC_2D_6X6_SRGB: - case PixelFormat::ASTC_2D_10X10: + case PixelFormat::ASTC_2D_10X10_UNORM: case PixelFormat::ASTC_2D_10X10_SRGB: - case PixelFormat::ASTC_2D_12X12: + case PixelFormat::ASTC_2D_12X12_UNORM: case PixelFormat::ASTC_2D_12X12_SRGB: - case PixelFormat::ASTC_2D_8X6: + case PixelFormat::ASTC_2D_8X6_UNORM: case PixelFormat::ASTC_2D_8X6_SRGB: - case PixelFormat::ASTC_2D_6X5: + case PixelFormat::ASTC_2D_6X5_UNORM: case PixelFormat::ASTC_2D_6X5_SRGB: return true; default: @@ -242,12 +256,12 @@ bool IsPixelFormatASTC(PixelFormat format) { bool IsPixelFormatSRGB(PixelFormat format) { switch (format) { - case PixelFormat::RGBA8_SRGB: - case PixelFormat::BGRA8_SRGB: - case PixelFormat::DXT1_SRGB: - case PixelFormat::DXT23_SRGB: - case PixelFormat::DXT45_SRGB: - case PixelFormat::BC7U_SRGB: + 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: @@ -269,25 +283,4 @@ std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)}; } -bool IsFormatBCn(PixelFormat format) { - switch (format) { - case PixelFormat::DXT1: - case PixelFormat::DXT23: - case PixelFormat::DXT45: - case PixelFormat::DXN1: - case PixelFormat::DXN2SNORM: - case PixelFormat::DXN2UNORM: - case PixelFormat::BC7U: - case PixelFormat::BC6H_UF16: - case PixelFormat::BC6H_SF16: - case PixelFormat::DXT1_SRGB: - case PixelFormat::DXT23_SRGB: - case PixelFormat::DXT45_SRGB: - case PixelFormat::BC7U_SRGB: - return true; - default: - return false; - } -} - } // namespace VideoCore::Surface diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 6da6a1b97..cfd12fa61 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -15,94 +15,105 @@ namespace VideoCore::Surface { enum class PixelFormat { - ABGR8U = 0, - ABGR8S = 1, - ABGR8UI = 2, - B5G6R5U = 3, - A2B10G10R10U = 4, - A1B5G5R5U = 5, - R8U = 6, - R8UI = 7, - RGBA16F = 8, - RGBA16U = 9, - RGBA16S = 10, - RGBA16UI = 11, - R11FG11FB10F = 12, - RGBA32UI = 13, - DXT1 = 14, - DXT23 = 15, - DXT45 = 16, - DXN1 = 17, // This is also known as BC4 - DXN2UNORM = 18, - DXN2SNORM = 19, - BC7U = 20, - BC6H_UF16 = 21, - BC6H_SF16 = 22, - ASTC_2D_4X4 = 23, - BGRA8 = 24, - RGBA32F = 25, - RG32F = 26, - R32F = 27, - R16F = 28, - R16U = 29, - R16S = 30, - R16UI = 31, - R16I = 32, - RG16 = 33, - RG16F = 34, - RG16UI = 35, - RG16I = 36, - RG16S = 37, - RGB32F = 38, - RGBA8_SRGB = 39, - RG8U = 40, - RG8S = 41, - RG8UI = 42, - RG32UI = 43, - RGBX16F = 44, - R32UI = 45, - R32I = 46, - ASTC_2D_8X8 = 47, - ASTC_2D_8X5 = 48, - ASTC_2D_5X4 = 49, - BGRA8_SRGB = 50, - DXT1_SRGB = 51, - DXT23_SRGB = 52, - DXT45_SRGB = 53, - BC7U_SRGB = 54, - R4G4B4A4U = 55, - ASTC_2D_4X4_SRGB = 56, - ASTC_2D_8X8_SRGB = 57, - ASTC_2D_8X5_SRGB = 58, - ASTC_2D_5X4_SRGB = 59, - ASTC_2D_5X5 = 60, - ASTC_2D_5X5_SRGB = 61, - ASTC_2D_10X8 = 62, - ASTC_2D_10X8_SRGB = 63, - ASTC_2D_6X6 = 64, - ASTC_2D_6X6_SRGB = 65, - ASTC_2D_10X10 = 66, - ASTC_2D_10X10_SRGB = 67, - ASTC_2D_12X12 = 68, - ASTC_2D_12X12_SRGB = 69, - ASTC_2D_8X6 = 70, - ASTC_2D_8X6_SRGB = 71, - ASTC_2D_6X5 = 72, - ASTC_2D_6X5_SRGB = 73, - E5B9G9R9F = 74, + A8B8G8R8_UNORM, + A8B8G8R8_SNORM, + A8B8G8R8_SINT, + A8B8G8R8_UINT, + R5G6B5_UNORM, + B5G6R5_UNORM, + A1R5G5B5_UNORM, + A2B10G10R10_UNORM, + A2B10G10R10_UINT, + A1B5G5R5_UNORM, + R8_UNORM, + R8_SNORM, + R8_SINT, + R8_UINT, + R16G16B16A16_FLOAT, + R16G16B16A16_UNORM, + R16G16B16A16_SNORM, + R16G16B16A16_SINT, + R16G16B16A16_UINT, + B10G11R11_FLOAT, + R32G32B32A32_UINT, + BC1_RGBA_UNORM, + BC2_UNORM, + BC3_UNORM, + BC4_UNORM, + BC4_SNORM, + BC5_UNORM, + BC5_SNORM, + BC7_UNORM, + BC6H_UFLOAT, + BC6H_SFLOAT, + ASTC_2D_4X4_UNORM, + B8G8R8A8_UNORM, + R32G32B32A32_FLOAT, + R32G32B32A32_SINT, + R32G32_FLOAT, + R32G32_SINT, + R32_FLOAT, + R16_FLOAT, + R16_UNORM, + R16_SNORM, + R16_UINT, + R16_SINT, + R16G16_UNORM, + R16G16_FLOAT, + R16G16_UINT, + R16G16_SINT, + R16G16_SNORM, + R32G32B32_FLOAT, + A8B8G8R8_SRGB, + R8G8_UNORM, + R8G8_SNORM, + R8G8_SINT, + R8G8_UINT, + R32G32_UINT, + R16G16B16X16_FLOAT, + R32_UINT, + R32_SINT, + ASTC_2D_8X8_UNORM, + ASTC_2D_8X5_UNORM, + ASTC_2D_5X4_UNORM, + B8G8R8A8_SRGB, + BC1_RGBA_SRGB, + BC2_SRGB, + BC3_SRGB, + BC7_SRGB, + A4B4G4R4_UNORM, + ASTC_2D_4X4_SRGB, + ASTC_2D_8X8_SRGB, + ASTC_2D_8X5_SRGB, + ASTC_2D_5X4_SRGB, + ASTC_2D_5X5_UNORM, + ASTC_2D_5X5_SRGB, + ASTC_2D_10X8_UNORM, + ASTC_2D_10X8_SRGB, + ASTC_2D_6X6_UNORM, + ASTC_2D_6X6_SRGB, + ASTC_2D_10X10_UNORM, + ASTC_2D_10X10_SRGB, + ASTC_2D_12X12_UNORM, + ASTC_2D_12X12_SRGB, + ASTC_2D_8X6_UNORM, + ASTC_2D_8X6_SRGB, + ASTC_2D_6X5_UNORM, + ASTC_2D_6X5_SRGB, + E5B9G9R9_FLOAT, MaxColorFormat, // Depth formats - Z32F = 75, - Z16 = 76, + D32_FLOAT = MaxColorFormat, + D16_UNORM, MaxDepthFormat, // DepthStencil formats - Z24S8 = 77, - S8Z24 = 78, - Z32FS8 = 79, + D24_UNORM_S8_UINT = MaxDepthFormat, + S8_UINT_D24_UNORM, + D32_FLOAT_S8_UINT, MaxDepthStencilFormat, @@ -130,86 +141,97 @@ enum class SurfaceTarget { }; constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{ - 0, // ABGR8U - 0, // ABGR8S - 0, // ABGR8UI - 0, // B5G6R5U - 0, // A2B10G10R10U - 0, // A1B5G5R5U - 0, // R8U - 0, // R8UI - 0, // RGBA16F - 0, // RGBA16U - 0, // RGBA16S - 0, // RGBA16UI - 0, // R11FG11FB10F - 0, // RGBA32UI - 2, // DXT1 - 2, // DXT23 - 2, // DXT45 - 2, // DXN1 - 2, // DXN2UNORM - 2, // DXN2SNORM - 2, // BC7U - 2, // BC6H_UF16 - 2, // BC6H_SF16 - 2, // ASTC_2D_4X4 - 0, // BGRA8 - 0, // RGBA32F - 0, // RG32F - 0, // R32F - 0, // R16F - 0, // R16U - 0, // R16S - 0, // R16UI - 0, // R16I - 0, // RG16 - 0, // RG16F - 0, // RG16UI - 0, // RG16I - 0, // RG16S - 0, // RGB32F - 0, // RGBA8_SRGB - 0, // RG8U - 0, // RG8S - 0, // RG8UI - 0, // RG32UI - 0, // RGBX16F - 0, // R32UI - 0, // R32I - 2, // ASTC_2D_8X8 - 2, // ASTC_2D_8X5 - 2, // ASTC_2D_5X4 - 0, // BGRA8_SRGB - 2, // DXT1_SRGB - 2, // DXT23_SRGB - 2, // DXT45_SRGB - 2, // BC7U_SRGB - 0, // R4G4B4A4U + 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 + 2, // ASTC_2D_5X5_UNORM 2, // ASTC_2D_5X5_SRGB - 2, // ASTC_2D_10X8 + 2, // ASTC_2D_10X8_UNORM 2, // ASTC_2D_10X8_SRGB - 2, // ASTC_2D_6X6 + 2, // ASTC_2D_6X6_UNORM 2, // ASTC_2D_6X6_SRGB - 2, // ASTC_2D_10X10 + 2, // ASTC_2D_10X10_UNORM 2, // ASTC_2D_10X10_SRGB - 2, // ASTC_2D_12X12 + 2, // ASTC_2D_12X12_UNORM 2, // ASTC_2D_12X12_SRGB - 2, // ASTC_2D_8X6 + 2, // ASTC_2D_8X6_UNORM 2, // ASTC_2D_8X6_SRGB - 2, // ASTC_2D_6X5 + 2, // ASTC_2D_6X5_UNORM 2, // ASTC_2D_6X5_SRGB - 0, // E5B9G9R9F - 0, // Z32F - 0, // Z16 - 0, // Z24S8 - 0, // S8Z24 - 0, // Z32FS8 + 0, // E5B9G9R9_FLOAT + 0, // D32_FLOAT + 0, // D16_UNORM + 0, // D24_UNORM_S8_UINT + 0, // S8_UINT_D24_UNORM + 0, // D32_FLOAT_S8_UINT }}; /** @@ -229,86 +251,97 @@ inline constexpr u32 GetCompressionFactor(PixelFormat format) { } constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16S - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG8UI - 1, // RG32UI - 1, // RGBX16F - 1, // R32UI - 1, // R32I - 8, // ASTC_2D_8X8 - 8, // ASTC_2D_8X5 - 5, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 1, // R4G4B4A4U + 1, // A8B8G8R8_UNORM + 1, // A8B8G8R8_SNORM + 1, // A8B8G8R8_SINT + 1, // A8B8G8R8_UINT + 1, // R5G6B5_UNORM + 1, // B5G6R5_UNORM + 1, // A1R5G5B5_UNORM + 1, // A2B10G10R10_UNORM + 1, // A2B10G10R10_UINT + 1, // A1B5G5R5_UNORM + 1, // R8_UNORM + 1, // R8_SNORM + 1, // R8_SINT + 1, // R8_UINT + 1, // R16G16B16A16_FLOAT + 1, // R16G16B16A16_UNORM + 1, // R16G16B16A16_SNORM + 1, // R16G16B16A16_SINT + 1, // R16G16B16A16_UINT + 1, // B10G11R11_FLOAT + 1, // R32G32B32A32_UINT + 4, // BC1_RGBA_UNORM + 4, // BC2_UNORM + 4, // BC3_UNORM + 4, // BC4_UNORM + 4, // BC4_SNORM + 4, // BC5_UNORM + 4, // BC5_SNORM + 4, // BC7_UNORM + 4, // BC6H_UFLOAT + 4, // BC6H_SFLOAT + 4, // ASTC_2D_4X4_UNORM + 1, // B8G8R8A8_UNORM + 1, // R32G32B32A32_FLOAT + 1, // R32G32B32A32_SINT + 1, // R32G32_FLOAT + 1, // R32G32_SINT + 1, // R32_FLOAT + 1, // R16_FLOAT + 1, // R16_UNORM + 1, // R16_SNORM + 1, // R16_UINT + 1, // R16_SINT + 1, // R16G16_UNORM + 1, // R16G16_FLOAT + 1, // R16G16_UINT + 1, // R16G16_SINT + 1, // R16G16_SNORM + 1, // R32G32B32_FLOAT + 1, // A8B8G8R8_SRGB + 1, // R8G8_UNORM + 1, // R8G8_SNORM + 1, // R8G8_SINT + 1, // R8G8_UINT + 1, // R32G32_UINT + 1, // R16G16B16X16_FLOAT + 1, // R32_UINT + 1, // R32_SINT + 8, // ASTC_2D_8X8_UNORM + 8, // ASTC_2D_8X5_UNORM + 5, // ASTC_2D_5X4_UNORM + 1, // B8G8R8A8_SRGB + 4, // BC1_RGBA_SRGB + 4, // BC2_SRGB + 4, // BC3_SRGB + 4, // BC7_SRGB + 1, // A4B4G4R4_UNORM 4, // ASTC_2D_4X4_SRGB 8, // ASTC_2D_8X8_SRGB 8, // ASTC_2D_8X5_SRGB 5, // ASTC_2D_5X4_SRGB - 5, // ASTC_2D_5X5 + 5, // ASTC_2D_5X5_UNORM 5, // ASTC_2D_5X5_SRGB - 10, // ASTC_2D_10X8 + 10, // ASTC_2D_10X8_UNORM 10, // ASTC_2D_10X8_SRGB - 6, // ASTC_2D_6X6 + 6, // ASTC_2D_6X6_UNORM 6, // ASTC_2D_6X6_SRGB - 10, // ASTC_2D_10X10 + 10, // ASTC_2D_10X10_UNORM 10, // ASTC_2D_10X10_SRGB - 12, // ASTC_2D_12X12 + 12, // ASTC_2D_12X12_UNORM 12, // ASTC_2D_12X12_SRGB - 8, // ASTC_2D_8X6 + 8, // ASTC_2D_8X6_UNORM 8, // ASTC_2D_8X6_SRGB - 6, // ASTC_2D_6X5 + 6, // ASTC_2D_6X5_UNORM 6, // ASTC_2D_6X5_SRGB - 1, // E5B9G9R9F - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 + 1, // E5B9G9R9_FLOAT + 1, // D32_FLOAT + 1, // D16_UNORM + 1, // D24_UNORM_S8_UINT + 1, // S8_UINT_D24_UNORM + 1, // D32_FLOAT_S8_UINT }}; static constexpr u32 GetDefaultBlockWidth(PixelFormat format) { @@ -320,86 +353,97 @@ static constexpr u32 GetDefaultBlockWidth(PixelFormat format) { } constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16S - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG8UI - 1, // RG32UI - 1, // RGBX16F - 1, // R32UI - 1, // R32I - 8, // ASTC_2D_8X8 - 5, // ASTC_2D_8X5 - 4, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 1, // R4G4B4A4U + 1, // A8B8G8R8_UNORM + 1, // A8B8G8R8_SNORM + 1, // A8B8G8R8_SINT + 1, // A8B8G8R8_UINT + 1, // R5G6B5_UNORM + 1, // B5G6R5_UNORM + 1, // A1R5G5B5_UNORM + 1, // A2B10G10R10_UNORM + 1, // A2B10G10R10_UINT + 1, // A1B5G5R5_UNORM + 1, // R8_UNORM + 1, // R8_SNORM + 1, // R8_SINT + 1, // R8_UINT + 1, // R16G16B16A16_FLOAT + 1, // R16G16B16A16_UNORM + 1, // R16G16B16A16_SNORM + 1, // R16G16B16A16_SINT + 1, // R16G16B16A16_UINT + 1, // B10G11R11_FLOAT + 1, // R32G32B32A32_UINT + 4, // BC1_RGBA_UNORM + 4, // BC2_UNORM + 4, // BC3_UNORM + 4, // BC4_UNORM + 4, // BC4_SNORM + 4, // BC5_UNORM + 4, // BC5_SNORM + 4, // BC7_UNORM + 4, // BC6H_UFLOAT + 4, // BC6H_SFLOAT + 4, // ASTC_2D_4X4_UNORM + 1, // B8G8R8A8_UNORM + 1, // R32G32B32A32_FLOAT + 1, // R32G32B32A32_SINT + 1, // R32G32_FLOAT + 1, // R32G32_SINT + 1, // R32_FLOAT + 1, // R16_FLOAT + 1, // R16_UNORM + 1, // R16_SNORM + 1, // R16_UINT + 1, // R16_SINT + 1, // R16G16_UNORM + 1, // R16G16_FLOAT + 1, // R16G16_UINT + 1, // R16G16_SINT + 1, // R16G16_SNORM + 1, // R32G32B32_FLOAT + 1, // A8B8G8R8_SRGB + 1, // R8G8_UNORM + 1, // R8G8_SNORM + 1, // R8G8_SINT + 1, // R8G8_UINT + 1, // R32G32_UINT + 1, // R16G16B16X16_FLOAT + 1, // R32_UINT + 1, // R32_SINT + 8, // ASTC_2D_8X8_UNORM + 5, // ASTC_2D_8X5_UNORM + 4, // ASTC_2D_5X4_UNORM + 1, // B8G8R8A8_SRGB + 4, // BC1_RGBA_SRGB + 4, // BC2_SRGB + 4, // BC3_SRGB + 4, // BC7_SRGB + 1, // A4B4G4R4_UNORM 4, // ASTC_2D_4X4_SRGB 8, // ASTC_2D_8X8_SRGB 5, // ASTC_2D_8X5_SRGB 4, // ASTC_2D_5X4_SRGB - 5, // ASTC_2D_5X5 + 5, // ASTC_2D_5X5_UNORM 5, // ASTC_2D_5X5_SRGB - 8, // ASTC_2D_10X8 + 8, // ASTC_2D_10X8_UNORM 8, // ASTC_2D_10X8_SRGB - 6, // ASTC_2D_6X6 + 6, // ASTC_2D_6X6_UNORM 6, // ASTC_2D_6X6_SRGB - 10, // ASTC_2D_10X10 + 10, // ASTC_2D_10X10_UNORM 10, // ASTC_2D_10X10_SRGB - 12, // ASTC_2D_12X12 + 12, // ASTC_2D_12X12_UNORM 12, // ASTC_2D_12X12_SRGB - 6, // ASTC_2D_8X6 + 6, // ASTC_2D_8X6_UNORM 6, // ASTC_2D_8X6_SRGB - 5, // ASTC_2D_6X5 + 5, // ASTC_2D_6X5_UNORM 5, // ASTC_2D_6X5_SRGB - 1, // E5B9G9R9F - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 + 1, // E5B9G9R9_FLOAT + 1, // D32_FLOAT + 1, // D16_UNORM + 1, // D24_UNORM_S8_UINT + 1, // S8_UINT_D24_UNORM + 1, // D32_FLOAT_S8_UINT }}; static constexpr u32 GetDefaultBlockHeight(PixelFormat format) { @@ -411,86 +455,97 @@ static constexpr u32 GetDefaultBlockHeight(PixelFormat format) { } constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ - 32, // ABGR8U - 32, // ABGR8S - 32, // ABGR8UI - 16, // B5G6R5U - 32, // A2B10G10R10U - 16, // A1B5G5R5U - 8, // R8U - 8, // R8UI - 64, // RGBA16F - 64, // RGBA16U - 64, // RGBA16S - 64, // RGBA16UI - 32, // R11FG11FB10F - 128, // RGBA32UI - 64, // DXT1 - 128, // DXT23 - 128, // DXT45 - 64, // DXN1 - 128, // DXN2UNORM - 128, // DXN2SNORM - 128, // BC7U - 128, // BC6H_UF16 - 128, // BC6H_SF16 - 128, // ASTC_2D_4X4 - 32, // BGRA8 - 128, // RGBA32F - 64, // RG32F - 32, // R32F - 16, // R16F - 16, // R16U - 16, // R16S - 16, // R16UI - 16, // R16I - 32, // RG16 - 32, // RG16F - 32, // RG16UI - 32, // RG16I - 32, // RG16S - 96, // RGB32F - 32, // RGBA8_SRGB - 16, // RG8U - 16, // RG8S - 16, // RG8UI - 64, // RG32UI - 64, // RGBX16F - 32, // R32UI - 32, // R32I - 128, // ASTC_2D_8X8 - 128, // ASTC_2D_8X5 - 128, // ASTC_2D_5X4 - 32, // BGRA8_SRGB - 64, // DXT1_SRGB - 128, // DXT23_SRGB - 128, // DXT45_SRGB - 128, // BC7U - 16, // R4G4B4A4U + 32, // A8B8G8R8_UNORM + 32, // A8B8G8R8_SNORM + 32, // A8B8G8R8_SINT + 32, // A8B8G8R8_UINT + 16, // R5G6B5_UNORM + 16, // B5G6R5_UNORM + 16, // A1R5G5B5_UNORM + 32, // A2B10G10R10_UNORM + 32, // A2B10G10R10_UINT + 16, // A1B5G5R5_UNORM + 8, // R8_UNORM + 8, // R8_SNORM + 8, // R8_SINT + 8, // R8_UINT + 64, // R16G16B16A16_FLOAT + 64, // R16G16B16A16_UNORM + 64, // R16G16B16A16_SNORM + 64, // R16G16B16A16_SINT + 64, // R16G16B16A16_UINT + 32, // B10G11R11_FLOAT + 128, // R32G32B32A32_UINT + 64, // BC1_RGBA_UNORM + 128, // BC2_UNORM + 128, // BC3_UNORM + 64, // BC4_UNORM + 64, // BC4_SNORM + 128, // BC5_UNORM + 128, // BC5_SNORM + 128, // BC7_UNORM + 128, // BC6H_UFLOAT + 128, // BC6H_SFLOAT + 128, // ASTC_2D_4X4_UNORM + 32, // B8G8R8A8_UNORM + 128, // R32G32B32A32_FLOAT + 128, // R32G32B32A32_SINT + 64, // R32G32_FLOAT + 64, // R32G32_SINT + 32, // R32_FLOAT + 16, // R16_FLOAT + 16, // R16_UNORM + 16, // R16_SNORM + 16, // R16_UINT + 16, // R16_SINT + 32, // R16G16_UNORM + 32, // R16G16_FLOAT + 32, // R16G16_UINT + 32, // R16G16_SINT + 32, // R16G16_SNORM + 96, // R32G32B32_FLOAT + 32, // A8B8G8R8_SRGB + 16, // R8G8_UNORM + 16, // R8G8_SNORM + 16, // R8G8_SINT + 16, // R8G8_UINT + 64, // R32G32_UINT + 64, // R16G16B16X16_FLOAT + 32, // R32_UINT + 32, // R32_SINT + 128, // ASTC_2D_8X8_UNORM + 128, // ASTC_2D_8X5_UNORM + 128, // ASTC_2D_5X4_UNORM + 32, // B8G8R8A8_SRGB + 64, // BC1_RGBA_SRGB + 128, // BC2_SRGB + 128, // BC3_SRGB + 128, // BC7_UNORM + 16, // A4B4G4R4_UNORM 128, // ASTC_2D_4X4_SRGB 128, // ASTC_2D_8X8_SRGB 128, // ASTC_2D_8X5_SRGB 128, // ASTC_2D_5X4_SRGB - 128, // ASTC_2D_5X5 + 128, // ASTC_2D_5X5_UNORM 128, // ASTC_2D_5X5_SRGB - 128, // ASTC_2D_10X8 + 128, // ASTC_2D_10X8_UNORM 128, // ASTC_2D_10X8_SRGB - 128, // ASTC_2D_6X6 + 128, // ASTC_2D_6X6_UNORM 128, // ASTC_2D_6X6_SRGB - 128, // ASTC_2D_10X10 + 128, // ASTC_2D_10X10_UNORM 128, // ASTC_2D_10X10_SRGB - 128, // ASTC_2D_12X12 + 128, // ASTC_2D_12X12_UNORM 128, // ASTC_2D_12X12_SRGB - 128, // ASTC_2D_8X6 + 128, // ASTC_2D_8X6_UNORM 128, // ASTC_2D_8X6_SRGB - 128, // ASTC_2D_6X5 + 128, // ASTC_2D_6X5_UNORM 128, // ASTC_2D_6X5_SRGB - 32, // E5B9G9R9F - 32, // Z32F - 16, // Z16 - 32, // Z24S8 - 32, // S8Z24 - 64, // Z32FS8 + 32, // E5B9G9R9_FLOAT + 32, // D32_FLOAT + 16, // D16_UNORM + 32, // D24_UNORM_S8_UINT + 32, // S8_UINT_D24_UNORM + 64, // D32_FLOAT_S8_UINT }}; static constexpr u32 GetFormatBpp(PixelFormat format) { @@ -529,7 +584,4 @@ bool IsPixelFormatSRGB(PixelFormat format); std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); -/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN -bool IsFormatBCn(PixelFormat format); - } // namespace VideoCore::Surface diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index f476f03b0..7d5a75648 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -19,8 +19,6 @@ constexpr auto SNORM = ComponentType::SNORM; constexpr auto UNORM = ComponentType::UNORM; constexpr auto SINT = ComponentType::SINT; constexpr auto UINT = ComponentType::UINT; -constexpr auto SNORM_FORCE_FP16 = ComponentType::SNORM_FORCE_FP16; -constexpr auto UNORM_FORCE_FP16 = ComponentType::UNORM_FORCE_FP16; constexpr auto FLOAT = ComponentType::FLOAT; constexpr bool C = false; // Normal color constexpr bool S = true; // Srgb @@ -41,119 +39,126 @@ struct Table { ComponentType alpha_component; bool is_srgb; }; -constexpr std::array<Table, 78> DefinitionTable = {{ - {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ABGR8U}, - {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::ABGR8S}, - {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::ABGR8UI}, - {TextureFormat::A8R8G8B8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::RGBA8_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::B5G6R5U}, + {TextureFormat::B5G6R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::B5G6R5_UNORM}, - {TextureFormat::A2B10G10R10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A2B10G10R10U}, + {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::A1B5G5R5U}, + {TextureFormat::A1B5G5R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A1B5G5R5_UNORM}, - {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R4G4B4A4U}, + {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A4B4G4R4_UNORM}, - {TextureFormat::R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8U}, - {TextureFormat::R8, C, UINT, UINT, UINT, UINT, PixelFormat::R8UI}, + {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::G8R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RG8U}, - {TextureFormat::G8R8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RG8S}, - {TextureFormat::G8R8, C, UINT, UINT, UINT, UINT, PixelFormat::RG8UI}, + {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::R16_G16_B16_A16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RGBA16S}, - {TextureFormat::R16_G16_B16_A16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RGBA16U}, - {TextureFormat::R16_G16_B16_A16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGBA16F}, - {TextureFormat::R16_G16_B16_A16, C, UINT, UINT, UINT, UINT, PixelFormat::RGBA16UI}, + {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::R16_G16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RG16F}, - {TextureFormat::R16_G16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::RG16}, - {TextureFormat::R16_G16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::RG16S}, - {TextureFormat::R16_G16, C, UINT, UINT, UINT, UINT, PixelFormat::RG16UI}, - {TextureFormat::R16_G16, C, SINT, SINT, SINT, SINT, PixelFormat::RG16I}, + {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::R16F}, - {TextureFormat::R16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16U}, - {TextureFormat::R16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16S}, - {TextureFormat::R16, C, UINT, UINT, UINT, UINT, PixelFormat::R16UI}, - {TextureFormat::R16, C, SINT, SINT, SINT, SINT, PixelFormat::R16I}, + {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::BF10GF11RF11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R11FG11FB10F}, + {TextureFormat::B10G11R11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::B10G11R11_FLOAT}, - {TextureFormat::R32_G32_B32_A32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGBA32F}, - {TextureFormat::R32_G32_B32_A32, C, UINT, UINT, UINT, UINT, PixelFormat::RGBA32UI}, + {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::R32_G32_B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RGB32F}, + {TextureFormat::R32G32B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32_FLOAT}, - {TextureFormat::R32_G32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::RG32F}, - {TextureFormat::R32_G32, C, UINT, UINT, UINT, UINT, PixelFormat::RG32UI}, + {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::R32F}, - {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32UI}, - {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32I}, + {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_SHAREDEXP, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9F}, + {TextureFormat::E5B9G9R9, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9_FLOAT}, - {TextureFormat::ZF32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::Z32F}, - {TextureFormat::Z16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::Z16}, - {TextureFormat::S8Z24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8Z24}, - {TextureFormat::G24R8, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8Z24}, - {TextureFormat::ZF32_X24S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::Z32FS8}, + {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::DXT1, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1}, - {TextureFormat::DXT1, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1_SRGB}, + {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::DXT23, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT23}, - {TextureFormat::DXT23, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT23_SRGB}, + {TextureFormat::BC2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_UNORM}, + {TextureFormat::BC2, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_SRGB}, - {TextureFormat::DXT45, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT45}, - {TextureFormat::DXT45, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT45_SRGB}, + {TextureFormat::BC3, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_UNORM}, + {TextureFormat::BC3, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_SRGB}, - // TODO: Use a different pixel format for SNORM - {TextureFormat::DXN1, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXN1}, - {TextureFormat::DXN1, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::DXN1}, + {TextureFormat::BC4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC4_UNORM}, + {TextureFormat::BC4, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC4_SNORM}, - {TextureFormat::DXN2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXN2UNORM}, - {TextureFormat::DXN2, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::DXN2SNORM}, + {TextureFormat::BC5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC5_UNORM}, + {TextureFormat::BC5, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC5_SNORM}, - {TextureFormat::BC7U, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7U}, - {TextureFormat::BC7U, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7U_SRGB}, + {TextureFormat::BC7, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_UNORM}, + {TextureFormat::BC7, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_SRGB}, - {TextureFormat::BC6H_SF16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_SF16}, - {TextureFormat::BC6H_UF16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_UF16}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, + {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}, - {TextureFormat::ASTC_2D_6X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5}, + {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}, }}; @@ -184,7 +189,7 @@ PixelFormat FormatLookupTable::GetPixelFormat(TextureFormat format, bool is_srgb 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)); - return PixelFormat::ABGR8U; + return PixelFormat::A8B8G8R8_UNORM; } void FormatLookupTable::Set(TextureFormat format, bool is_srgb, ComponentType red_component, diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp index 0caf3b4f0..b44c09d71 100644 --- a/src/video_core/texture_cache/surface_base.cpp +++ b/src/video_core/texture_cache/surface_base.cpp @@ -115,20 +115,24 @@ std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap( if (gpu_addr == candidate_gpu_addr) { return {{0, 0}}; } + if (candidate_gpu_addr < gpu_addr) { - return {}; + return std::nullopt; } + 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 {}; + return std::nullopt; } + 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 {}; + return std::nullopt; } + const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))}; return std::make_pair(layer, level); } @@ -228,7 +232,7 @@ void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager, } } - if (!is_converted && params.pixel_format != PixelFormat::S8Z24) { + if (!is_converted && params.pixel_format != PixelFormat::S8_UINT_D24_UNORM) { return; } diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index 921562c1f..e8515321b 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp @@ -74,21 +74,21 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta 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.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::R16U: - case PixelFormat::R16F: - params.pixel_format = PixelFormat::Z16; + case PixelFormat::R16_UNORM: + case PixelFormat::R16_FLOAT: + params.pixel_format = PixelFormat::D16_UNORM; break; - case PixelFormat::R32F: - params.pixel_format = PixelFormat::Z32F; + case PixelFormat::R32_FLOAT: + params.pixel_format = PixelFormat::D32_FLOAT; break; default: UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}", @@ -96,7 +96,6 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta } params.type = GetFormatType(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; @@ -130,14 +129,13 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl 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.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.type = GetFormatType(params.pixel_format); params.target = ImageTypeToSurfaceTarget(entry.type); // TODO: on 1DBuffer we should use the tic info. if (tic.IsBuffer()) { @@ -165,38 +163,40 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl return params; } -SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) { - const auto& regs = system.GPU().Maxwell3D().regs; - SurfaceParams params; - params.is_tiled = regs.zeta.memory_layout.type == - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.srgb_conversion = false; - params.block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U); - params.block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U); - params.block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U); - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(regs.zeta.format); - params.type = GetFormatType(params.pixel_format); - params.width = regs.zeta_width; - params.height = regs.zeta_height; - params.pitch = 0; - params.num_levels = 1; - params.emulated_levels = 1; - - const bool is_layered = regs.zeta_layers > 1 && params.block_depth == 0; - params.is_layered = is_layered; - params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; - params.depth = is_layered ? regs.zeta_layers.Value() : 1U; - return params; +SurfaceParams SurfaceParams::CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d) { + const auto& regs = 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 SurfaceParams::CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d, + std::size_t index) { + const auto& config{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::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; + 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; @@ -233,24 +233,29 @@ SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::siz SurfaceParams SurfaceParams::CreateForFermiCopySurface( const Tegra::Engines::Fermi2D::Regs::Surface& config) { - SurfaceParams params{}; - params.is_tiled = !config.linear; - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; - params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 5U) : 0, - params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 5U) : 0, - params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 5U) : 0, - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.type = GetFormatType(params.pixel_format); - params.width = config.width; - params.height = config.height; - params.pitch = config.pitch; - // TODO(Rodrigo): Try to guess texture arrays from parameters - params.target = SurfaceTarget::Texture2D; - params.depth = 1; - params.num_levels = 1; - params.emulated_levels = 1; + 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; } diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index 118aa689e..4466c3c34 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -33,10 +33,11 @@ public: const VideoCommon::Shader::Image& entry); /// Creates SurfaceCachedParams for a depth buffer configuration. - static SurfaceParams CreateForDepthBuffer(Core::System& system); + static SurfaceParams CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d); /// Creates SurfaceCachedParams from a framebuffer configuration. - static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); + static SurfaceParams CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d, + std::size_t index); /// Creates SurfaceCachedParams from a Fermi2D surface configuration. static SurfaceParams CreateForFermiCopySurface( diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index cdcddb225..ea835c59f 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -135,8 +135,7 @@ public: return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); } @@ -160,8 +159,7 @@ public: if (!gpu_addr) { return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { return GetNullSurface(SurfaceParams::ExpectedTarget(entry)); } @@ -183,11 +181,11 @@ public: TView GetDepthBufferSurface(bool preserve_contents) { std::lock_guard lock{mutex}; - auto& maxwell3d = system.GPU().Maxwell3D(); - if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) { + auto& dirty = maxwell3d.dirty; + if (!dirty.flags[VideoCommon::Dirty::ZetaBuffer]) { return depth_buffer.view; } - maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false; + dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false; const auto& regs{maxwell3d.regs}; const auto gpu_addr{regs.zeta.Address()}; @@ -195,13 +193,12 @@ public: SetEmptyDepthBuffer(); return {}; } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { SetEmptyDepthBuffer(); return {}; } - const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)}; + const auto depth_params{SurfaceParams::CreateForDepthBuffer(maxwell3d)}; auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true); if (depth_buffer.target) depth_buffer.target->MarkAsRenderTarget(false, NO_RT); @@ -215,7 +212,6 @@ public: 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; } @@ -235,15 +231,14 @@ public: return {}; } - const std::optional<VAddr> cpu_addr = - system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { SetEmptyColorBuffer(index); return {}; } auto surface_view = - GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), + GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(maxwell3d, index), preserve_contents, true); if (render_targets[index].target) { auto& surface = render_targets[index].target; @@ -300,9 +295,8 @@ public: 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); + const std::optional<VAddr> dst_cpu_addr = gpu_memory.GpuToCpuAddress(dst_gpu_addr); + const std::optional<VAddr> src_cpu_addr = gpu_memory.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); @@ -358,9 +352,11 @@ public: } protected: - explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - bool is_astc_supported) - : system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} { + explicit TextureCache(VideoCore::RasterizerInterface& rasterizer_, + Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_, + bool is_astc_supported_) + : is_astc_supported{is_astc_supported_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, + gpu_memory{gpu_memory_} { for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { SetEmptyColorBuffer(i); } @@ -373,9 +369,9 @@ protected: siblings_table[static_cast<std::size_t>(b)] = a; }; std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid); - make_siblings(PixelFormat::Z16, PixelFormat::R16U); - make_siblings(PixelFormat::Z32F, PixelFormat::R32F); - make_siblings(PixelFormat::Z32FS8, PixelFormat::RG32F); + 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); sampled_textures.reserve(64); } @@ -395,7 +391,7 @@ protected: virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0; void ManageRenderTargetUnregister(TSurface& surface) { - auto& dirty = system.GPU().Maxwell3D().dirty; + auto& dirty = maxwell3d.dirty; const u32 index = surface->GetRenderTarget(); if (index == DEPTH_RT) { dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true; @@ -408,8 +404,7 @@ protected: 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); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}", gpu_addr); @@ -459,7 +454,6 @@ protected: return new_surface; } - Core::System& system; const bool is_astc_supported; private: @@ -954,8 +948,7 @@ private: * @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); + const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); if (!cpu_addr) { Deduction result{}; @@ -1031,7 +1024,7 @@ private: params.pitch = 4; params.num_levels = 1; params.emulated_levels = 1; - params.pixel_format = VideoCore::Surface::PixelFormat::R8U; + 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); @@ -1112,7 +1105,7 @@ private: void LoadSurface(const TSurface& surface) { staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); - surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache); + surface->LoadBuffer(gpu_memory, staging_cache); surface->UploadTexture(staging_cache.GetBuffer(0)); surface->MarkAsModified(false, Tick()); } @@ -1123,7 +1116,7 @@ private: } staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); surface->DownloadTexture(staging_cache.GetBuffer(0)); - surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache); + surface->FlushBuffer(gpu_memory, staging_cache); surface->MarkAsModified(false, Tick()); } @@ -1253,6 +1246,8 @@ private: } VideoCore::RasterizerInterface& rasterizer; + Tegra::Engines::Maxwell3D& maxwell3d; + Tegra::MemoryManager& gpu_memory; FormatLookupTable format_lookup_table; FormatCompatibility format_compatibility; diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp index f3efa7eb0..962921483 100644 --- a/src/video_core/textures/convert.cpp +++ b/src/video_core/textures/convert.cpp @@ -35,7 +35,7 @@ void SwapS8Z24ToZ24S8(u8* data, u32 width, u32 height) { S8Z24 s8z24_pixel{}; Z24S8 z24s8_pixel{}; constexpr auto bpp{ - VideoCore::Surface::GetBytesPerPixel(VideoCore::Surface::PixelFormat::S8Z24)}; + 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)}; @@ -73,7 +73,7 @@ void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format, 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::S8Z24) { + } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) { Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height); } } @@ -85,7 +85,7 @@ void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 h static_cast<u32>(pixel_format)); UNREACHABLE(); - } else if (convert_s8z24 && pixel_format == PixelFormat::S8Z24) { + } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) { Tegra::Texture::ConvertZ24S8ToS8Z24(data, width, height); } } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 98beabef1..16d46a018 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -184,53 +184,6 @@ void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, } } -u32 BytesPerPixel(TextureFormat format) { - switch (format) { - case TextureFormat::DXT1: - case TextureFormat::DXN1: - // In this case a 'pixel' actually refers to a 4x4 tile. - return 8; - case TextureFormat::DXT23: - case TextureFormat::DXT45: - case TextureFormat::DXN2: - case TextureFormat::BC7U: - case TextureFormat::BC6H_UF16: - case TextureFormat::BC6H_SF16: - // In this case a 'pixel' actually refers to a 4x4 tile. - return 16; - case TextureFormat::R32_G32_B32: - return 12; - case TextureFormat::ASTC_2D_4X4: - case TextureFormat::ASTC_2D_5X4: - case TextureFormat::ASTC_2D_8X8: - case TextureFormat::ASTC_2D_8X5: - case TextureFormat::ASTC_2D_10X8: - case TextureFormat::ASTC_2D_5X5: - case TextureFormat::A8R8G8B8: - case TextureFormat::A2B10G10R10: - case TextureFormat::BF10GF11RF11: - case TextureFormat::R32: - case TextureFormat::R16_G16: - return 4; - case TextureFormat::A1B5G5R5: - case TextureFormat::B5G6R5: - case TextureFormat::G8R8: - case TextureFormat::R16: - return 2; - case TextureFormat::R8: - return 1; - case TextureFormat::R16_G16_B16_A16: - return 8; - case TextureFormat::R32_G32_B32_A32: - return 16; - case TextureFormat::R32_G32: - return 8; - default: - UNIMPLEMENTED_MSG("Format not implemented"); - return 1; - } -} - 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) { @@ -275,24 +228,30 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 } } -void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, - u32 block_height_bit, u32 offset_x, u32 offset_y) { - const u32 block_height = 1U << block_height_bit; - for (u32 line = 0; line < subrect_height; ++line) { - const u32 y2 = line + offset_y; - const u32 gob_address_y = (y2 / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height + - ((y2 % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; - const auto& table = LEGACY_SWIZZLE_TABLE[y2 % GOB_SIZE_Y]; - for (u32 x = 0; x < subrect_width; ++x) { - const u32 x2 = (x + offset_x) * bytes_per_pixel; - const u32 gob_address = gob_address_y + (x2 / GOB_SIZE_X) * GOB_SIZE * block_height; - const u32 swizzled_offset = gob_address + table[x2 % GOB_SIZE_X]; - const u32 unswizzled_offset = line * dest_pitch + x * bytes_per_pixel; - u8* dest_line = unswizzled_data + unswizzled_offset; - u8* source_addr = swizzled_data + swizzled_offset; +void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel, + u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) { + const u32 stride = width * bytes_per_pixel; + const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X; + const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height); + + const u32 block_height_mask = (1U << block_height) - 1; + const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height; - std::memcpy(dest_line, source_addr, bytes_per_pixel); + 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 u32 block_y = src_y >> GOB_SIZE_Y_SHIFT; + const u32 src_offset_y = (block_y >> block_height) * block_size + + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); + for (u32 column = 0; column < line_length_in; ++column) { + const u32 src_x = (column + origin_x) * bytes_per_pixel; + const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift; + + const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X]; + const u32 unswizzled_offset = line * pitch + column * bytes_per_pixel; + + std::memcpy(output + unswizzled_offset, input + swizzled_offset, bytes_per_pixel); } } } @@ -308,7 +267,7 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); const u32 block_height_mask = (1U << block_height) - 1; - const u32 x_shift = Common::CountTrailingZeroes32(GOB_SIZE << (block_height + block_depth)); + 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]; @@ -348,48 +307,6 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 } } -std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, - u32 height) { - std::vector<u8> rgba_data; - - // TODO(Subv): Implement. - switch (format) { - case TextureFormat::DXT1: - case TextureFormat::DXT23: - case TextureFormat::DXT45: - case TextureFormat::DXN1: - case TextureFormat::DXN2: - case TextureFormat::BC7U: - case TextureFormat::BC6H_UF16: - case TextureFormat::BC6H_SF16: - case TextureFormat::ASTC_2D_4X4: - case TextureFormat::ASTC_2D_8X8: - case TextureFormat::ASTC_2D_5X5: - case TextureFormat::ASTC_2D_10X8: - case TextureFormat::A8R8G8B8: - case TextureFormat::A2B10G10R10: - case TextureFormat::A1B5G5R5: - case TextureFormat::B5G6R5: - case TextureFormat::R8: - case TextureFormat::G8R8: - case TextureFormat::BF10GF11RF11: - case TextureFormat::R32_G32_B32_A32: - case TextureFormat::R32_G32: - case TextureFormat::R32: - case TextureFormat::R16: - case TextureFormat::R16_G16: - case TextureFormat::R32_G32_B32: - // TODO(Subv): For the time being just forward the same data without any decoding. - rgba_data = texture_data; - break; - default: - UNIMPLEMENTED_MSG("Format not implemented"); - break; - } - - return rgba_data; -} - std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { if (tiled) { diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 232b696b3..01e156bc8 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -38,10 +38,6 @@ 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); -/// Decodes an unswizzled texture into a A8R8G8B8 texture. -std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, - u32 height); - /// 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, u32 block_height, u32 block_depth); @@ -52,9 +48,8 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 u32 block_height_bit, u32 offset_x, u32 offset_y); /// Copies a tiled subrectangle into a linear surface. -void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, - u32 offset_x, u32 offset_y); +void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel, + u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input); /// @brief Swizzles a 2D array of pixels into a 3D texture /// @param line_length_in Number of pixels per line diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index eba05aced..0574fef12 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -12,10 +12,10 @@ namespace Tegra::Texture { enum class TextureFormat : u32 { - R32_G32_B32_A32 = 0x01, - R32_G32_B32 = 0x02, - R16_G16_B16_A16 = 0x03, - R32_G32 = 0x04, + R32G32B32A32 = 0x01, + R32G32B32 = 0x02, + R16G16B16A16 = 0x03, + R32G32 = 0x04, R32_B24G8 = 0x05, ETC2_RGB = 0x06, X8B8G8R8 = 0x07, @@ -23,19 +23,19 @@ enum class TextureFormat : u32 { A2B10G10R10 = 0x09, ETC2_RGB_PTA = 0x0a, ETC2_RGBA = 0x0b, - R16_G16 = 0x0c, - G8R24 = 0x0d, - G24R8 = 0x0e, + R16G16 = 0x0c, + R24G8 = 0x0d, + R8G24 = 0x0e, R32 = 0x0f, - BC6H_SF16 = 0x10, - BC6H_UF16 = 0x11, + BC6H_SFLOAT = 0x10, + BC6H_UFLOAT = 0x11, A4B4G4R4 = 0x12, A5B5G5R1 = 0x13, A1B5G5R5 = 0x14, B5G6R5 = 0x15, B6G5R5 = 0x16, - BC7U = 0x17, - G8R8 = 0x18, + BC7 = 0x17, + R8G8 = 0x18, EAC = 0x19, EACX2 = 0x1a, R16 = 0x1b, @@ -43,23 +43,23 @@ enum class TextureFormat : u32 { R8 = 0x1d, G4R4 = 0x1e, R1 = 0x1f, - E5B9G9R9_SHAREDEXP = 0x20, - BF10GF11RF11 = 0x21, + E5B9G9R9 = 0x20, + B10G11R11 = 0x21, G8B8G8R8 = 0x22, B8G8R8G8 = 0x23, - DXT1 = 0x24, - DXT23 = 0x25, - DXT45 = 0x26, - DXN1 = 0x27, - DXN2 = 0x28, - S8Z24 = 0x29, + BC1_RGBA = 0x24, + BC2 = 0x25, + BC3 = 0x26, + BC4 = 0x27, + BC5 = 0x28, + S8D24 = 0x29, X8Z24 = 0x2a, - Z24S8 = 0x2b, + D24S8 = 0x2b, X4V4Z24__COV4R4V = 0x2c, X4V4Z24__COV8R8V = 0x2d, V8Z24__COV4R12V = 0x2e, - ZF32 = 0x2f, - ZF32_X24S8 = 0x30, + D32 = 0x2f, + D32S8 = 0x30, X8Z24_X20V4S8__COV4R4V = 0x31, X8Z24_X20V4S8__COV8R8V = 0x32, ZF32_X20V4X8__COV4R4V = 0x33, @@ -69,7 +69,7 @@ enum class TextureFormat : u32 { X8Z24_X16V8S8__COV4R12V = 0x37, ZF32_X16V8X8__COV4R12V = 0x38, ZF32_X16V8S8__COV4R12V = 0x39, - Z16 = 0x3a, + D16 = 0x3a, V8Z24__COV8R24V = 0x3b, X8Z24_X16V8S8__COV8R24V = 0x3c, ZF32_X16V8X8__COV8R24V = 0x3d, @@ -375,7 +375,4 @@ struct FullTextureInfo { TSCEntry tsc; }; -/// Returns the number of bytes per pixel of the input texture format. -u32 BytesPerPixel(TextureFormat format); - } // namespace Tegra::Texture diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 45f360bdd..a14df06a3 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> + #include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" @@ -16,37 +17,49 @@ #include "video_core/video_core.h" namespace { -std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window, - Core::System& system, - Core::Frontend::GraphicsContext& context) { + +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>(emu_window, system, context); + return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory, + gpu, std::move(context)); #ifdef HAS_VULKAN case Settings::RendererBackend::Vulkan: - return std::make_unique<Vulkan::RendererVulkan>(emu_window, system); + return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory, + gpu, std::move(context)); #endif default: return nullptr; } } + } // Anonymous namespace 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); + } + auto context = emu_window.CreateSharedContext(); const auto scope = context->Acquire(); - auto renderer = CreateRenderer(emu_window, system, *context); + + auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context)); if (!renderer->Init()) { return nullptr; } - if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { - return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer), - std::move(context)); - } - return std::make_unique<VideoCommon::GPUSynch>(system, std::move(renderer), std::move(context)); + gpu->BindRenderer(std::move(renderer)); + return gpu; } u16 GetResolutionScaleFactor(const RendererBase& renderer) { diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index 06ab7c59d..7e484b906 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(web_service STATIC verify_login.h web_backend.cpp web_backend.h + web_result.h ) create_target_directory_groups(web_service) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 7a480e33c..6215c914f 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -4,12 +4,14 @@ #include <nlohmann/json.hpp> #include "common/detached_tasks.h" -#include "common/web_result.h" #include "web_service/telemetry_json.h" #include "web_service/web_backend.h" +#include "web_service/web_result.h" namespace WebService { +namespace Telemetry = Common::Telemetry; + struct TelemetryJson::Impl { Impl(std::string host, std::string username, std::string token) : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {} @@ -123,7 +125,7 @@ bool TelemetryJson::SubmitTestcase() { Client client(impl->host, impl->username, impl->token); auto value = client.PostJson("/gamedb/testcase", content, false); - return value.result_code == Common::WebResult::Code::Success; + return value.result_code == WebResult::Code::Success; } } // namespace WebService diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index dfd202829..df51e00f8 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -14,25 +14,25 @@ namespace WebService { * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the * yuzu web service */ -class TelemetryJson : public Telemetry::VisitorInterface { +class TelemetryJson : public Common::Telemetry::VisitorInterface { public: TelemetryJson(std::string host, std::string username, std::string token); ~TelemetryJson() override; - void Visit(const Telemetry::Field<bool>& field) override; - void Visit(const Telemetry::Field<double>& field) override; - void Visit(const Telemetry::Field<float>& field) override; - void Visit(const Telemetry::Field<u8>& field) override; - void Visit(const Telemetry::Field<u16>& field) override; - void Visit(const Telemetry::Field<u32>& field) override; - void Visit(const Telemetry::Field<u64>& field) override; - void Visit(const Telemetry::Field<s8>& field) override; - void Visit(const Telemetry::Field<s16>& field) override; - void Visit(const Telemetry::Field<s32>& field) override; - void Visit(const Telemetry::Field<s64>& field) override; - void Visit(const Telemetry::Field<std::string>& field) override; - void Visit(const Telemetry::Field<const char*>& field) override; - void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; + void Visit(const Common::Telemetry::Field<bool>& field) override; + void Visit(const Common::Telemetry::Field<double>& field) override; + void Visit(const Common::Telemetry::Field<float>& field) override; + void Visit(const Common::Telemetry::Field<u8>& field) override; + void Visit(const Common::Telemetry::Field<u16>& field) override; + void Visit(const Common::Telemetry::Field<u32>& field) override; + void Visit(const Common::Telemetry::Field<u64>& field) override; + void Visit(const Common::Telemetry::Field<s8>& field) override; + void Visit(const Common::Telemetry::Field<s16>& field) override; + void Visit(const Common::Telemetry::Field<s32>& field) override; + void Visit(const Common::Telemetry::Field<s64>& field) override; + void Visit(const Common::Telemetry::Field<std::string>& field) override; + void Visit(const Common::Telemetry::Field<const char*>& field) override; + void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override; void Complete() override; bool SubmitTestcase() override; diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp index bfaa5b70a..ceb55ca6b 100644 --- a/src/web_service/verify_login.cpp +++ b/src/web_service/verify_login.cpp @@ -3,9 +3,9 @@ // Refer to the license.txt file included. #include <nlohmann/json.hpp> -#include "common/web_result.h" #include "web_service/verify_login.h" #include "web_service/web_backend.h" +#include "web_service/web_result.h" namespace WebService { diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 09d1651ac..74e287045 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -6,13 +6,14 @@ #include <cstdlib> #include <mutex> #include <string> + #include <LUrlParser.h> #include <fmt/format.h> #include <httplib.h> -#include "common/common_types.h" + #include "common/logging/log.h" -#include "common/web_result.h" #include "web_service/web_backend.h" +#include "web_service/web_result.h" namespace WebService { @@ -33,17 +34,16 @@ struct Client::Impl { } /// A generic function handles POST, GET and DELETE request together - Common::WebResult GenericRequest(const std::string& method, const std::string& path, - const std::string& data, bool allow_anonymous, - const std::string& accept) { + WebResult GenericRequest(const std::string& method, const std::string& path, + const std::string& data, bool allow_anonymous, + const std::string& accept) { if (jwt.empty()) { UpdateJWT(); } if (jwt.empty() && !allow_anonymous) { LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); - return Common::WebResult{Common::WebResult::Code::CredentialsMissing, - "Credentials needed", ""}; + return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""}; } auto result = GenericRequest(method, path, data, accept, jwt); @@ -62,10 +62,10 @@ struct Client::Impl { * username + token is used if jwt is empty but username and token are * not empty anonymous if all of jwt, username and token are empty */ - Common::WebResult GenericRequest(const std::string& method, const std::string& path, - const std::string& data, const std::string& accept, - const std::string& jwt = "", const std::string& username = "", - const std::string& token = "") { + WebResult GenericRequest(const std::string& method, const std::string& path, + const std::string& data, const std::string& accept, + const std::string& jwt = "", const std::string& username = "", + const std::string& token = "") { if (cli == nullptr) { auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); int port; @@ -81,12 +81,12 @@ struct Client::Impl { cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port); } else { LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme", ""}; + return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""}; } } if (cli == nullptr) { LOG_ERROR(WebService, "Invalid URL {}", host + path); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL", ""}; + return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""}; } cli->set_timeout_sec(TIMEOUT_SECONDS); @@ -106,7 +106,7 @@ struct Client::Impl { std::string(API_VERSION.begin(), API_VERSION.end())); if (method != "GET") { params.emplace(std::string("Content-Type"), std::string("application/json")); - }; + } httplib::Request request; request.method = method; @@ -118,29 +118,28 @@ struct Client::Impl { if (!cli->send(request, response)) { LOG_ERROR(WebService, "{} to {} returned null", method, host + path); - return Common::WebResult{Common::WebResult::Code::LibError, "Null response", ""}; + return WebResult{WebResult::Code::LibError, "Null response", ""}; } if (response.status >= 400) { LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, response.status); - return Common::WebResult{Common::WebResult::Code::HttpError, - std::to_string(response.status), ""}; + return WebResult{WebResult::Code::HttpError, std::to_string(response.status), ""}; } auto content_type = response.headers.find("content-type"); if (content_type == response.headers.end()) { LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); - return Common::WebResult{Common::WebResult::Code::WrongContent, "", ""}; + return WebResult{WebResult::Code::WrongContent, "", ""}; } if (content_type->second.find(accept) == std::string::npos) { LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, content_type->second); - return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content", ""}; + return WebResult{WebResult::Code::WrongContent, "Wrong content", ""}; } - return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; + return WebResult{WebResult::Code::Success, "", response.body}; } // Retrieve a new JWT from given username and token @@ -150,7 +149,7 @@ struct Client::Impl { } auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token); - if (result.result_code != Common::WebResult::Code::Success) { + if (result.result_code != WebResult::Code::Success) { LOG_ERROR(WebService, "UpdateJWT failed"); } else { std::lock_guard lock{jwt_cache.mutex}; @@ -180,29 +179,28 @@ Client::Client(std::string host, std::string username, std::string token) Client::~Client() = default; -Common::WebResult Client::PostJson(const std::string& path, const std::string& data, - bool allow_anonymous) { +WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) { return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json"); } -Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { +WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json"); } -Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data, - bool allow_anonymous) { +WebResult Client::DeleteJson(const std::string& path, const std::string& data, + bool allow_anonymous) { return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json"); } -Common::WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) { +WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) { return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain"); } -Common::WebResult Client::GetImage(const std::string& path, bool allow_anonymous) { +WebResult Client::GetImage(const std::string& path, bool allow_anonymous) { return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png"); } -Common::WebResult Client::GetExternalJWT(const std::string& audience) { +WebResult Client::GetExternalJWT(const std::string& audience) { return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false, "text/html"); } diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h index 04121f17e..81f58583c 100644 --- a/src/web_service/web_backend.h +++ b/src/web_service/web_backend.h @@ -7,12 +7,10 @@ #include <memory> #include <string> -namespace Common { -struct WebResult; -} - namespace WebService { +struct WebResult; + class Client { public: Client(std::string host, std::string username, std::string token); @@ -25,8 +23,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult PostJson(const std::string& path, const std::string& data, - bool allow_anonymous); + WebResult PostJson(const std::string& path, const std::string& data, bool allow_anonymous); /** * Gets JSON from the specified path. @@ -34,7 +31,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult GetJson(const std::string& path, bool allow_anonymous); + WebResult GetJson(const std::string& path, bool allow_anonymous); /** * Deletes JSON to the specified path. @@ -43,8 +40,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult DeleteJson(const std::string& path, const std::string& data, - bool allow_anonymous); + WebResult DeleteJson(const std::string& path, const std::string& data, bool allow_anonymous); /** * Gets a plain string from the specified path. @@ -52,7 +48,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult GetPlain(const std::string& path, bool allow_anonymous); + WebResult GetPlain(const std::string& path, bool allow_anonymous); /** * Gets an PNG image from the specified path. @@ -60,14 +56,14 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult GetImage(const std::string& path, bool allow_anonymous); + WebResult GetImage(const std::string& path, bool allow_anonymous); /** * Requests an external JWT for the specific audience provided. * @param audience the audience of the JWT requested. * @return the result of the request. */ - Common::WebResult GetExternalJWT(const std::string& audience); + WebResult GetExternalJWT(const std::string& audience); private: struct Impl; diff --git a/src/common/web_result.h b/src/web_service/web_result.h index 8bfa2141d..3aeeb5288 100644 --- a/src/common/web_result.h +++ b/src/web_service/web_result.h @@ -7,7 +7,7 @@ #include <string> #include "common/common_types.h" -namespace Common { +namespace WebService { struct WebResult { enum class Code : u32 { Success, @@ -22,4 +22,4 @@ struct WebResult { std::string result_string; std::string returned_data; }; -} // namespace Common +} // namespace WebService diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index a862b2610..cc0291b15 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 @@ -39,6 +42,9 @@ add_executable(yuzu configuration/configure_debug.cpp configuration/configure_debug.h configuration/configure_debug.ui + configuration/configure_debug_controller.cpp + configuration/configure_debug_controller.h + configuration/configure_debug_controller.ui configuration/configure_dialog.cpp configuration/configure_dialog.h configuration/configure_filesystem.cpp @@ -59,12 +65,18 @@ add_executable(yuzu configuration/configure_input.cpp configuration/configure_input.h configuration/configure_input.ui + configuration/configure_input_advanced.cpp + configuration/configure_input_advanced.h + configuration/configure_input_advanced.ui + configuration/configure_input_dialog.cpp + configuration/configure_input_dialog.h + configuration/configure_input_dialog.ui configuration/configure_input_player.cpp configuration/configure_input_player.h configuration/configure_input_player.ui - configuration/configure_input_simple.cpp - configuration/configure_input_simple.h - configuration/configure_input_simple.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 @@ -83,9 +95,13 @@ 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 @@ -133,11 +149,44 @@ file(GLOB COMPAT_LIST file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) +if (ENABLE_QT_TRANSLATION) + set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") + option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) + + # Update source TS file if enabled + if (GENERATE_QT_TRANSLATION) + get_target_property(SRCS yuzu SOURCES) + qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts) + add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts) + endif() + + # Find all TS files except en.ts + file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts) + list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts) + + # Compile TS files to QM files + qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + + # Build a QRC file from the QM file list + set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) + file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n") + foreach (QM ${LANGUAGES_QM}) + get_filename_component(QM_FILE ${QM} NAME) + file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n") + endforeach (QM) + file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>") + + # Add the QRC file to package in all QM files + qt5_add_resources(LANGUAGES ${LANGUAGES_QRC}) +else() + set(LANGUAGES) +endif() target_sources(yuzu PRIVATE ${COMPAT_LIST} ${ICONS} + ${LANGUAGES} ${THEMES} ) diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp new file mode 100644 index 000000000..9d45f2a01 --- /dev/null +++ b/src/yuzu/applets/controller.cpp @@ -0,0 +1,601 @@ +// 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 "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_dialog.h" +#include "yuzu/main.h" + +namespace { + +constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{ + {1, 0, 0, 0}, + {1, 1, 0, 0}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {0, 1, 1, 0}, +}}; + +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; + default: + return false; + } +} + +/// 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; + } +} + +} // 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_) { + 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(); + 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](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + CheckIfParametersMet(); + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +void QtControllerSelectorDialog::ApplyConfiguration() { + // Update the controller state once more, just to be sure they are properly applied. + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + UpdateControllerState(index); + } + + 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); + + Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto connected = Settings::values.players[index].connected || + (index == 0 && Settings::values.players[8].connected); + player_groupboxes[index]->setChecked(connected); + connected_controller_checkboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex( + GetIndexFromControllerType(Settings::values.players[index].controller_type)); + } + + UpdateDockedState(Settings::values.players[8].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); +} + +void QtControllerSelectorDialog::CallConfigureInputDialog() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + ConfigureInputDialog dialog(this, max_supported_players, input_subsystem); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + dialog.ApplyConfiguration(); + + LoadConfiguration(); + CheckIfParametersMet(); +} + +void 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; + } + + // 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()), + 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; + }(); + + if (!all_controllers_compatible) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return; + } + + parameters_met = true; + ui->buttonBox->setEnabled(parameters_met); +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [this] { + 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) { + 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::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())) { + case Settings::ControllerType::ProController: + 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{}; + } + }(); + + const QString theme = [this] { + 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[player_index]; + + player.controller_type = + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()); + player.connected = player_groupboxes[player_index]->isChecked(); + + // Player 2-8 + if (player_index != 0) { + UpdateController(player.controller_type, player_index, player.connected); + return; + } + + // Player 1 and Handheld + auto& handheld = Settings::values.players[8]; + // If Handheld is selected, copy all the settings from Player 1 to Handheld. + if (player.controller_type == Settings::ControllerType::Handheld) { + handheld = player; + handheld.connected = player_groupboxes[player_index]->isChecked(); + player.connected = false; // Disconnect Player 1 + } else { + player.connected = player_groupboxes[player_index]->isChecked(); + handheld.connected = false; // Disconnect Handheld + } + + UpdateController(player.controller_type, player_index, player.connected); + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) == + 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); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); + + // 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[index].connected = false; + UpdateController(Settings::values.players[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, Core::Frontend::ControllerParameters parameters) const { + this->callback = std::move(callback); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> 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..2d6d588c6 --- /dev/null +++ b/src/yuzu/applets/controller.h @@ -0,0 +1,133 @@ +// 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; + +namespace InputCommon { +class InputSubsystem; +} + +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; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + // Initializes the "Configure Input" Dialog. + void CallConfigureInputDialog(); + + // Checks the current configuration against the given parameters and + // sets the value of parameters_met. + void CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // 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; + + // 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; + + // 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, + Core::Frontend::ControllerParameters parameters) const override; + +signals: + void MainWindowReconfigureControllers(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..c4108a979 --- /dev/null +++ b/src/yuzu/applets/controller.ui @@ -0,0 +1,2672 @@ +<?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"> + <property name="editable"> + <bool>false</bool> + </property> + <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>6</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="QSpinBox" name="vibrationSpin"> + <property name="minimumSize"> + <size> + <width>65</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>65</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> + <property name="value"> + <number>100</number> + </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>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="inputConfigGroup"> + <property name="title"> + <string>Input Config</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>65</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Open</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> + <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/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index 4bc8ee726..dca8835ed 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp @@ -26,7 +26,7 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) { } QString GetImagePath(Common::UUID uuid) { - const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; return QString::fromStdString(path); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 5738787ac..408eac2b7 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -30,6 +30,7 @@ #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" @@ -63,7 +64,8 @@ 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.CurrentProcess()->GetTitleID(), stop_run, + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); }); @@ -216,15 +218,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,20 +236,8 @@ 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 @@ -304,8 +285,9 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* return wsi; } -GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_) - : QWidget(parent_), emu_thread(emu_thread_) { +GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_) + : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} { setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -314,15 +296,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_) auto layout = new QHBoxLayout(this); layout->setMargin(0); setLayout(layout); - InputCommon::Init(); + input_subsystem->Initialize(); this->setMouseTracking(true); - connect(this, &GRenderWindow::FirstFrameDisplayed, parent_, &GMainWindow::OnLoadComplete); + connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); } GRenderWindow::~GRenderWindow() { - InputCommon::Shutdown(); + input_subsystem->Shutdown(); } void GRenderWindow::PollEvents() { @@ -391,11 +373,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { } void GRenderWindow::keyPressEvent(QKeyEvent* event) { - InputCommon::GetKeyboard()->PressKey(event->key()); + input_subsystem->GetKeyboard()->PressKey(event->key()); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - InputCommon::GetKeyboard()->ReleaseKey(event->key()); + input_subsystem->GetKeyboard()->ReleaseKey(event->key()); } void GRenderWindow::mousePressEvent(QMouseEvent* event) { @@ -409,7 +391,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) { const auto [x, y] = ScaleTouch(pos); this->TouchPressed(x, y); } else if (event->button() == Qt::RightButton) { - InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); + input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y()); } QWidget::mousePressEvent(event); } @@ -423,7 +405,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { auto pos = event->pos(); const auto [x, y] = ScaleTouch(pos); this->TouchMoved(x, y); - InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); + input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y()); QWidget::mouseMoveEvent(event); } @@ -436,7 +418,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { this->TouchReleased(); } else if (event->button() == Qt::RightButton) { - InputCommon::GetMotionEmu()->EndTilt(); + input_subsystem->GetMotionEmu()->EndTilt(); } } @@ -451,7 +433,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { int active_points = 0; // average all active touch points - for (const auto tp : event->touchPoints()) { + for (const auto& tp : event->touchPoints()) { if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { active_points++; pos += tp.pos(); @@ -485,7 +467,7 @@ bool GRenderWindow::event(QEvent* event) { void GRenderWindow::focusOutEvent(QFocusEvent* event) { QWidget::focusOutEvent(event); - InputCommon::GetKeyboard()->ReleaseAllKeys(); + input_subsystem->GetKeyboard()->ReleaseAllKeys(); } void GRenderWindow::resizeEvent(QResizeEvent* event) { @@ -567,7 +549,7 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); renderer.RequestScreenshot( screenshot_image.bits(), - [=] { + [=, this] { const std::string std_screenshot_path = screenshot_path.toStdString(); if (screenshot_image.mirrored(false, true).save(screenshot_path)) { LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 6c59b4d5c..ca35cf831 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -6,6 +6,7 @@ #include <atomic> #include <condition_variable> +#include <memory> #include <mutex> #include <QImage> @@ -23,6 +24,10 @@ class QKeyEvent; class QTouchEvent; class QStringList; +namespace InputCommon { +class InputSubsystem; +} + namespace VideoCore { enum class LoadCallbackStage; } @@ -121,7 +126,8 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: - GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); + explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_); ~GRenderWindow() override; // EmuWindow implementation. @@ -183,6 +189,7 @@ private: QStringList GetUnsupportedGLExtensions() const; EmuThread* emu_thread; + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; // Main context that will be shared with all other contexts that are requested. // If this is used in a shared context setting, then this should not be used directly, but diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 5477f050c..649912557 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -54,7 +54,8 @@ void CompatDB::Submit() { back(); LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); Core::System::GetInstance().TelemetrySession().AddField( - Telemetry::FieldType::UserFeedback, "Compatibility", compatibility->checkedId()); + Common::Telemetry::FieldType::UserFeedback, "Compatibility", + compatibility->checkedId()); button(NextButton)->setEnabled(false); button(NextButton)->setText(tr("Submitting")); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d25b99a32..d2913d613 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -6,17 +6,18 @@ #include <QKeySequence> #include <QSettings> #include "common/file_util.h" -#include "configure_input_simple.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" #include "input_common/udp/client.h" #include "yuzu/configuration/config.h" +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 = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file; - FileUtil::CreateFullPath(qt_config_loc); + 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; @@ -30,29 +31,36 @@ Config::~Config() { } const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { - Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q, - Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T, - Qt::Key_H, Qt::Key_G, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right, Qt::Key_Down, Qt::Key_J, - Qt::Key_I, Qt::Key_L, Qt::Key_K, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, + Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q, + Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T, + Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, }; -const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ +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, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, - Qt::Key_E, }, { Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, - Qt::Key_R, }, }}; +const std::array<int, 2> Config::default_stick_mod = { + Qt::Key_E, + Qt::Key_R, +}; + const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons = { Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, @@ -241,10 +249,10 @@ void Config::ReadPlayerValues() { player.connected = ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); - player.type = static_cast<Settings::ControllerType>( + player.controller_type = static_cast<Settings::ControllerType>( qt_config ->value(QStringLiteral("player_%1_type").arg(p), - static_cast<u8>(Settings::ControllerType::DualJoycon)) + static_cast<u8>(Settings::ControllerType::ProController)) .toUInt()); player.body_color_left = qt_config @@ -281,10 +289,26 @@ void Config::ReadPlayerValues() { } } + 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("player_%1_").arg(p) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = 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_analogs[i][4], 0.5f); + default_analogs[i][3], default_stick_mod[i], 0.5f); auto& player_analogs = player.analogs[i]; player_analogs = qt_config @@ -298,12 +322,6 @@ void Config::ReadPlayerValues() { } } } - - std::stable_partition( - Settings::values.players.begin(), - Settings::values.players.begin() + - Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), - [](const auto& player) { return player.connected; }); } void Config::ReadDebugValues() { @@ -328,7 +346,7 @@ void Config::ReadDebugValues() { 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_analogs[i][4], 0.5f); + default_analogs[i][3], default_stick_mod[i], 0.5f); auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i]; debug_pad_analogs = qt_config @@ -395,13 +413,6 @@ void Config::ReadTouchscreenValues() { ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt(); } -void Config::ApplyDefaultProfileIfInputInvalid() { - if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), - [](const Settings::PlayerInput& in) { return in.connected; })) { - ApplyInputProfileConfiguration(UISettings::values.profile_index); - } -} - void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); @@ -430,12 +441,65 @@ void Config::ReadControlValues() { ReadKeyboardValues(); ReadMouseValues(); ReadTouchscreenValues(); + ReadMotionTouchValues(); + + Settings::values.vibration_enabled = + ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); + Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool(); + Settings::values.use_docked_mode = + ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); + + 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.motion_device = ReadSetting(QStringLiteral("motion_device"), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) .toString() .toStdString(); + Settings::values.touch_device = + ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window")) + .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_address = ReadSetting(QStringLiteral("udp_input_address"), QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) @@ -446,10 +510,6 @@ void Config::ReadControlValues() { .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() { @@ -464,47 +524,42 @@ void Config::ReadDataStorageValues() { qt_config->beginGroup(QStringLiteral("Data Storage")); Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); - FileUtil::GetUserPath( - FileUtil::UserPath::NANDDir, - qt_config - ->value(QStringLiteral("nand_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))) - .toString() - .toStdString()); - FileUtil::GetUserPath( - FileUtil::UserPath::SDMCDir, - qt_config - ->value(QStringLiteral("sdmc_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) - .toString() - .toStdString()); - FileUtil::GetUserPath( - FileUtil::UserPath::LoadDir, - qt_config - ->value(QStringLiteral("load_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))) - .toString() - .toStdString()); - FileUtil::GetUserPath( - FileUtil::UserPath::DumpDir, - qt_config - ->value(QStringLiteral("dump_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))) - .toString() - .toStdString()); - FileUtil::GetUserPath( - FileUtil::UserPath::CacheDir, - qt_config - ->value(QStringLiteral("cache_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))) - .toString() - .toStdString()); + FS::GetUserPath(FS::UserPath::NANDDir, + qt_config + ->value(QStringLiteral("nand_directory"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))) + .toString() + .toStdString()); + FS::GetUserPath(FS::UserPath::SDMCDir, + qt_config + ->value(QStringLiteral("sdmc_directory"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))) + .toString() + .toStdString()); + FS::GetUserPath(FS::UserPath::LoadDir, + qt_config + ->value(QStringLiteral("load_directory"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))) + .toString() + .toStdString()); + FS::GetUserPath(FS::UserPath::DumpDir, + qt_config + ->value(QStringLiteral("dump_directory"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) + .toString() + .toStdString()); + FS::GetUserPath(FS::UserPath::CacheDir, + qt_config + ->value(QStringLiteral("cache_directory"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir))) + .toString() + .toStdString()); Settings::values.gamecard_inserted = ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); Settings::values.gamecard_current_game = ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool(); Settings::values.gamecard_path = - ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString(); + ReadSetting(QStringLiteral("gamecard_path"), QString{}).toString().toStdString(); qt_config->endGroup(); } @@ -518,7 +573,7 @@ void Config::ReadDebuggingValues() { Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = - ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString(); + ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); Settings::values.reporting_services = @@ -551,8 +606,7 @@ void Config::ReadDisabledAddOnValues() { const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled")); for (int j = 0; j < d_size; ++j) { qt_config->setArrayIndex(j); - out.push_back( - ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString()); + out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString()); } qt_config->endArray(); Settings::values.disabled_addons.insert_or_assign(title_id, out); @@ -578,7 +632,6 @@ void Config::ReadPathValues() { UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); - UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); UISettings::values.game_dir_deprecated = ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); UISettings::values.game_dir_deprecated_deepscan = @@ -611,6 +664,7 @@ void Config::ReadPathValues() { } } UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); + UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString(); qt_config->endGroup(); } @@ -638,6 +692,11 @@ void Config::ReadCpuValues() { ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); Settings::values.cpuopt_reduce_misalign_checks = ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); + + Settings::values.cpuopt_unsafe_unfuse_fma = + ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); + Settings::values.cpuopt_unsafe_reduce_fp_error = + ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); } qt_config->endGroup(); @@ -661,10 +720,10 @@ void Config::ReadRendererValues() { ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), false); + ReadSettingGlobal(Settings::values.use_asynchronous_shaders, + QStringLiteral("use_asynchronous_shaders"), false); ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), true); - ReadSettingGlobal(Settings::values.force_30fps_mode, QStringLiteral("force_30fps_mode"), false); - ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0); ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0); ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); @@ -672,6 +731,22 @@ void Config::ReadRendererValues() { qt_config->endGroup(); } +void Config::ReadScreenshotValues() { + qt_config->beginGroup(QStringLiteral("Screenshots")); + + UISettings::values.enable_screenshot_save_as = + ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); + FS::GetUserPath( + FS::UserPath::ScreenshotsDir, + qt_config + ->value(QStringLiteral("screenshot_path"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))) + .toString() + .toStdString()); + + qt_config->endGroup(); +} + void Config::ReadShortcutValues() { qt_config->beginGroup(QStringLiteral("Shortcuts")); @@ -753,6 +828,7 @@ void Config::ReadUIValues() { ReadUIGamelistValues(); ReadUILayoutValues(); ReadPathValues(); + ReadScreenshotValues(); ReadShortcutValues(); UISettings::values.single_window_mode = @@ -769,14 +845,11 @@ void Config::ReadUIValues() { UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool(); UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt(); UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool(); - UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt(); UISettings::values.pause_when_in_background = ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool(); UISettings::values.hide_mouse = ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool(); - ApplyDefaultProfileIfInputInvalid(); - qt_config->endGroup(); } @@ -850,8 +923,9 @@ void Config::SavePlayerValues() { 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.type), - static_cast<u8>(Settings::ControllerType::DualJoycon)); + 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, Settings::JOYCON_BODY_NEON_BLUE); @@ -870,10 +944,18 @@ void Config::SavePlayerValues() { QString::fromStdString(player.buttons[i]), QString::fromStdString(default_param)); } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = + InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[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_analogs[i][4], 0.5f); + 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]), @@ -894,7 +976,7 @@ void Config::SaveDebugValues() { 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_analogs[i][4], 0.5f); + default_analogs[i][3], default_stick_mod[i], 0.5f); WriteSetting(QStringLiteral("debug_pad_") + QString::fromStdString(Settings::NativeAnalog::mapping[i]), QString::fromStdString(Settings::values.debug_pad_analogs[i]), @@ -928,6 +1010,43 @@ void Config::SaveTouchscreenValues() { 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_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); + + 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(); @@ -970,17 +1089,17 @@ void Config::SaveControlValues() { SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); + SaveMotionTouchValues(); + WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); + WriteSetting(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); qt_config->endGroup(); @@ -999,25 +1118,25 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); WriteSetting(QStringLiteral("nand_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)), + QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))); WriteSetting(QStringLiteral("sdmc_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)), + QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))); WriteSetting(QStringLiteral("load_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); + QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)), + QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))); WriteSetting(QStringLiteral("dump_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); + QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), + QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); WriteSetting(QStringLiteral("cache_directory"), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)), - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)), + QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir))); WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, false); WriteSetting(QStringLiteral("gamecard_path"), - QString::fromStdString(Settings::values.gamecard_path), QStringLiteral("")); + QString::fromStdString(Settings::values.gamecard_path), QString{}); qt_config->endGroup(); } @@ -1030,7 +1149,7 @@ void Config::SaveDebuggingValues() { WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), - QString::fromStdString(Settings::values.program_args), QStringLiteral("")); + QString::fromStdString(Settings::values.program_args), QString{}); WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); @@ -1057,8 +1176,7 @@ void Config::SaveDisabledAddOnValues() { qt_config->beginWriteArray(QStringLiteral("disabled")); for (std::size_t j = 0; j < elem.second.size(); ++j) { qt_config->setArrayIndex(static_cast<int>(j)); - WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), - QStringLiteral("")); + WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{}); } qt_config->endArray(); ++i; @@ -1082,7 +1200,6 @@ void Config::SavePathValues() { WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); - WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); qt_config->beginWriteArray(QStringLiteral("gamedirs")); for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { qt_config->setArrayIndex(i); @@ -1093,6 +1210,7 @@ void Config::SavePathValues() { } qt_config->endArray(); WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); + WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{}); qt_config->endGroup(); } @@ -1118,6 +1236,11 @@ void Config::SaveCpuValues() { WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), Settings::values.cpuopt_reduce_misalign_checks, true); + + WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), + Settings::values.cpuopt_unsafe_unfuse_fma, true); + WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), + Settings::values.cpuopt_unsafe_reduce_fp_error, true); } qt_config->endGroup(); @@ -1145,11 +1268,10 @@ void Config::SaveRendererValues() { WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, false); + WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"), + Settings::values.use_asynchronous_shaders, false); WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, true); - WriteSettingGlobal(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, - false); - // Cast to double because Qt's written float values are not human-readable WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0); WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); @@ -1158,6 +1280,17 @@ void Config::SaveRendererValues() { qt_config->endGroup(); } +void Config::SaveScreenshotValues() { + qt_config->beginGroup(QStringLiteral("Screenshots")); + + WriteSetting(QStringLiteral("enable_screenshot_save_as"), + UISettings::values.enable_screenshot_save_as); + WriteSetting(QStringLiteral("screenshot_path"), + QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))); + + qt_config->endGroup(); +} + void Config::SaveShortcutValues() { qt_config->beginGroup(QStringLiteral("Shortcuts")); @@ -1220,6 +1353,7 @@ void Config::SaveUIValues() { SaveUIGamelistValues(); SaveUILayoutValues(); SavePathValues(); + SaveScreenshotValues(); SaveShortcutValues(); WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true); @@ -1231,7 +1365,6 @@ void Config::SaveUIValues() { WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true); WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0); WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false); - WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0); WriteSetting(QStringLiteral("pauseWhenInBackground"), UISettings::values.pause_when_in_background, false); WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 8e815f829..5d8e45d78 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -23,7 +23,9 @@ public: void Save(); static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; - static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; + 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; @@ -37,7 +39,7 @@ private: void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); - void ApplyDefaultProfileIfInputInvalid(); + void ReadMotionTouchValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -51,6 +53,7 @@ private: void ReadPathValues(); void ReadCpuValues(); void ReadRendererValues(); + void ReadScreenshotValues(); void ReadShortcutValues(); void ReadSystemValues(); void ReadUIValues(); @@ -63,6 +66,7 @@ private: void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); + void SaveMotionTouchValues(); // Save functions based off the respective config section names. void SaveAudioValues(); @@ -76,6 +80,7 @@ private: void SavePathValues(); void SaveCpuValues(); void SaveRendererValues(); + void SaveScreenshotValues(); void SaveShortcutValues(); void SaveSystemValues(); void SaveUIValues(); diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp index bb47c3933..18482795c 100644 --- a/src/yuzu/configuration/configuration_shared.cpp +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -4,17 +4,20 @@ #include <QCheckBox> #include <QComboBox> +#include <QObject> +#include <QString> #include "core/settings.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_per_game.h" void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, - const QCheckBox* checkbox) { - if (checkbox->checkState() == Qt::PartiallyChecked) { + const QCheckBox* checkbox, + const CheckState& tracker) { + if (tracker == CheckState::Global) { setting->SetGlobal(true); } else { setting->SetGlobal(false); - setting->SetValue(checkbox->checkState() == Qt::Checked); + setting->SetValue(checkbox->checkState()); } } @@ -69,8 +72,63 @@ void ConfigurationShared::SetPerGameSetting( ConfigurationShared::USE_GLOBAL_OFFSET); } -void ConfigurationShared::InsertGlobalItem(QComboBox* combobox) { - const QString use_global_text = ConfigurePerGame::tr("Use global configuration"); +void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) { + if (highlighted) { + widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }") + .arg(widget->objectName())); + } else { + widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }") + .arg(widget->objectName())); + } + widget->show(); +} + +void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, + const Settings::Setting<bool>& setting, + CheckState& tracker) { + if (setting.UsingGlobal()) { + tracker = CheckState::Global; + } else { + tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off; + } + SetHighlight(checkbox, tracker != CheckState::Global); + QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] { + tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % + static_cast<int>(CheckState::Count)); + if (tracker == CheckState::Global) { + checkbox->setChecked(setting.GetValue(true)); + } + SetHighlight(checkbox, tracker != CheckState::Global); + }); +} + +void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state, + bool global_state, CheckState& tracker) { + if (global) { + tracker = CheckState::Global; + } else { + tracker = (state == global_state) ? CheckState::On : CheckState::Off; + } + SetHighlight(checkbox, tracker != CheckState::Global); + QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] { + tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % + static_cast<int>(CheckState::Count)); + if (tracker == CheckState::Global) { + checkbox->setChecked(global_state); + } + SetHighlight(checkbox, tracker != CheckState::Global); + }); +} + +void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) { + InsertGlobalItem(combobox, global); + QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target, + [target](int index) { SetHighlight(target, index != 0); }); +} + +void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) { + const QString use_global_text = + ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index)); combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text); combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX); } diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index b11b1b950..312b9e549 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -15,9 +15,17 @@ constexpr int USE_GLOBAL_INDEX = 0; constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; constexpr int USE_GLOBAL_OFFSET = 2; +enum class CheckState { + Off, + On, + Global, + Count, +}; + // Global-aware apply and set functions -void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox); +void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, + const CheckState& tracker); void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, const QComboBox* combobox); @@ -31,6 +39,13 @@ void SetPerGameSetting(QComboBox* combobox, void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting); -void InsertGlobalItem(QComboBox* combobox); +void SetHighlight(QWidget* widget, bool highlighted); +void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting, + CheckState& tracker); +void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state, + CheckState& tracker); +void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); + +void InsertGlobalItem(QComboBox* combobox, int global_index); } // namespace ConfigurationShared diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 5f5d8e571..fcf42cdcb 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>382</width> + <width>650</width> <height>650</height> </rect> </property> @@ -26,13 +26,13 @@ <widget class="QListWidget" name="selectorList"> <property name="minimumSize"> <size> - <width>150</width> + <width>120</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>150</width> + <width>120</width> <height>16777215</height> </size> </property> @@ -44,76 +44,121 @@ <number>0</number> </property> <widget class="ConfigureGeneral" name="generalTab"> + <property name="accessibleName"> + <string>General</string> + </property> <attribute name="title"> <string>General</string> </attribute> </widget> <widget class="ConfigureUi" name="uiTab"> + <property name="accessibleName"> + <string>UI</string> + </property> <attribute name="title"> <string>Game List</string> </attribute> </widget> <widget class="ConfigureSystem" name="systemTab"> + <property name="accessibleName"> + <string>System</string> + </property> <attribute name="title"> <string>System</string> </attribute> </widget> <widget class="ConfigureProfileManager" name="profileManagerTab"> + <property name="accessibleName"> + <string>Profiles</string> + </property> <attribute name="title"> <string>Profiles</string> </attribute> </widget> <widget class="ConfigureFilesystem" name="filesystemTab"> + <property name="accessibleName"> + <string>Filesystem</string> + </property> <attribute name="title"> <string>Filesystem</string> </attribute> </widget> - <widget class="ConfigureInputSimple" name="inputTab"> + <widget class="ConfigureInput" name="inputTab"> + <property name="accessibleName"> + <string>Controls</string> + </property> <attribute name="title"> - <string>Input</string> + <string>Controls</string> </attribute> </widget> <widget class="ConfigureHotkeys" name="hotkeysTab"> + <property name="accessibleName"> + <string>Hotkeys</string> + </property> <attribute name="title"> <string>Hotkeys</string> </attribute> </widget> <widget class="ConfigureCpu" name="cpuTab"> + <property name="accessibleName"> + <string>CPU</string> + </property> <attribute name="title"> <string>CPU</string> </attribute> </widget> <widget class="ConfigureCpuDebug" name="cpuDebugTab"> + <property name="accessibleName"> + <string>Debug</string> + </property> <attribute name="title"> <string>Debug</string> </attribute> </widget> <widget class="ConfigureGraphics" name="graphicsTab"> + <property name="accessibleName"> + <string>Graphics</string> + </property> <attribute name="title"> <string>Graphics</string> </attribute> </widget> <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab"> + <property name="accessibleName"> + <string>Advanced</string> + </property> <attribute name="title"> <string>GraphicsAdvanced</string> </attribute> </widget> <widget class="ConfigureAudio" name="audioTab"> + <property name="accessibleName"> + <string>Audio</string> + </property> <attribute name="title"> <string>Audio</string> </attribute> </widget> <widget class="ConfigureDebug" name="debugTab"> + <property name="accessibleName"> + <string>Debug</string> + </property> <attribute name="title"> <string>Debug</string> </attribute> </widget> <widget class="ConfigureWeb" name="webTab"> + <property name="accessibleName"> + <string>Web</string> + </property> <attribute name="title"> <string>Web</string> </attribute> </widget> <widget class="ConfigureService" name="serviceTab"> + <property name="accessibleName"> + <string>Services</string> + </property> <attribute name="title"> <string>Services</string> </attribute> @@ -205,9 +250,9 @@ <container>1</container> </customwidget> <customwidget> - <class>ConfigureInputSimple</class> + <class>ConfigureInput</class> <extends>QWidget</extends> - <header>configuration/configure_input_simple.h</header> + <header>configuration/configure_input.h</header> <container>1</container> </customwidget> <customwidget> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index cc021beec..fa9124ecf 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -49,12 +49,9 @@ void ConfigureAudio::SetConfiguration() { ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum()); - if (Settings::configuring_global) { - ui->toggle_audio_stretching->setChecked( - Settings::values.enable_audio_stretching.GetValue()); - } else { - ConfigurationShared::SetPerGameSetting(ui->toggle_audio_stretching, - &Settings::values.enable_audio_stretching); + ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); + + if (!Settings::configuring_global) { if (Settings::values.volume.UsingGlobal()) { ui->volume_combo_box->setCurrentIndex(0); ui->volume_slider->setEnabled(false); @@ -62,6 +59,8 @@ void ConfigureAudio::SetConfiguration() { ui->volume_combo_box->setCurrentIndex(1); ui->volume_slider->setEnabled(true); } + ConfigurationShared::SetHighlight(ui->volume_layout, + !Settings::values.volume.UsingGlobal()); } SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } @@ -120,7 +119,8 @@ void ConfigureAudio::ApplyConfiguration() { } } else { ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, - ui->toggle_audio_stretching); + ui->toggle_audio_stretching, + enable_audio_stretching); if (ui->volume_combo_box->currentIndex() == 0) { Settings::values.volume.SetGlobal(true); } else { @@ -173,9 +173,13 @@ void ConfigureAudio::SetupPerGameUI() { return; } - ui->toggle_audio_stretching->setTristate(true); - connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), - this, [this](int index) { ui->volume_slider->setEnabled(index == 1); }); + ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching, + Settings::values.enable_audio_stretching, + enable_audio_stretching); + connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) { + ui->volume_slider->setEnabled(index == 1); + ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); + }); ui->output_sink_combo_box->setVisible(false); ui->output_sink_label->setVisible(false); diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index d84f4a682..9dbd3d93e 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -7,6 +7,10 @@ #include <memory> #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} + namespace Ui { class ConfigureAudio; } @@ -37,4 +41,6 @@ private: void SetupPerGameUI(); std::unique_ptr<Ui::ConfigureAudio> ui; + + ConfigurationShared::CheckState enable_audio_stretching; }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index 862ccb988..9bd0cca96 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -56,80 +56,91 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <property name="topMargin"> - <number>0</number> - </property> - <item> - <widget class="QComboBox" name="volume_combo_box"> - <item> + <widget class="QWidget" name="volume_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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="QComboBox" name="volume_combo_box"> + <item> + <property name="text"> + <string>Use global volume</string> + </property> + </item> + <item> + <property name="text"> + <string>Set volume:</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="volume_label"> <property name="text"> - <string>Use global volume</string> + <string>Volume:</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="volume_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="pageStep"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="volume_indicator"> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> </property> - </item> - <item> <property name="text"> - <string>Set volume:</string> + <string>0 %</string> </property> - </item> - </widget> - </item> - <item> - <widget class="QLabel" name="volume_label"> - <property name="text"> - <string>Volume:</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>30</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QSlider" name="volume_slider"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximum"> - <number>100</number> - </property> - <property name="pageStep"> - <number>10</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="volume_indicator"> - <property name="minimumSize"> - <size> - <width>32</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>0 %</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - </layout> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 7493e5ffb..37fcd6adc 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -19,6 +19,8 @@ ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::Config connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, &ConfigureCpu::AccuracyUpdated); + connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this, + &ConfigureCpu::UpdateGroup); } ConfigureCpu::~ConfigureCpu() = default; @@ -28,6 +30,12 @@ void ConfigureCpu::SetConfiguration() { ui->accuracy->setEnabled(runtime_lock); ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy)); + UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy)); + + ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); + 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); } void ConfigureCpu::AccuracyUpdated(int index) { @@ -38,14 +46,21 @@ void ConfigureCpu::AccuracyUpdated(int index) { QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::No) { ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate)); - return; + UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate)); } } } +void ConfigureCpu::UpdateGroup(int index) { + ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) == + Settings::CPUAccuracy::Unsafe); +} + void ConfigureCpu::ApplyConfiguration() { Settings::values.cpu_accuracy = 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(); } void ConfigureCpu::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index e4741d3a4..3c5683d81 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h @@ -26,6 +26,7 @@ private: void RetranslateUI(); void AccuracyUpdated(int index); + void UpdateGroup(int index); void SetConfiguration(); diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index bf6ea79bb..ebdd2e6e9 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -40,6 +40,11 @@ </item> <item> <property name="text"> + <string>Unsafe</string> + </property> + </item> + <item> + <property name="text"> <string>Enable Debug Mode</string> </property> </item> @@ -63,6 +68,53 @@ </layout> </item> <item> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="unsafe_group"> + <property name="title"> + <string>Unsafe CPU Optimization Settings</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QLabel"> + <property name="wordWrap"> + <bool>1</bool> + </property> + <property name="text"> + <string>These settings reduce accuracy for speed.</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma"> + <property name="text"> + <string>Unfuse FMA (improve performance on CPUs without FMA)</string> + </property> + <property name="toolTip"> + <string> + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + </string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error"> + <property name="text"> + <string>Faster FRSQRTE and FRECPE</string> + </property> + <property name="toolTip"> + <string> + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + </string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index d0e71dd60..2bfe2c306 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -19,7 +19,8 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co SetConfiguration(); connect(ui->open_log_button, &QPushButton::clicked, []() { - QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); + const auto path = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); } diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 272bdd6b8..9d6feb9f7 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -171,26 +171,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_6"> <item> - <widget class="QCheckBox" name="dump_decompressed_nso"> - <property name="whatsThis"> - <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string> - </property> - <property name="text"> - <string>Dump Decompressed NSOs</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="dump_exefs"> - <property name="whatsThis"> - <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string> - </property> - <property name="text"> - <string>Dump ExeFS</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="reporting_services"> <property name="text"> <string>Enable Verbose Reporting Services</string> @@ -257,8 +237,6 @@ <tabstop>open_log_button</tabstop> <tabstop>homebrew_args_edit</tabstop> <tabstop>enable_graphics_debugging</tabstop> - <tabstop>dump_decompressed_nso</tabstop> - <tabstop>dump_exefs</tabstop> <tabstop>reporting_services</tabstop> <tabstop>quest_flag</tabstop> </tabstops> diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp new file mode 100644 index 000000000..0097c9a29 --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_debug_controller.h" +#include "yuzu/configuration/configure_debug_controller.h" + +ConfigureDebugController::ConfigureDebugController(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), + debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) { + ui->setupUi(this); + + ui->controllerLayout->addWidget(debug_controller); + + connect(ui->clear_all_button, &QPushButton::clicked, this, + [this] { debug_controller->ClearAll(); }); + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + [this] { debug_controller->RestoreDefaults(); }); + + RetranslateUI(); +} + +ConfigureDebugController::~ConfigureDebugController() = default; + +void ConfigureDebugController::ApplyConfiguration() { + debug_controller->ApplyConfiguration(); +} + +void ConfigureDebugController::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureDebugController::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h new file mode 100644 index 000000000..34dcf705f --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.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 <QDialog> +#include "yuzu/configuration/configure_input_player.h" + +class QPushButton; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureDebugController; +} + +class ConfigureDebugController : public QDialog { + Q_OBJECT + +public: + explicit ConfigureDebugController(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem); + ~ConfigureDebugController() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureDebugController> ui; + + ConfigureInputPlayer* debug_controller; +}; diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui new file mode 100644 index 000000000..a95ed50ff --- /dev/null +++ b/src/yuzu/configuration/configure_debug_controller.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureDebugController</class> + <widget class="QDialog" name="ConfigureDebugController"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>780</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Debug Controller</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::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <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 a5afb354f..8186929a6 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -12,7 +12,8 @@ #include "yuzu/configuration/configure_input_player.h" #include "yuzu/hotkeys.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, + InputCommon::InputSubsystem* input_subsystem) : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { Settings::configuring_global = true; @@ -20,9 +21,12 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) ui->hotkeysTab->Populate(registry); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + ui->inputTab->Initialize(input_subsystem); + SetConfiguration(); PopulateSelectionList(); + connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged); connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, &ConfigureDialog::UpdateVisibleTabs); @@ -79,12 +83,12 @@ Q_DECLARE_METATYPE(QList<QWidget*>); void ConfigureDialog::PopulateSelectionList() { const std::array<std::pair<QString, QList<QWidget*>>, 6> items{ - {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}}, + {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}}, {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, {tr("CPU"), {ui->cpuTab, ui->cpuDebugTab}}, {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}}, {tr("Audio"), {ui->audioTab}}, - {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, + {tr("Controls"), ui->inputTab->GetSubTabs()}}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); @@ -98,6 +102,14 @@ void ConfigureDialog::PopulateSelectionList() { } } +void ConfigureDialog::OnLanguageChanged(const QString& locale) { + emit LanguageChanged(locale); + // first apply the configuration, and then restore the display + ApplyConfiguration(); + RetranslateUI(); + SetConfiguration(); +} + void ConfigureDialog::UpdateVisibleTabs() { const auto items = ui->selectorList->selectedItems(); if (items.isEmpty()) { @@ -108,7 +120,7 @@ void ConfigureDialog::UpdateVisibleTabs() { {ui->generalTab, tr("General")}, {ui->systemTab, tr("System")}, {ui->profileManagerTab, tr("Profiles")}, - {ui->inputTab, tr("Input")}, + {ui->inputTab, tr("Controls")}, {ui->hotkeysTab, tr("Hotkeys")}, {ui->cpuTab, tr("CPU")}, {ui->cpuDebugTab, tr("Debug")}, @@ -129,6 +141,6 @@ void ConfigureDialog::UpdateVisibleTabs() { const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); for (const auto tab : tabs) { - ui->tabWidget->addTab(tab, widgets.at(tab)); + ui->tabWidget->addTab(tab, tab->accessibleName()); } } diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 2d3bfc2da..570c3b941 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -9,6 +9,10 @@ class HotkeyRegistry; +namespace InputCommon { +class InputSubsystem; +} + namespace Ui { class ConfigureDialog; } @@ -17,11 +21,18 @@ class ConfigureDialog : public QDialog { Q_OBJECT public: - explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry); + explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, + InputCommon::InputSubsystem* input_subsystem); ~ConfigureDialog() override; void ApplyConfiguration(); +private slots: + void OnLanguageChanged(const QString& locale); + +signals: + void LanguageChanged(const QString& locale); + private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index a089f5733..7ab4a80f7 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -42,16 +42,16 @@ ConfigureFilesystem::~ConfigureFilesystem() = default; void ConfigureFilesystem::setConfiguration() { ui->nand_directory_edit->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir))); ui->sdmc_directory_edit->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir))); ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); ui->dump_path_edit->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); ui->load_path_edit->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); ui->cache_directory_edit->setText( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))); ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); @@ -64,14 +64,16 @@ void ConfigureFilesystem::setConfiguration() { } void ConfigureFilesystem::applyConfiguration() { - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, - ui->nand_directory_edit->text().toStdString()); - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, - ui->sdmc_directory_edit->text().toStdString()); - FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString()); - FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString()); - FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, - ui->cache_directory_edit->text().toStdString()); + Common::FS::GetUserPath(Common::FS::UserPath::NANDDir, + ui->nand_directory_edit->text().toStdString()); + Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir, + ui->sdmc_directory_edit->text().toStdString()); + Common::FS::GetUserPath(Common::FS::UserPath::DumpDir, + ui->dump_path_edit->text().toStdString()); + Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, + ui->load_path_edit->text().toStdString()); + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir, + ui->cache_directory_edit->text().toStdString()); Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString(); Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); @@ -121,12 +123,13 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) } void ConfigureFilesystem::ResetMetadata() { - if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + - "game_list")) { + if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list")) { QMessageBox::information(this, tr("Reset Metadata Cache"), tr("The metadata cache is already empty.")); - } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + - DIR_SEP + "game_list")) { + } else if (Common::FS::DeleteDirRecursively( + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list")) { QMessageBox::information(this, tr("Reset Metadata Cache"), tr("The operation completed successfully.")); UISettings::values.is_game_list_reload_pending.exchange(true); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 20316c9cc..830096ea0 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -19,9 +19,10 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) SetConfiguration(); - connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, [this]() { - ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked); - }); + if (Settings::configuring_global) { + connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, + [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); + } } ConfigureGeneral::~ConfigureGeneral() = default; @@ -40,17 +41,12 @@ 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::values.use_multi_core.UsingGlobal()) { - ui->use_multi_core->setCheckState(Qt::PartiallyChecked); - } - if (Settings::values.use_frame_limit.UsingGlobal()) { - ui->toggle_frame_limit->setCheckState(Qt::PartiallyChecked); - } + if (Settings::configuring_global) { + ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); + } else { + ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() && + use_frame_limit != ConfigurationShared::CheckState::Global); } - - ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked && - ui->toggle_frame_limit->isEnabled()); } void ConfigureGeneral::ApplyConfiguration() { @@ -71,9 +67,9 @@ void ConfigureGeneral::ApplyConfiguration() { } } else { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, - ui->use_multi_core); + ui->use_multi_core, use_multi_core); - bool global_frame_limit = ui->toggle_frame_limit->checkState() == Qt::PartiallyChecked; + bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global; Settings::values.use_frame_limit.SetGlobal(global_frame_limit); Settings::values.frame_limit.SetGlobal(global_frame_limit); if (!global_frame_limit) { @@ -109,6 +105,13 @@ void ConfigureGeneral::SetupPerGameUI() { ui->toggle_background_pause->setVisible(false); ui->toggle_hide_mouse->setVisible(false); - ui->toggle_frame_limit->setTristate(true); - ui->use_multi_core->setTristate(true); + ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, + Settings::values.use_frame_limit, use_frame_limit); + ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, + use_multi_core); + + connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() { + ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() && + (use_frame_limit != ConfigurationShared::CheckState::Global)); + }); } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 9c785c22e..323ffbd8f 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -7,6 +7,10 @@ #include <memory> #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} + class HotkeyRegistry; namespace Ui { @@ -31,4 +35,7 @@ private: void SetupPerGameUI(); std::unique_ptr<Ui::ConfigureGeneral> ui; + + ConfigurationShared::CheckState use_frame_limit; + ConfigurationShared::CheckState use_multi_core; }; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index cb4706bd6..07d818548 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -31,8 +31,13 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) SetConfiguration(); - connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, - [this] { UpdateDeviceComboBox(); }); + connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { + UpdateDeviceComboBox(); + if (!Settings::configuring_global) { + ConfigurationShared::SetHighlight( + ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); + } + }); connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) { UpdateDeviceSelection(device); }); @@ -65,25 +70,25 @@ 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_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); + ui->use_asynchronous_gpu_emulation->setChecked( + Settings::values.use_asynchronous_gpu_emulation.GetValue()); if (Settings::configuring_global) { ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); - 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()); } else { - ConfigurationShared::SetPerGameSetting(ui->use_disk_shader_cache, - &Settings::values.use_disk_shader_cache); - ConfigurationShared::SetPerGameSetting(ui->use_asynchronous_gpu_emulation, - &Settings::values.use_asynchronous_gpu_emulation); - ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); + ConfigurationShared::SetHighlight(ui->api_layout, + !Settings::values.renderer_backend.UsingGlobal()); ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, &Settings::values.aspect_ratio); ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->ar_label, + !Settings::values.aspect_ratio.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal()); } UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(), @@ -135,9 +140,10 @@ void ConfigureGraphics::ApplyConfiguration() { ui->aspect_ratio_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, - ui->use_disk_shader_cache); + ui->use_disk_shader_cache, use_disk_shader_cache); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, - ui->use_asynchronous_gpu_emulation); + ui->use_asynchronous_gpu_emulation, + use_asynchronous_gpu_emulation); if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.bg_red.SetGlobal(true); @@ -240,11 +246,19 @@ void ConfigureGraphics::SetupPerGameUI() { return; } - connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, - [this](int index) { ui->bg_button->setEnabled(index == 1); }); + connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) { + ui->bg_button->setEnabled(index == 1); + ConfigurationShared::SetHighlight(ui->bg_layout, index == 1); + }); + + ConfigurationShared::SetColoredTristate( + ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); + ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, + Settings::values.use_asynchronous_gpu_emulation, + use_asynchronous_gpu_emulation); - ui->use_disk_shader_cache->setTristate(true); - ui->use_asynchronous_gpu_emulation->setTristate(true); - ConfigurationShared::InsertGlobalItem(ui->aspect_ratio_combobox); - ConfigurationShared::InsertGlobalItem(ui->api); + ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, + Settings::values.aspect_ratio.GetValue(true)); + ConfigurationShared::InsertGlobalItem( + ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 24f01c739..b4961f719 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -10,6 +10,10 @@ #include <QWidget> #include "core/settings.h" +namespace ConfigurationShared { +enum class CheckState; +} + namespace Ui { class ConfigureGraphics; } @@ -42,6 +46,9 @@ private: std::unique_ptr<Ui::ConfigureGraphics> ui; QColor bg_color; + ConfigurationShared::CheckState use_disk_shader_cache; + ConfigurationShared::CheckState use_asynchronous_gpu_emulation; + std::vector<QString> vulkan_devices; u32 vulkan_device{}; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 62418fc14..62aa337e7 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>400</width> + <width>437</width> <height>321</height> </rect> </property> @@ -23,43 +23,56 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>API:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="api"> - <item> + <widget class="QWidget" name="api_layout" native="true"> + <layout class="QGridLayout" name="gridLayout"> + <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> + <property name="horizontalSpacing"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="api_label"> <property name="text"> - <string notr="true">OpenGL</string> + <string>API:</string> </property> - </item> - <item> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="api"> + <item> + <property name="text"> + <string notr="true">OpenGL</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">Vulkan</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="device_label"> <property name="text"> - <string notr="true">Vulkan</string> + <string>Device:</string> </property> - </item> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Device:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="device"/> - </item> - </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="device"/> + </item> + </layout> + </widget> </item> </layout> </widget> @@ -85,96 +98,133 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_6"> - <item> - <widget class="QLabel" name="ar_label"> - <property name="text"> - <string>Aspect Ratio:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="aspect_ratio_combobox"> - <item> - <property name="text"> - <string>Default (16:9)</string> - </property> - </item> - <item> - <property name="text"> - <string>Force 4:3</string> - </property> - </item> - <item> - <property name="text"> - <string>Force 21:9</string> - </property> - </item> - <item> + <widget class="QWidget" name="aspect_ratio_layout" 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="QLabel" name="ar_label"> <property name="text"> - <string>Stretch to Window</string> + <string>Aspect Ratio:</string> </property> - </item> - </widget> - </item> - </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="aspect_ratio_combobox"> + <item> + <property name="text"> + <string>Default (16:9)</string> + </property> + </item> + <item> + <property name="text"> + <string>Force 4:3</string> + </property> + </item> + <item> + <property name="text"> + <string>Force 21:9</string> + </property> + </item> + <item> + <property name="text"> + <string>Stretch to Window</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QComboBox" name="bg_combobox"> - <property name="currentText"> - <string>Use global background color</string> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <property name="maxVisibleItems"> - <number>10</number> - </property> - <item> - <property name="text"> + <widget class="QWidget" name="bg_layout" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="spacing"> + <number>6</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="QComboBox" name="bg_combobox"> + <property name="currentText"> <string>Use global background color</string> </property> - </item> - <item> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="maxVisibleItems"> + <number>10</number> + </property> + <item> + <property name="text"> + <string>Use global background color</string> + </property> + </item> + <item> + <property name="text"> + <string>Set background color:</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="bg_label"> <property name="text"> - <string>Set background color:</string> + <string>Background Color:</string> </property> - </item> - </widget> - </item> - <item> - <widget class="QLabel" name="bg_label"> - <property name="text"> - <string>Background Color:</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="bg_button"> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - </widget> - </item> - </layout> + </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="bg_button"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 7c0fa7ec5..73f276949 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -24,29 +24,27 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); ui->use_vsync->setEnabled(runtime_lock); ui->use_assembly_shaders->setEnabled(runtime_lock); - ui->force_30fps_mode->setEnabled(runtime_lock); + ui->use_asynchronous_shaders->setEnabled(runtime_lock); ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); + ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); + ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); + ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + if (Settings::configuring_global) { ui->gpu_accuracy->setCurrentIndex( static_cast<int>(Settings::values.gpu_accuracy.GetValue())); - ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); - ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); - ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); - ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode.GetValue()); ui->anisotropic_filtering_combobox->setCurrentIndex( Settings::values.max_anisotropy.GetValue()); } else { ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); - ConfigurationShared::SetPerGameSetting(ui->use_vsync, &Settings::values.use_vsync); - ConfigurationShared::SetPerGameSetting(ui->use_assembly_shaders, - &Settings::values.use_assembly_shaders); - ConfigurationShared::SetPerGameSetting(ui->use_fast_gpu_time, - &Settings::values.use_fast_gpu_time); - ConfigurationShared::SetPerGameSetting(ui->force_30fps_mode, - &Settings::values.force_30fps_mode); ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, &Settings::values.max_anisotropy); + ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, + !Settings::values.gpu_accuracy.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->af_label, + !Settings::values.max_anisotropy.UsingGlobal()); } } @@ -67,12 +65,17 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { if (Settings::values.use_assembly_shaders.UsingGlobal()) { Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); } + if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { + Settings::values.use_asynchronous_shaders.SetValue( + ui->use_asynchronous_shaders->isChecked()); + } + if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { + Settings::values.use_asynchronous_shaders.SetValue( + ui->use_asynchronous_shaders->isChecked()); + } if (Settings::values.use_fast_gpu_time.UsingGlobal()) { Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); } - if (Settings::values.force_30fps_mode.UsingGlobal()) { - Settings::values.force_30fps_mode.SetValue(ui->force_30fps_mode->isChecked()); - } if (Settings::values.max_anisotropy.UsingGlobal()) { Settings::values.max_anisotropy.SetValue( ui->anisotropic_filtering_combobox->currentIndex()); @@ -80,13 +83,15 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { } else { ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, + use_vsync); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, - ui->use_assembly_shaders); + ui->use_assembly_shaders, use_assembly_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, + ui->use_asynchronous_shaders, + use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, - ui->use_fast_gpu_time); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.force_30fps_mode, - ui->force_30fps_mode); + ui->use_fast_gpu_time, use_fast_gpu_time); ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); @@ -117,18 +122,27 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { 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()); + ui->use_asynchronous_shaders->setEnabled( + Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); - ui->force_30fps_mode->setEnabled(Settings::values.force_30fps_mode.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); return; } - ConfigurationShared::InsertGlobalItem(ui->gpu_accuracy); - ui->use_vsync->setTristate(true); - ui->use_assembly_shaders->setTristate(true); - ui->use_fast_gpu_time->setTristate(true); - ui->force_30fps_mode->setTristate(true); - ConfigurationShared::InsertGlobalItem(ui->anisotropic_filtering_combobox); + ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); + ConfigurationShared::SetColoredTristate( + ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders); + ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, + Settings::values.use_asynchronous_shaders, + use_asynchronous_shaders); + ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, + Settings::values.use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::SetColoredComboBox( + ui->gpu_accuracy, ui->label_gpu_accuracy, + static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); + ConfigurationShared::SetColoredComboBox( + ui->anisotropic_filtering_combobox, ui->af_label, + static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); } diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index c043588ff..e61b571c7 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -7,6 +7,10 @@ #include <memory> #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} + namespace Ui { class ConfigureGraphicsAdvanced; } @@ -29,4 +33,9 @@ private: void SetupPerGameUI(); std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; + + ConfigurationShared::CheckState use_vsync; + ConfigurationShared::CheckState use_assembly_shaders; + ConfigurationShared::CheckState use_asynchronous_shaders; + ConfigurationShared::CheckState use_fast_gpu_time; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 0021607ac..846a30586 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>400</width> + <width>404</width> <height>321</height> </rect> </property> @@ -23,34 +23,48 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label_gpu_accuracy"> - <property name="text"> - <string>Accuracy Level:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="gpu_accuracy"> - <item> + <widget class="QWidget" name="gpu_accuracy_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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="label_gpu_accuracy"> <property name="text"> - <string notr="true">Normal</string> + <string>Accuracy Level:</string> </property> - </item> - <item> - <property name="text"> - <string notr="true">High</string> - </property> - </item> - <item> - <property name="text"> - <string notr="true">Extreme(very slow)</string> - </property> - </item> - </widget> - </item> - </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="gpu_accuracy"> + <item> + <property name="text"> + <string notr="true">Normal</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">High</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">Extreme(very slow)</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> </item> <item> <widget class="QCheckBox" name="use_vsync"> @@ -73,9 +87,12 @@ </widget> </item> <item> - <widget class="QCheckBox" name="force_30fps_mode"> + <widget class="QCheckBox" name="use_asynchronous_shaders"> + <property name="toolTip"> + <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> + </property> <property name="text"> - <string>Force 30 FPS mode</string> + <string>Use asynchronous shader building (experimental)</string> </property> </widget> </item> @@ -87,44 +104,58 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_1"> - <item> - <widget class="QLabel" name="af_label"> - <property name="text"> - <string>Anisotropic Filtering:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="anisotropic_filtering_combobox"> - <item> - <property name="text"> - <string>Default</string> - </property> - </item> - <item> - <property name="text"> - <string>2x</string> - </property> - </item> - <item> - <property name="text"> - <string>4x</string> - </property> - </item> - <item> - <property name="text"> - <string>8x</string> - </property> - </item> - <item> + <widget class="QWidget" name="af_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_1"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="af_label"> <property name="text"> - <string>16x</string> + <string>Anisotropic Filtering:</string> </property> - </item> - </widget> - </item> - </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="anisotropic_filtering_combobox"> + <item> + <property name="text"> + <string>Default</string> + </property> + </item> + <item> + <property name="text"> + <string>2x</string> + </property> + </item> + <item> + <property name="text"> + <string>4x</string> + </property> + </item> + <item> + <property name="text"> + <string>8x</string> + </property> + </item> + <item> + <property name="text"> + <string>16x</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 6f7fd4414..cbee51a5e 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -154,7 +154,7 @@ void ConfigureHotkeys::ClearAll() { const QStandardItem* parent = model->item(r, 0); for (int r2 = 0; r2 < parent->rowCount(); ++r2) { - model->item(r, 0)->child(r2, 1)->setText(tr("")); + model->item(r, 0)->child(r2, 1)->setText(QString{}); } } } @@ -186,7 +186,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); } }); - connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, tr("")); }); + connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); }); context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index f2977719c..2725fcb2b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -8,18 +8,33 @@ #include <QSignalBlocker> #include <QTimer> -#include "configuration/configure_touchscreen_advanced.h" #include "core/core.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" -#include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/sm/sm.h" #include "ui_configure_input.h" +#include "ui_configure_input_advanced.h" #include "ui_configure_input_player.h" +#include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_advanced.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_mouse_advanced.h" +#include "yuzu/configuration/configure_touchscreen_advanced.h" + +namespace { +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { + Dialog dialog(&parent, std::forward<Args>(args)...); + + const auto res = dialog.exec(); + if (res == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} +} // Anonymous namespace void OnDockedModeChanged(bool last_state, bool new_state) { if (last_state == new_state) { @@ -48,97 +63,120 @@ void OnDockedModeChanged(bool last_state, bool new_state) { } } -namespace { -template <typename Dialog, typename... Args> -void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { - parent.ApplyConfiguration(); - Dialog dialog(&parent, std::forward<Args>(args)...); - - const auto res = dialog.exec(); - if (res == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} -} // Anonymous namespace - ConfigureInput::ConfigureInput(QWidget* parent) - : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { ui->setupUi(this); +} - players_controller = { - ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox, - ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox, +ConfigureInput::~ConfigureInput() = default; + +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), }; - players_configure = { - ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure, - ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure, + player_tabs = { + ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, + ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, }; - RetranslateUI(); - LoadConfiguration(); - UpdateUIEnabled(); + player_connected = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; - connect(ui->restore_defaults_button, &QPushButton::clicked, this, - &ConfigureInput::RestoreDefaults); + 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 (auto* enabled : players_controller) { - connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - &ConfigureInput::UpdateUIEnabled); - } - connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); - connect(ui->handheld_connected, &QCheckBox::stateChanged, this, - &ConfigureInput::UpdateUIEnabled); - connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); - connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); - connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); - connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, - &ConfigureInput::UpdateUIEnabled); - - for (std::size_t i = 0; i < players_configure.size(); ++i) { - connect(players_configure[i], &QPushButton::clicked, this, - [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); }); + 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]); + connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { + if (is_connected) { + for (std::size_t index = 0; index <= i; ++index) { + player_connected[index]->setChecked(is_connected); + } + } else { + for (std::size_t index = i; index < player_tabs.size(); ++index) { + player_connected[index]->setChecked(is_connected); + } + } + }); + connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, + [this] { UpdateAllInputDevices(); }); + 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, + [this](bool is_handheld) { UpdateDockedState(is_handheld); }); + + advanced = new ConfigureInputAdvanced(this); + ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); + ui->tabAdvanced->layout()->addWidget(advanced); + connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { + CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem); + }); + 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->handheld_configure, &QPushButton::clicked, this, - [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); }); - - connect(ui->debug_configure, &QPushButton::clicked, this, - [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); }); + connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { + CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); + }); - connect(ui->mouse_advanced, &QPushButton::clicked, this, - [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); + connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); - connect(ui->touchscreen_advanced, &QPushButton::clicked, this, - [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); + RetranslateUI(); + LoadConfiguration(); } -ConfigureInput::~ConfigureInput() = default; +QList<QWidget*> ConfigureInput::GetSubTabs() const { + return { + ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, + ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced, + }; +} void ConfigureInput::ApplyConfiguration() { - for (std::size_t i = 0; i < players_controller.size(); ++i) { - const auto controller_type_index = players_controller[i]->currentIndex(); - - Settings::values.players[i].connected = controller_type_index != 0; - - if (controller_type_index > 0) { - Settings::values.players[i].type = - static_cast<Settings::ControllerType>(controller_type_index - 1); - } else { - Settings::values.players[i].type = Settings::ControllerType::DualJoycon; - } + for (auto controller : player_controllers) { + controller->ApplyConfiguration(); } + advanced->ApplyConfiguration(); + const bool pre_docked_mode = Settings::values.use_docked_mode; - Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); + Settings::values.use_docked_mode = ui->radioDocked->isChecked(); OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); - Settings::values - .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] - .connected = ui->handheld_connected->isChecked(); - 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.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); + + Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); + Settings::values.motion_enabled = ui->motionGroup->isChecked(); } void ConfigureInput::changeEvent(QEvent* event) { @@ -146,94 +184,64 @@ void ConfigureInput::changeEvent(QEvent* event) { RetranslateUI(); } - QDialog::changeEvent(event); + QWidget::changeEvent(event); } void ConfigureInput::RetranslateUI() { ui->retranslateUi(this); - RetranslateControllerComboBoxes(); } -void ConfigureInput::RetranslateControllerComboBoxes() { - for (auto* controller_box : players_controller) { - [[maybe_unused]] const QSignalBlocker blocker(controller_box); - - controller_box->clear(); - controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"), - tr("Single Right Joycon"), tr("Single Left Joycon")}); - } - +void ConfigureInput::LoadConfiguration() { LoadPlayerControllerIndices(); -} + UpdateDockedState(Settings::values.players[8].connected); -void ConfigureInput::UpdateUIEnabled() { - bool hit_disabled = false; - for (auto* player : players_controller) { - player->setDisabled(hit_disabled); - if (hit_disabled) { - player->setCurrentIndex(0); - } - if (!hit_disabled && player->currentIndex() == 0) { - hit_disabled = true; - } - } + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); + ui->motionGroup->setChecked(Settings::values.motion_enabled); +} - for (std::size_t i = 0; i < players_controller.size(); ++i) { - players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); +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); + player_connected[i]->setChecked(connected); } +} - ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() && - !ui->use_docked_mode->isChecked()); - ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); - ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && - !ui->use_docked_mode->isChecked()); - ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); - ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); - ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); +void ConfigureInput::ClearAll() { + // We don't have a good way to know what tab is active, but we can find out by getting the + // parent of the consoleInputSettings + auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent()); + player_tab->ClearAll(); } -void ConfigureInput::LoadConfiguration() { - std::stable_partition( - Settings::values.players.begin(), - Settings::values.players.begin() + - Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), - [](const auto& player) { return player.connected; }); +void ConfigureInput::RestoreDefaults() { + // We don't have a good way to know what tab is active, but we can find out by getting the + // parent of the consoleInputSettings + auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent()); + player_tab->RestoreDefaults(); + + ui->radioDocked->setChecked(true); + ui->radioUndocked->setChecked(false); + ui->vibrationGroup->setChecked(true); + ui->motionGroup->setChecked(true); +} - LoadPlayerControllerIndices(); +void ConfigureInput::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->use_docked_mode->setChecked(Settings::values.use_docked_mode); - ui->handheld_connected->setChecked( - Settings::values - .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] - .connected); - 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->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); - - UpdateUIEnabled(); -} + ui->radioDocked->setChecked(Settings::values.use_docked_mode); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); -void ConfigureInput::LoadPlayerControllerIndices() { - for (std::size_t i = 0; i < players_controller.size(); ++i) { - const auto connected = Settings::values.players[i].connected; - players_controller[i]->setCurrentIndex( - connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); } } -void ConfigureInput::RestoreDefaults() { - players_controller[0]->setCurrentIndex(2); - - for (std::size_t i = 1; i < players_controller.size(); ++i) { - players_controller[i]->setCurrentIndex(0); +void ConfigureInput::UpdateAllInputDevices() { + for (const auto& player : player_controllers) { + player->UpdateInputDevices(); } - - ui->use_docked_mode->setCheckState(Qt::Unchecked); - ui->handheld_connected->setCheckState(Qt::Unchecked); - ui->mouse_enabled->setCheckState(Qt::Unchecked); - ui->keyboard_enabled->setCheckState(Qt::Unchecked); - ui->debug_enabled->setCheckState(Qt::Unchecked); - ui->touchscreen_enabled->setCheckState(Qt::Checked); - UpdateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 2f70cb3ca..0e8b2fd4e 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,37 +7,50 @@ #include <array> #include <memory> -#include <QDialog> #include <QKeyEvent> +#include <QWidget> + +#include "yuzu/configuration/configure_input_advanced.h" +#include "yuzu/configuration/configure_input_player.h" #include "ui_configure_input.h" -class QPushButton; +class QCheckBox; class QString; class QTimer; +namespace InputCommon { +class InputSubsystem; +} + namespace Ui { class ConfigureInput; } void OnDockedModeChanged(bool last_state, bool new_state); -class ConfigureInput : public QDialog { +class ConfigureInput : public QWidget { Q_OBJECT public: explicit ConfigureInput(QWidget* parent = nullptr); ~ConfigureInput() override; - /// Save all button configurations to settings file + /// Initializes the input dialog with the given input subsystem. + void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); + + /// Save all button configurations to settings file. void ApplyConfiguration(); + QList<QWidget*> GetSubTabs() const; + private: void changeEvent(QEvent* event) override; void RetranslateUI(); - void RetranslateControllerComboBoxes(); + void ClearAll(); - void UpdateUIEnabled(); + void UpdateDockedState(bool is_handheld); + void UpdateAllInputDevices(); /// Load configuration settings. void LoadConfiguration(); @@ -48,6 +61,8 @@ private: std::unique_ptr<Ui::ConfigureInput> ui; - std::array<QComboBox*, 8> players_controller; - std::array<QPushButton*, 8> players_configure; + std::array<ConfigureInputPlayer*, 8> player_controllers; + std::array<QWidget*, 8> player_tabs; + std::array<QCheckBox*, 8> player_connected; + ConfigureInputAdvanced* advanced; }; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index efffd8487..136955224 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,529 +1,554 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ConfigureInput</class> - <widget class="QDialog" name="ConfigureInput"> + <widget class="QWidget" name="ConfigureInput"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>384</width> - <height>576</height> + <width>700</width> + <height>540</height> </rect> </property> <property name="windowTitle"> - <string>Custom Input Settings</string> + <string>ConfigureInput</string> </property> <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="spacing"> + <number>2</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> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QGroupBox" name="gridGroupBox_1"> - <property name="title"> - <string>Players</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="2"> - <widget class="QComboBox" name="player1_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="1" column="3"> - <widget class="QPushButton" name="player1_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Controller Type</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QComboBox" name="player2_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="3" column="2"> - <widget class="QComboBox" name="player3_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="4" column="2"> - <widget class="QComboBox" name="player4_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="5" column="2"> - <widget class="QComboBox" name="player5_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="6" column="2"> - <widget class="QComboBox" name="player6_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="7" column="2"> - <widget class="QComboBox" name="player7_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="8" column="2"> - <widget class="QComboBox" name="player8_combobox"> - <property name="minimumSize"> - <size> - <width>110</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QPushButton" name="player2_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="3" column="3"> - <widget class="QPushButton" name="player3_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="4" column="3"> - <widget class="QPushButton" name="player4_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="5" column="3"> - <widget class="QPushButton" name="player5_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="6" column="3"> - <widget class="QPushButton" name="player6_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="7" column="3"> - <widget class="QPushButton" name="player7_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="8" column="3"> - <widget class="QPushButton" name="player8_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="4"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="label_3"> - <property name="minimumSize"> - <size> - <width>55</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>Player 1</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Player 2</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Player 3</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Player 4</string> - </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>Player 5</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>Player 6</string> - </property> - </widget> - </item> - <item row="7" column="1"> - <widget class="QLabel" name="label_9"> - <property name="text"> - <string>Player 7</string> - </property> - </widget> - </item> - <item row="8" column="1"> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Player 8</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="gridGroupBox_2"> - <property name="title"> - <string>Handheld</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="2"> - <spacer name="horizontalSpacer_5"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>72</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="4"> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="3"> - <widget class="QPushButton" name="handheld_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="1"> - <widget class="QCheckBox" name="handheld_connected"> - <property name="text"> - <string>Joycons Docked</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QCheckBox" name="use_docked_mode"> - <property name="text"> - <string>Use Docked Mode</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="gridGroupBox_3"> - <property name="title"> - <string>Other</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="1" column="1"> - <widget class="QCheckBox" name="keyboard_enabled"> - <property name="minimumSize"> - <size> - <width>0</width> - <height>23</height> - </size> - </property> - <property name="text"> - <string>Keyboard</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QCheckBox" name="debug_enabled"> - <property name="text"> - <string>Debug Controller</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="touchscreen_enabled"> - <property name="text"> - <string>Touchscreen</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QCheckBox" name="mouse_enabled"> - <property name="minimumSize"> - <size> - <width>0</width> - <height>23</height> - </size> - </property> - <property name="text"> - <string>Mouse</string> - </property> - </widget> - </item> - <item row="0" column="4"> - <spacer name="horizontalSpacer_7"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer_8"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>76</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0"> - <spacer name="horizontalSpacer_6"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="3" column="3"> - <widget class="QPushButton" name="touchscreen_advanced"> - <property name="text"> - <string>Advanced</string> - </property> - </widget> - </item> - <item row="2" column="3"> - <widget class="QPushButton" name="debug_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="0" column="3"> - <widget class="QPushButton" name="mouse_advanced"> - <property name="text"> - <string>Advanced</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QPushButton" name="restore_defaults_button"> - <property name="text"> - <string>Restore Defaults</string> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tabPlayer1"> + <property name="accessibleName"> + <string>Player 1</string> + </property> + <attribute name="title"> + <string>Player 1</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer2"> + <property name="accessibleName"> + <string>Player 2</string> + </property> + <attribute name="title"> + <string>Player 2</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer3"> + <property name="accessibleName"> + <string>Player 3</string> + </property> + <attribute name="title"> + <string>Player 3</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer4"> + <property name="accessibleName"> + <string>Player 4</string> + </property> + <attribute name="title"> + <string>Player 4</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer5"> + <property name="accessibleName"> + <string>Player 5</string> + </property> + <attribute name="title"> + <string>Player 5</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer6"> + <property name="accessibleName"> + <string>Player 6</string> + </property> + <attribute name="title"> + <string>Player 6</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer7"> + <property name="accessibleName"> + <string>Player 7</string> + </property> + <attribute name="title"> + <string>Player 7</string> + </attribute> + </widget> + <widget class="QWidget" name="tabPlayer8"> + <property name="accessibleName"> + <string>Player 8</string> + </property> + <attribute name="title"> + <string>Player 8</string> + </attribute> + </widget> + <widget class="QWidget" name="tabAdvanced"> + <property name="accessibleName"> + <string>Advanced</string> + </property> + <attribute name="title"> + <string>Advanced</string> + </attribute> + </widget> + </widget> + </item> + <item alignment="Qt::AlignVCenter"> + <widget class="QWidget" name="consoleInputSettings" native="true"> + <layout class="QHBoxLayout" name="buttonsBottomRightHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignVCenter"> + <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"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>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_2"> + <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="vibrationSpin"> + <property name="minimumSize"> + <size> + <width>65</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>65</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> + <property name="value"> + <number>100</number> + </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_3"> + <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>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignVCenter"> + <widget class="QWidget" name="widget" native="true"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_9"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <property name="rightMargin"> + <number>0</number> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <property name="bottomMargin"> + <number>0</number> </property> - </spacer> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + <property name="spacing"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - </layout> + <item row="1" column="2"> + <widget class="QCheckBox" name="checkboxPlayer2Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Controllers</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QCheckBox" name="checkboxPlayer4Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QCheckBox" name="checkboxPlayer3Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QCheckBox" name="checkboxPlayer5Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>1</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="7"> + <widget class="QCheckBox" name="checkboxPlayer7Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="6"> + <widget class="QCheckBox" name="checkboxPlayer6Connected"> + <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>true</bool> + </property> + </widget> + </item> + <item row="1" column="8"> + <widget class="QCheckBox" name="checkboxPlayer8Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>2</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>3</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>4</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>5</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="6"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>6</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>7</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="8"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>8</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Connected</string> + </property> + </widget> + </item> + </layout> + </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 alignment="Qt::AlignBottom"> + <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Defaults</string> + </property> + </widget> + </item> + <item alignment="Qt::AlignBottom"> + <widget class="QPushButton" name="buttonClearAll"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + </layout> + </widget> </item> </layout> </widget> <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ConfigureInput</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>294</x> - <y>553</y> - </hint> - <hint type="destinationlabel"> - <x>191</x> - <y>287</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>ConfigureInput</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>294</x> - <y>553</y> - </hint> - <hint type="destinationlabel"> - <x>191</x> - <y>287</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp new file mode 100644 index 000000000..81f9dc16c --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -0,0 +1,171 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QColorDialog> +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_input_advanced.h" +#include "yuzu/configuration/configure_input_advanced.h" + +ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()) { + ui->setupUi(this); + + controllers_color_buttons = {{ + { + ui->player1_left_body_button, + ui->player1_left_buttons_button, + ui->player1_right_body_button, + ui->player1_right_buttons_button, + }, + { + ui->player2_left_body_button, + ui->player2_left_buttons_button, + ui->player2_right_body_button, + ui->player2_right_buttons_button, + }, + { + ui->player3_left_body_button, + ui->player3_left_buttons_button, + ui->player3_right_body_button, + ui->player3_right_buttons_button, + }, + { + ui->player4_left_body_button, + ui->player4_left_buttons_button, + ui->player4_right_body_button, + ui->player4_right_buttons_button, + }, + { + ui->player5_left_body_button, + ui->player5_left_buttons_button, + ui->player5_right_body_button, + ui->player5_right_buttons_button, + }, + { + ui->player6_left_body_button, + ui->player6_left_buttons_button, + ui->player6_right_body_button, + ui->player6_right_buttons_button, + }, + { + ui->player7_left_body_button, + ui->player7_left_buttons_button, + ui->player7_right_body_button, + ui->player7_right_buttons_button, + }, + { + ui->player8_left_body_button, + ui->player8_left_buttons_button, + ui->player8_right_body_button, + ui->player8_right_buttons_button, + }, + }}; + + for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { + auto& color_buttons = controllers_color_buttons[player_idx]; + 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)); + }); + } + } + + connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, + &ConfigureInputAdvanced::UpdateUIEnabled); + connect(ui->debug_enabled, &QCheckBox::stateChanged, this, + &ConfigureInputAdvanced::UpdateUIEnabled); + connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, + &ConfigureInputAdvanced::UpdateUIEnabled); + + connect(ui->debug_configure, &QPushButton::clicked, this, + [this] { CallDebugControllerDialog(); }); + 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) { + 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;") + .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]; + std::array<u32, 4> colors{}; + std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(), + colors.begin(), [](QColor color) { return color.rgb(); }); + + player.body_color_left = colors[0]; + player.button_color_left = colors[1]; + player.body_color_right = colors[2]; + player.button_color_right = colors[3]; + } + + 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.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]; + std::array<u32, 4> colors = { + player.body_color_left, + player.button_color_left, + player.body_color_right, + player.button_color_right, + }; + + std::transform(colors.begin(), colors.end(), controllers_colors[player_idx].begin(), + [](u32 rgb) { return QColor::fromRgb(rgb); }); + + 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;") + .arg(controllers_colors[player_idx][button_idx].name())); + } + } + + 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->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + + UpdateUIEnabled(); +} + +void ConfigureInputAdvanced::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void ConfigureInputAdvanced::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureInputAdvanced::UpdateUIEnabled() { + ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); + ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); + ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); +} diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h new file mode 100644 index 000000000..50bb87768 --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -0,0 +1,46 @@ +// 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 <QWidget> + +class QColor; +class QPushButton; + +namespace Ui { +class ConfigureInputAdvanced; +} + +class ConfigureInputAdvanced : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInputAdvanced(QWidget* parent = nullptr); + ~ConfigureInputAdvanced() override; + + void ApplyConfiguration(); + +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 LoadConfiguration(); + + std::unique_ptr<Ui::ConfigureInputAdvanced> ui; + + std::array<std::array<QColor, 4>, 8> controllers_colors; + std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons; +}; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui new file mode 100644 index 000000000..5958435fc --- /dev/null +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -0,0 +1,2688 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputAdvanced</class> + <widget class="QWidget" name="ConfigureInputAdvanced"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>710</width> + <height>580</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Input</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <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="mainInputAdvanced" native="true"> + <layout class="QHBoxLayout" name="main" stretch="1,1"> + <property name="spacing"> + <number>9</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="leftInputAdvanced" native="true"> + <layout class="QVBoxLayout" name="leftLayout" stretch="0"> + <property name="spacing"> + <number>3</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="QGroupBox" name="joyconColorsGroup"> + <property name="title"> + <string>Joycon Colors</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,1"> + <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="topLeftInputAdvanced" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="spacing"> + <number>6</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="player12Widget" 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="player1Group"> + <property name="title"> + <string>Player 1</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player1LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_14"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player1LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_66"> + <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="player1_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player1LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_67"> + <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="player1_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player1RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_14"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player1RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_64"> + <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="player1_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player1RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_65"> + <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="player1_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="player2Group"> + <property name="title"> + <string>Player 2</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player2LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player2LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_70"> + <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="player2_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player2LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_71"> + <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="player2_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player2RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player2RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_68"> + <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="player2_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player2RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_69"> + <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="player2_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player34Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <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="player3Group"> + <property name="title"> + <string>Player 3</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player3LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player3LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_74"> + <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="player3_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player3LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_75"> + <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="player3_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player3RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player3RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_72"> + <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="player3_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player3RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_73"> + <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="player3_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="player4Group"> + <property name="title"> + <string>Player 4</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player4LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player4LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_78"> + <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="player4_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player4LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_79"> + <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="player4_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player4RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player4RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_76"> + <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="player4_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player4RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_77"> + <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="player4_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="bottomLeftInputAdvanced" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="spacing"> + <number>6</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="player56Widget" 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="player5Group"> + <property name="title"> + <string>Player 5</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player5LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_10"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player5LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_50"> + <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="player5_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player5LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_51"> + <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="player5_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player5RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_10"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player5RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_48"> + <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="player5_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player5RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_49"> + <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="player5_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="player6Group"> + <property name="title"> + <string>Player 6</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player6LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_11"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player6LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_54"> + <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="player6_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player6LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_55"> + <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="player6_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player6RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_11"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player6RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_52"> + <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="player6_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player6RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_53"> + <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="player6_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player78Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="player7Group"> + <property name="title"> + <string>Player 7</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player7LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_12"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player7LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_58"> + <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="player7_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player7LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_59"> + <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="player7_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player7RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_12"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player7RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_56"> + <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="player7_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player7RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_57"> + <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="player7_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="player8Group"> + <property name="title"> + <string>Player 8</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QWidget" name="player8LeftJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_13"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player8LeftBodyGroup"> + <property name="title"> + <string>L Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_62"> + <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="player8_left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player8LeftButtonsGroup"> + <property name="title"> + <string>L Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_63"> + <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="player8_left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player8RightJoycon" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_13"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player8RightBodyGroup"> + <property name="title"> + <string>R Body</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_60"> + <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="player8_right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="player8RightButtonsGroup"> + <property name="title"> + <string>R Button</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_61"> + <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="player8_right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="rightInputAdvanced" native="true"> + <layout class="QVBoxLayout" name="rightLayout" stretch="1,1"> + <property name="spacing"> + <number>3</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="topRightInputAdvanced" native="true"> + <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> + <widget class="QGroupBox" name="gridGroupBox_3"> + <property name="title"> + <string>Other</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QCheckBox" name="keyboard_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Keyboard</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QPushButton" name="touchscreen_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>76</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="mouse_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="touchscreen_enabled"> + <property name="text"> + <string>Touchscreen</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="mouse_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Mouse</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="motion_touch"> + <property name="text"> + <string>Motion / Touch</string> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QPushButton" name="buttonMotionTouch"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="debug_enabled"> + <property name="text"> + <string>Debug Controller</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QPushButton" name="debug_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="bottomRightInputAdvanced" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <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="mainVerticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + </resources> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp new file mode 100644 index 000000000..1866003c2 --- /dev/null +++ b/src/yuzu/configuration/configure_input_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_dialog.h" +#include "yuzu/configuration/configure_input_dialog.h" + +ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()), + input_widget(new ConfigureInput(this)) { + ui->setupUi(this); + + input_widget->Initialize(input_subsystem, max_players); + + ui->inputLayout->addWidget(input_widget); + + RetranslateUI(); +} + +ConfigureInputDialog::~ConfigureInputDialog() = default; + +void ConfigureInputDialog::ApplyConfiguration() { + input_widget->ApplyConfiguration(); +} + +void ConfigureInputDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureInputDialog::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h new file mode 100644 index 000000000..d1bd865f9 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.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 <QDialog> +#include "yuzu/configuration/configure_input.h" + +class QPushButton; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputDialog; +} + +class ConfigureInputDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem); + ~ConfigureInputDialog() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureInputDialog> ui; + + ConfigureInput* input_widget; +}; diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_dialog.ui new file mode 100644 index 000000000..b92ddb200 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.ui @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputDialog</class> + <widget class="QDialog" name="ConfigureInputDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>70</width> + <height>540</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Input</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="inputLayout"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <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>ConfigureInputDialog</receiver> + <slot>accept()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 00433926d..698cb1940 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -5,39 +5,98 @@ #include <algorithm> #include <memory> #include <utility> -#include <QColorDialog> #include <QGridLayout> +#include <QInputDialog> #include <QKeyEvent> #include <QMenu> #include <QMessageBox> #include <QTimer> -#include "common/assert.h" #include "common/param_package.h" +#include "core/core.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "input_common/gcadapter/gc_poller.h" #include "input_common/main.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" +constexpr std::size_t HANDHELD_INDEX = 8; + const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> ConfigureInputPlayer::analog_sub_buttons{{ "up", "down", "left", "right", - "modifier", }}; -static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) { - const int index1 = grid->indexOf(item); - const int index2 = grid->indexOf(onTopOf); - int row, column, rowSpan, columnSpan; - grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan); - grid->takeAt(index1); - grid->addWidget(item, row, column, rowSpan, columnSpan); +namespace { + +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); +} + +/// 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; + } } -static QString GetKeyName(int key_code) { +/// 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: + return QObject::tr("Click 0"); + case Qt::RightButton: + return QObject::tr("Click 1"); + case Qt::MiddleButton: + return QObject::tr("Click 2"); + case Qt::BackButton: + return QObject::tr("Click 3"); + case Qt::ForwardButton: + return QObject::tr("Click 4"); case Qt::Key_Shift: return QObject::tr("Shift"); case Qt::Key_Control: @@ -51,9 +110,16 @@ static QString GetKeyName(int key_code) { } } -static void SetAnalogButton(const Common::ParamPackage& input_param, - Common::ParamPackage& analog_param, const std::string& button_name) { - if (analog_param.Get("engine", "") != "analog_from_button") { +void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, + const std::string& button_name) { + // The poller returned a complete axis, so set all the buttons + if (input_param.Has("axis_x") && input_param.Has("axis_y")) { + analog_param = input_param; + return; + } + // Check if the current configuration has either no engine or an axis binding. + // Clears out the old binding and adds one with analog_from_button. + if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) { analog_param = { {"engine", "analog_from_button"}, }; @@ -61,7 +127,7 @@ static void SetAnalogButton(const Common::ParamPackage& input_param, analog_param.Set(button_name, input_param.Serialize()); } -static QString ButtonToText(const Common::ParamPackage& param) { +QString ButtonToText(const Common::ParamPackage& param) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } @@ -84,6 +150,14 @@ static 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", "")); @@ -111,7 +185,7 @@ static QString ButtonToText(const Common::ParamPackage& param) { return QObject::tr("[unknown]"); } -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { +QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } @@ -161,22 +235,30 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string } return QObject::tr("[unknown]"); } - -ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug) - : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), - debug(debug), timeout_timer(std::make_unique<QTimer>()), - poll_timer(std::make_unique<QTimer>()) { +} // namespace + +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, + QWidget* bottom_row, + InputCommon::InputSubsystem* input_subsystem_, + 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) { ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); button_map = { - ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, - ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, - ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, - ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, - ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, - ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, - ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, + ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, + ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, + ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, + ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, + }; + + mod_buttons = { + ui->buttonLStickMod, + ui->buttonRStickMod, }; analog_map_buttons = {{ @@ -185,216 +267,243 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i ui->buttonLStickDown, ui->buttonLStickLeft, ui->buttonLStickRight, - ui->buttonLStickMod, }, { ui->buttonRStickUp, ui->buttonRStickDown, ui->buttonRStickLeft, ui->buttonRStickRight, - ui->buttonRStickMod, }, }}; - debug_hidden = { - ui->buttonSL, ui->labelSL, - ui->buttonSR, ui->labelSR, - ui->buttonLStick, ui->labelLStickPressed, - ui->buttonRStick, ui->labelRStickPressed, - ui->buttonHome, ui->labelHome, - ui->buttonScreenshot, ui->labelScreenshot, + motion_map = { + ui->buttonMotionLeft, + ui->buttonMotionRight, }; - auto layout = Settings::values.players[player_index].type; - if (debug) - layout = Settings::ControllerType::DualJoycon; + 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_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, InputCommon::Polling::DeviceType type) { + connect(button, &QPushButton::clicked, [=, this] { + HandleClick( + button, + [=, this](Common::ParamPackage params) { + // Workaround for ZL & ZR for analog triggers like on XBOX + // controllers. Analog triggers (from controllers like the XBOX + // controller) would not work due to a different range of their + // signals (from 0 to 255 on analog triggers instead of -32768 to + // 32768 on analog joysticks). The SDL driver misinterprets analog + // triggers as analog joysticks. + // TODO: reinterpret the signal range for analog triggers to map the + // values correctly. This is required for the correct emulation of + // the analog triggers of the GameCube controller. + if (button == ui->buttonZL || button == ui->buttonZR) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } + *param = std::move(params); + }, + type); + }); + }; - switch (layout) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::DualJoycon: - layout_hidden = { - ui->buttonSL, - ui->labelSL, - ui->buttonSR, - ui->labelSR, - }; - break; - case Settings::ControllerType::LeftJoycon: - layout_hidden = { - ui->right_body_button, - ui->right_buttons_button, - ui->right_body_label, - ui->right_buttons_label, - ui->buttonR, - ui->labelR, - ui->buttonZR, - ui->labelZR, - ui->labelHome, - ui->buttonHome, - ui->buttonPlus, - ui->labelPlus, - ui->RStick, - ui->faceButtons, - }; - break; - case Settings::ControllerType::RightJoycon: - layout_hidden = { - ui->left_body_button, ui->left_buttons_button, - ui->left_body_label, ui->left_buttons_label, - ui->buttonL, ui->labelL, - ui->buttonZL, ui->labelZL, - ui->labelScreenshot, ui->buttonScreenshot, - ui->buttonMinus, ui->labelMinus, - ui->LStick, ui->Dpad, - }; - break; - } + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + auto* const button = button_map[button_id]; - if (debug || layout == Settings::ControllerType::ProController) { - ui->controller_color->hide(); - } else { - if (layout == Settings::ControllerType::LeftJoycon || - layout == Settings::ControllerType::RightJoycon) { - ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0}); - - LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad); - LayerGridElements(ui->buttons, ui->misc, ui->RStick); - LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons); - LayerGridElements(ui->buttons, ui->RStick, ui->LStick); + if (button == nullptr) { + continue; } - } - for (auto* widget : layout_hidden) - widget->setVisible(false); + ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], + Config::default_buttons[button_id], + InputCommon::Polling::DeviceType::Button); - analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; - analog_map_deadzone_and_modifier_slider = {ui->sliderLStickDeadzoneAndModifier, - ui->sliderRStickDeadzoneAndModifier}; - analog_map_deadzone_and_modifier_slider_label = {ui->labelLStickDeadzoneAndModifier, - ui->labelRStickDeadzoneAndModifier}; + button->setContextMenuPolicy(Qt::CustomContextMenu); - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - auto* const button = button_map[button_id]; + 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)); + }); + } + + 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], &motions_param[motion_id], + Config::default_motions[motion_id], + InputCommon::Polling::DeviceType::Motion); + button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button, &QPushButton::clicked, [=] { - HandleClick(button_map[button_id], - [=](Common::ParamPackage params) { - // Workaround for ZL & ZR for analog triggers like on XBOX controllors. - // Analog triggers (from controllers like the XBOX controller) would not - // work due to a different range of their signals (from 0 to 255 on - // analog triggers instead of -32768 to 32768 on analog joysticks). The - // SDL driver misinterprets analog triggers as analog joysticks. - // TODO: reinterpret the signal range for analog triggers to map the - // values correctly. This is required for the correct emulation of the - // analog triggers of the GameCube controller. - if (button_id == Settings::NativeButton::ZL || - button_id == Settings::NativeButton::ZR) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } - buttons_param[button_id] = std::move(params); - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - }); + + 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++) { + 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) { auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + if (analog_button == nullptr) { continue; } - analog_button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(analog_button, &QPushButton::clicked, [=]() { - HandleClick(analog_map_buttons[analog_id][sub_button_id], - [=](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - }, - InputCommon::Polling::DeviceType::Button); + connect(analog_button, &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_buttons[analog_id][sub_button_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, - [=](const QPoint& menu_location) { + [=, this](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), [&] { - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + analogs_param[analog_id].Clear(); analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); }); - context_menu.addAction(tr("Restore Default"), [&] { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[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)); }); } - connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { - if (QMessageBox::information( - this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically."), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - HandleClick( - analog_map_stick[analog_id], - [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, - InputCommon::Polling::DeviceType::Analog); - } + + // Handle clicks for the modifier buttons as well. + ConfigureButtonClick(mod_buttons[analog_id], &stick_mod_param[analog_id], + Config::default_stick_mod[analog_id], + InputCommon::Polling::DeviceType::Button); + + mod_buttons[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(mod_buttons[analog_id], &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + stick_mod_param[analog_id].Clear(); + mod_buttons[analog_id]->setText(tr("[not set]")); + }); + context_menu.exec(mod_buttons[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); + }); + + 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); }); - connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { - const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); - if (analogs_param[analog_id].Get("engine", "") == "sdl" || - analogs_param[analog_id].Get("engine", "") == "gcpad") { - analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( - tr("Deadzone: %1%").arg(slider_value)); - analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); - } else { - analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( - tr("Modifier Scale: %1%").arg(slider_value)); - analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f); - } + connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { + const auto slider_value = analog_map_modifier_slider[analog_id]->value(); + analog_map_modifier_label[analog_id]->setText( + tr("Modifier Range: %1%").arg(slider_value)); + analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f); }); } - connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); + // 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"), + }; + + if (player_index == 0) { + controller_types.append(tr("Handheld")); + connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), + [this](int index) { + emit HandheldStateChanged(GetControllerTypeFromIndex(index) == + Settings::ControllerType::Handheld); + }); + } + + // 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); + } else { + ui->comboControllerType->addItems(controller_types); + } + + UpdateControllerIcon(); + UpdateControllerAvailableButtons(); + UpdateMotionButtons(); + connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { + UpdateControllerIcon(); + UpdateControllerAvailableButtons(); + UpdateMotionButtons(); + }); + + connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, + &ConfigureInputPlayer::UpdateMappingWithDefaults); + + ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); + UpdateInputDevices(); + connect(ui->buttonRefreshDevices, &QPushButton::clicked, + [this] { emit RefreshInputDevices(); }); timeout_timer->setSingleShot(true); connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); connect(poll_timer.get(), &QTimer::timeout, [this] { Common::ParamPackage params; - if (InputCommon::GetGCButtons()->IsPolling()) { - params = InputCommon::GetGCButtons()->GetNextInput(); + if (input_subsystem->GetGCButtons()->IsPolling()) { + params = input_subsystem->GetGCButtons()->GetNextInput(); if (params.Has("engine")) { SetPollingResult(params, false); return; } } - if (InputCommon::GetGCAnalogs()->IsPolling()) { - params = InputCommon::GetGCAnalogs()->GetNextInput(); + if (input_subsystem->GetGCAnalogs()->IsPolling()) { + params = input_subsystem->GetGCAnalogs()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetUDPMotions()->IsPolling()) { + params = input_subsystem->GetUDPMotions()->GetNextInput(); if (params.Has("engine")) { SetPollingResult(params, false); return; @@ -409,20 +518,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } }); - controller_color_buttons = { - ui->left_body_button, - ui->left_buttons_button, - ui->right_body_button, - ui->right_buttons_button, - }; - - for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) { - connect(controller_color_buttons[i], &QPushButton::clicked, this, - [this, i] { OnControllerButtonClick(static_cast<int>(i)); }); - } - LoadConfiguration(); - resize(0, 0); // TODO(wwylele): enable this when we actually emulate it ui->buttonHome->setEnabled(false); @@ -431,27 +527,47 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i ConfigureInputPlayer::~ConfigureInputPlayer() = default; void ConfigureInputPlayer::ApplyConfiguration() { - auto& buttons = - debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons; - auto& analogs = - debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs; + auto& player = Settings::values.players[player_index]; + auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; + auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs; std::transform(buttons_param.begin(), buttons_param.end(), buttons.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(); }); - if (debug) + if (debug) { return; + } - std::array<u32, 4> colors{}; - std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(), - [](QColor color) { return color.rgb(); }); + auto& motions = player.motions; + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + player.controller_type = + static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); + player.connected = ui->groupConnectedController->isChecked(); - Settings::values.players[player_index].body_color_left = colors[0]; - Settings::values.players[player_index].button_color_left = colors[1]; - Settings::values.players[player_index].body_color_right = colors[2]; - Settings::values.players[player_index].button_color_right = colors[3]; + // Player 2-8 + if (player_index != 0) { + UpdateController(player.controller_type, player_index, player.connected); + 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 + } + + UpdateController(player.controller_type, player_index, player.connected); + UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); } void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -459,24 +575,16 @@ void ConfigureInputPlayer::changeEvent(QEvent* event) { RetranslateUI(); } - QDialog::changeEvent(event); + QWidget::changeEvent(event); } void ConfigureInputPlayer::RetranslateUI() { ui->retranslateUi(this); - UpdateButtonLabels(); -} - -void ConfigureInputPlayer::OnControllerButtonClick(int i) { - const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]); - if (!new_bg_color.isValid()) - return; - controller_colors[i] = new_bg_color; - controller_color_buttons[i]->setStyleSheet( - QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); + UpdateUI(); } void ConfigureInputPlayer::LoadConfiguration() { + auto& player = Settings::values.players[player_index]; if (debug) { std::transform(Settings::values.debug_pad_buttons.begin(), Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -485,86 +593,110 @@ void ConfigureInputPlayer::LoadConfiguration() { Settings::values.debug_pad_analogs.end(), analogs_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); } else { - std::transform(Settings::values.players[player_index].buttons.begin(), - Settings::values.players[player_index].buttons.end(), buttons_param.begin(), + std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(Settings::values.players[player_index].analogs.begin(), - Settings::values.players[player_index].analogs.end(), analogs_param.begin(), + 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); }); } - UpdateButtonLabels(); + UpdateUI(); - if (debug) + if (debug) { return; + } - std::array<u32, 4> colors = { - Settings::values.players[player_index].body_color_left, - Settings::values.players[player_index].button_color_left, - Settings::values.players[player_index].body_color_right, - Settings::values.players[player_index].button_color_right, - }; - - std::transform(colors.begin(), colors.end(), controller_colors.begin(), - [](u32 rgb) { return QColor::fromRgb(rgb); }); + ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); + ui->groupConnectedController->setChecked( + player.connected || + (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected)); +} - for (std::size_t i = 0; i < colors.size(); ++i) { - controller_color_buttons[i]->setStyleSheet( - QStringLiteral("QPushButton { background-color: %1 }") - .arg(controller_colors[i].name())); +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::RestoreDefaults() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + // 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])}; } - 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++) { + // Reset Analogs and Modifier Buttons + 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])}; - SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); } + + stick_mod_param[analog_id] = Common::ParamPackage( + 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])}; } - UpdateButtonLabels(); - ApplyConfiguration(); + UpdateUI(); + UpdateInputDevices(); + ui->comboControllerType->setCurrentIndex(0); } void ConfigureInputPlayer::ClearAll() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + 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(); } - 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++) { + 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; } analogs_param[analog_id].Clear(); } + + stick_mod_param[analog_id].Clear(); } - UpdateButtonLabels(); - ApplyConfiguration(); + 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(); } -void ConfigureInputPlayer::UpdateButtonLabels() { - for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { +void ConfigureInputPlayer::UpdateUI() { + for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) { button_map[button]->setText(ButtonToText(buttons_param[button])); } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + 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) { auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; if (analog_button == nullptr) { @@ -574,99 +706,153 @@ void ConfigureInputPlayer::UpdateButtonLabels() { analog_button->setText( AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); } - analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + mod_buttons[analog_id]->setText(ButtonToText(stick_mod_param[analog_id])); + + 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]; + const auto modifier_label = analog_map_modifier_label[analog_id]; + const auto modifier_slider = analog_map_modifier_slider[analog_id]; + const auto range_groupbox = analog_map_range_groupbox[analog_id]; + const auto range_spinbox = analog_map_range_spinbox[analog_id]; + + int slider_value; auto& param = analogs_param[analog_id]; - auto* const analog_stick_slider = analog_map_deadzone_and_modifier_slider[analog_id]; - auto* const analog_stick_slider_label = - analog_map_deadzone_and_modifier_slider_label[analog_id]; - - if (param.Has("engine")) { - if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") { - if (!param.Has("deadzone")) { - param.Set("deadzone", 0.1f); - } - - analog_stick_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100)); - if (analog_stick_slider->value() == 0) { - analog_stick_slider_label->setText(tr("Deadzone: 0%")); - } - } else { - if (!param.Has("modifier_scale")) { - param.Set("modifier_scale", 0.5f); - } - - analog_stick_slider->setValue( - static_cast<int>(param.Get("modifier_scale", 0.5f) * 100)); - if (analog_stick_slider->value() == 0) { - analog_stick_slider_label->setText(tr("Modifier Scale: 0%")); - } + const bool is_controller = + param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; + + if (is_controller) { + if (!param.Has("deadzone")) { + param.Set("deadzone", 0.1f); + } + slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100); + deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); + deadzone_slider->setValue(slider_value); + if (!param.Has("range")) { + param.Set("range", 1.0f); + } + range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100)); + } else { + if (!param.Has("modifier_scale")) { + param.Set("modifier_scale", 0.5f); } + slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100); + modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value)); + modifier_slider->setValue(slider_value); } + + deadzone_label->setVisible(is_controller); + deadzone_slider->setVisible(is_controller); + modifier_groupbox->setVisible(!is_controller); + modifier_label->setVisible(!is_controller); + modifier_slider->setVisible(!is_controller); + range_groupbox->setVisible(is_controller); + } +} + +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 (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::function<void(const Common::ParamPackage&)> new_input_setter, InputCommon::Polling::DeviceType type) { - button->setText(tr("[press key]")); + if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { + button->setText(tr("Shake!")); + } else { + button->setText(tr("[waiting]")); + } button->setFocus(); - // Keyboard keys can only be used as button devices - want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; - if (want_keyboard_keys) { - const auto iter = std::find(button_map.begin(), button_map.end(), button); - ASSERT(iter != button_map.end()); - const auto index = std::distance(button_map.begin(), iter); - ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); - } + // 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 = InputCommon::Polling::GetPollers(type); + device_pollers = input_subsystem->GetPollers(type); for (auto& poller : device_pollers) { poller->Start(); } - grabKeyboard(); - grabMouse(); + QWidget::grabMouse(); + QWidget::grabKeyboard(); + if (type == InputCommon::Polling::DeviceType::Button) { - InputCommon::GetGCButtons()->BeginConfiguration(); + input_subsystem->GetGCButtons()->BeginConfiguration(); } else { - InputCommon::GetGCAnalogs()->BeginConfiguration(); + input_subsystem->GetGCAnalogs()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetUDPMotions()->BeginConfiguration(); } - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + + 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) { - releaseKeyboard(); - releaseMouse(); timeout_timer->stop(); poll_timer->stop(); for (auto& poller : device_pollers) { poller->Stop(); } - InputCommon::GetGCButtons()->EndConfiguration(); - InputCommon::GetGCAnalogs()->EndConfiguration(); + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + input_subsystem->GetGCButtons()->EndConfiguration(); + input_subsystem->GetGCAnalogs()->EndConfiguration(); + + input_subsystem->GetUDPMotions()->EndConfiguration(); if (!abort) { (*input_setter)(params); } - UpdateButtonLabels(); + UpdateUI(); input_setter = std::nullopt; } +void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + 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; + } + + SetPollingResult({}, true); +} + void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { if (!input_setter || !event) { return; } if (event->key() != Qt::Key_Escape) { - if (want_keyboard_keys) { + if (want_keyboard_mouse) { SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, false); } else { @@ -674,5 +860,139 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { return; } } + SetPollingResult({}, true); } + +void ConfigureInputPlayer::UpdateControllerIcon() { + // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its + // "nonstandard" to use an image through the icon support) + const QString stylesheet = [this] { + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + return QStringLiteral("image: url(:/controller/pro_controller%0)"); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/dual_joycon%0)"); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)"); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)"); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/handheld%0)"); + default: + return QString{}; + } + }(); + + const QString theme = [this] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); +} + +void ConfigureInputPlayer::UpdateControllerAvailableButtons() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + // 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 = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget, + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonShoulderButtonsLeft, + ui->buttonMiscButtonsMinusScreenshot, + ui->bottomLeft, + ui->buttonShoulderButtonsRight, + ui->buttonMiscButtonsPlusHome, + ui->bottomRight, + }; + + for (auto* widget : layout_show) { + widget->show(); + } + + std::vector<QWidget*> layout_hidden; + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + layout_hidden = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget2, + }; + break; + case Settings::ControllerType::LeftJoycon: + layout_hidden = { + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonShoulderButtonsRight, + ui->buttonMiscButtonsPlusHome, + ui->bottomRight, + }; + break; + case Settings::ControllerType::RightJoycon: + layout_hidden = { + ui->horizontalSpacerShoulderButtonsWidget, + ui->buttonShoulderButtonsLeft, + ui->buttonMiscButtonsMinusScreenshot, + ui->bottomLeft, + }; + break; + } + + for (auto* widget : layout_hidden) { + widget->hide(); + } +} + +void ConfigureInputPlayer::UpdateMotionButtons() { + if (debug) { + // Motion isn't used with the debug controller, hide both groupboxes. + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); + return; + } + + // 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::DualJoyconDetached: + default: + // Show both "Motion 1/2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->show(); + break; + } +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { + if (bottom_row == nullptr) { + return; + } + QWidget::showEvent(event); + ui->main->addWidget(bottom_row); +} + +void ConfigureInputPlayer::ConnectPlayer(bool connected) { + ui->groupConnectedController->setChecked(connected); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 95afa5375..ce443dec5 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -10,16 +10,25 @@ #include <optional> #include <string> -#include <QDialog> +#include <QWidget> #include "common/param_package.h" #include "core/settings.h" #include "ui_configure_input.h" +class QCheckBox; class QKeyEvent; +class QLabel; class QPushButton; +class QSlider; +class QSpinBox; class QString; class QTimer; +class QWidget; + +namespace InputCommon { +class InputSubsystem; +} namespace InputCommon::Polling { class DevicePoller; @@ -30,77 +39,122 @@ namespace Ui { class ConfigureInputPlayer; } -class ConfigureInputPlayer : public QDialog { +class ConfigureInputPlayer : public QWidget { Q_OBJECT public: - explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false); + explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, + InputCommon::InputSubsystem* input_subsystem_, + bool debug = false); ~ConfigureInputPlayer() override; - /// Save all button configurations to settings file + /// Save all button configurations to settings file. void ApplyConfiguration(); + /// Update the input devices combobox. + void UpdateInputDevices(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + /// 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); + /// Emitted when the Handheld mode is selected (undocked with dual joycons attached). + void HandheldStateChanged(bool is_handheld); + /// Emitted when the input devices combobox is being refreshed. + void RefreshInputDevices(); + +protected: + void showEvent(QShowEvent* event) override; + private: void changeEvent(QEvent* event) override; void RetranslateUI(); - void OnControllerButtonClick(int i); - /// Load configuration settings. void LoadConfiguration(); - /// Restore all buttons to their default values. - void RestoreDefaults(); - /// Clear all input configuration - void ClearAll(); - - /// Update UI to reflect current configuration. - void UpdateButtonLabels(); /// Called when the button was pressed. void HandleClick(QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, InputCommon::Polling::DeviceType type); - /// Finish polling and configure input using the input_setter + /// Finish polling and configure input using the input_setter. void SetPollingResult(const Common::ParamPackage& params, bool abort); + /// Handle mouse button press events. + void mousePressEvent(QMouseEvent* event) override; + /// Handle key press events. void keyPressEvent(QKeyEvent* event) override; + /// Update UI to reflect current configuration. + void UpdateUI(); + + /// Update the controller selection combobox + void UpdateControllerCombobox(); + + /// Update the current controller icon. + void UpdateControllerIcon(); + + /// Hides and disables controller settings based on the current controller type. + void UpdateControllerAvailableButtons(); + + /// Shows or hides motion groupboxes based on the current controller type. + void UpdateMotionButtons(); + + /// Gets the default controller mapping for this device and auto configures the input to match. + void UpdateMappingWithDefaults(); + std::unique_ptr<Ui::ConfigureInputPlayer> ui; std::size_t player_index; bool debug; + InputCommon::InputSubsystem* input_subsystem; + std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> poll_timer; + static constexpr int PLAYER_COUNT = 8; + std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; + /// This will be the the setting function when an input is awaiting configuration. std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> stick_mod_param; + std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param; - static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; /// Each button input is represented by a QPushButton. std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + /// Each motion input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; + /// Extra buttons for the modifiers. + std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> mod_buttons; - std::vector<QWidget*> debug_hidden; - std::vector<QWidget*> layout_hidden; - - /// A group of five QPushButtons represent one analog input. The buttons each represent up, - /// down, left, right, and modifier, respectively. + /// 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; - /// Analog inputs are also represented each with a single button, used to configure with an - /// actual analog stick - std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; - std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> - analog_map_deadzone_and_modifier_slider; - std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> - analog_map_deadzone_and_modifier_slider_label; + 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<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; + std::array<QSpinBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_spinbox; static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; @@ -108,8 +162,14 @@ private: /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. - bool want_keyboard_keys = false; + bool want_keyboard_mouse = false; + + /// 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. + std::vector<Common::ParamPackage> input_devices; - std::array<QPushButton*, 4> controller_color_buttons; - std::array<QColor, 4> controller_colors; + /// Bottom row is where console wide settings are held, and its "owned" by the parent + /// ConfigureInput widget. On show, add this widget to the main layout. This will change the + /// parent of the widget to this widget (but thats fine). + QWidget* bottom_row; }; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index f27a77180..e03461d9d 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -1,1243 +1,3075 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ConfigureInputPlayer</class> - <widget class="QDialog" name="ConfigureInputPlayer"> + <widget class="QWidget" name="ConfigureInputPlayer"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>408</width> - <height>731</height> + <width>780</width> + <height>487</height> </rect> </property> <property name="windowTitle"> <string>Configure Input</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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="buttons"> - <item row="1" column="1"> - <widget class="QGroupBox" name="RStick"> - <property name="title"> - <string>Right Stick</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - <property name="flat"> - <bool>false</bool> + <layout class="QVBoxLayout" name="main"> + <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="QHBoxLayout" name="top" stretch="0,1,2"> + <property name="spacing"> + <number>3</number> </property> - <property name="checkable"> - <bool>false</bool> + <property name="topMargin"> + <number>0</number> </property> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> + <item> + <widget class="QGroupBox" name="groupConnectedController"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="title"> + <string>Connect Controller</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> <item> - <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout"> + <widget class="QComboBox" name="comboControllerType"> <item> - <widget class="QLabel" name="labelRStickDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> + <property name="text"> + <string>Pro Controller</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout"> <item> - <widget class="QLabel" name="labelRStickRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> + <property name="text"> + <string>Dual Joycons</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QPushButton" name="buttonRStickAnalog"> - <property name="text"> - <string>Set Analog Stick</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout"> <item> - <widget class="QLabel" name="labelRStickLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> + <property name="text"> + <string>Left Joycon</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout"> <item> - <widget class="QLabel" name="labelRStickUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> + <property name="text"> + <string>Right Joycon</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="0"> - <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout"> <item> - <widget class="QLabel" name="labelRStickPressed"> - <property name="text"> - <string>Pressed:</string> - </property> - </widget> + <property name="text"> + <string>Handheld</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStick"> - <property name="text"> - <string/> - </property> </widget> </item> </layout> - </item> - <item row="2" column="1"> - <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> + </widget> + </item> + <item> + <widget class="QGroupBox" name="devicesGroup"> + <property name="title"> + <string>Input Device</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> <item> - <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout"> + <widget class="QComboBox" name="comboDevices"> <item> - <widget class="QLabel" name="labelRStickMod"> - <property name="text"> - <string>Modifier:</string> - </property> - </widget> + <property name="text"> + <string>Any</string> + </property> </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonRStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="4" column="0" colspan="2"> - <layout class="QVBoxLayout" name="sliderRStickDeadzoneAndModifierVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="sliderRStickDeadzoneAndModifierHorizontalLayout"> <item> - <widget class="QLabel" name="labelRStickDeadzoneAndModifier"> - <property name="text"> - <string>Deadzone: 0</string> - </property> - <property name="alignment"> - <enum>Qt::AlignHCenter</enum> - </property> - </widget> + <property name="text"> + <string>Keyboard/Mouse</string> + </property> </item> - </layout> + </widget> </item> <item> - <widget class="QSlider" name="sliderRStickDeadzoneAndModifier"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <widget class="QPushButton" name="buttonRefreshDevices"> + <property name="minimumSize"> + <size> + <width>24</width> + <height>22</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>24</width> + <height>22</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> </property> </widget> </item> </layout> - </item> - <item row="5" column="0"> - <spacer name="RStick_verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> + </widget> + </item> + <item> + <widget class="QGroupBox" name="profilesGroup"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Profile</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,0,0,0"> + <property name="spacing"> + <number>3</number> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>0</width> - <height>0</height> - </size> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> </property> - </spacer> - </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <widget class="QGroupBox" name="Dpad"> - <property name="title"> - <string>Directional Pad</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> <item> - <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout"> - <item> - <widget class="QLabel" name="labelDpadUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - </layout> + <widget class="QComboBox" name="comboProfiles"/> </item> <item> - <widget class="QPushButton" name="buttonDpadUp"> - <property name="text"> - <string/> + <widget class="QPushButton" name="buttonProfilesSave"> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout"> - <item> - <widget class="QLabel" name="labelDpadDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonDpadDown"> <property name="text"> - <string/> + <string>Save</string> </property> </widget> </item> - </layout> - </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout"> - <item> - <widget class="QLabel" name="labelDpadLeft"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - </layout> - </item> <item> - <widget class="QPushButton" name="buttonDpadLeft"> + <widget class="QPushButton" name="buttonProfilesNew"> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> <property name="text"> - <string/> + <string>New</string> </property> </widget> </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout"> - <item> - <widget class="QLabel" name="labelDpadRight"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - </layout> - </item> <item> - <widget class="QPushButton" name="buttonDpadRight"> + <widget class="QPushButton" name="buttonProfilesDelete"> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> <property name="text"> - <string/> + <string>Delete</string> </property> </widget> </item> </layout> - </item> - </layout> - </widget> + </widget> + </item> + </layout> </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="faceButtons"> - <property name="title"> - <string>Face Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> + <item> + <widget class="QFrame" name="bottom"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout"> - <item> - <widget class="QLabel" name="labelA"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> + <layout class="QHBoxLayout" name="_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </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="bottomLeft" native="true"> + <layout class="QVBoxLayout" name="bottomLeftLayout" stretch="0,0,0,0"> + <property name="spacing"> + <number>0</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </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="QGroupBox" name="LStick"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Left Stick</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> </property> - <property name="text"> - <string>A:</string> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonA"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout"> - <item> - <widget class="QLabel" name="labelB"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> + <property name="leftMargin"> + <number>3</number> </property> - <property name="text"> - <string>B:</string> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonB"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout"> - <item> - <widget class="QLabel" name="labelX"> - <property name="text"> - <string>X:</string> + <property name="rightMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonX"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout"> - <item> - <widget class="QLabel" name="labelY"> - <property name="text"> - <string>Y:</string> + <property name="bottomMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonY"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item row="5" column="0" colspan="2"> - <widget class="QGroupBox" name="controller_color"> - <property name="title"> - <string>Controller Color</string> - </property> - <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0"> - <item row="0" column="0"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="left_body_label"> - <property name="text"> - <string>Left Body</string> - </property> - </widget> - </item> - <item row="0" column="6"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="left_buttons_label"> - <property name="minimumSize"> - <size> - <width>90</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>Left Buttons</string> - </property> - </widget> - </item> - <item row="1" column="5"> - <widget class="QPushButton" name="right_buttons_button"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>32</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="0" column="4"> - <widget class="QLabel" name="right_body_label"> - <property name="text"> - <string>Right Body</string> - </property> - </widget> - </item> - <item row="1" column="4"> - <widget class="QLabel" name="right_buttons_label"> - <property name="minimumSize"> - <size> - <width>90</width> - <height>0</height> - </size> - </property> - <property name="text"> - <string>Right Buttons</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QPushButton" name="left_buttons_button"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>32</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QPushButton" name="left_body_button"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>32</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="0" column="5"> - <widget class="QPushButton" name="right_body_button"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>32</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="0" column="3"> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - </item> - <item row="1" column="0"> - <widget class="QGroupBox" name="LStick"> - <property name="title"> - <string>Left Stick</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickUp"> - <property name="text"> - <string>Up:</string> + <item> + <widget class="QWidget" name="buttonLStickUpWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_20"> + <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="horizontalSpacerLStickUpLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickUpGroup"> + <property name="title"> + <string>Up</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> + <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="buttonLStickUp"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Up</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerLStickUpRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonLStickLeftRightHorizontaLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickLeftGroup"> + <property name="title"> + <string>Left</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> + <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="buttonLStickLeft"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Left</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickRightGroup"> + <property name="title"> + <string>Right</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> + <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="buttonLStickRight"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Right</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="buttonLStickDownWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_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="horizontalSpacerLStickDownLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickDownGroup"> + <property name="title"> + <string>Down</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> + <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="buttonLStickDown"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Down</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerLStickDownRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonLStickPressedModifierHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickPressedGroup"> + <property name="title"> + <string>Pressed</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0"> + <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="buttonLStick"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Pressed</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonLStickModGroup"> + <property name="title"> + <string>Modifier</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> + <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="buttonLStickMod"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Modifier</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="buttonLStickRangeGroup"> + <property name="title"> + <string>Range</string> + </property> + <layout class="QHBoxLayout" name="buttonLStickRangeGroupHorizontalLayout"> + <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="QSpinBox" name="spinboxLStickRange"> + <property name="minimumSize"> + <size> + <width>55</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>50</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="sliderLStickDeadzoneModifierRangeVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickDeadzone"> + <property name="text"> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderLStickDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="sliderLStickModifierRangeHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickModifierRange"> + <property name="text"> + <string>Modifier Range: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderLStickModifierRange"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerBottomLeft"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="Dpad"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>D-Pad</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="spacing"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickRight"> - <property name="text"> - <string>Right:</string> + <property name="leftMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="4" column="1" colspan="2"> - <widget class="QPushButton" name="buttonLStickAnalog"> - <property name="text"> - <string>Set Analog Stick</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2"> - <item> - <widget class="QLabel" name="labelLStickLeft"> - <property name="text"> - <string>Left:</string> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="2"> - <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickDown"> - <property name="text"> - <string>Down:</string> + <property name="rightMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="2"> - <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickMod"> - <property name="text"> - <string>Modifier:</string> + <property name="bottomMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item> + <widget class="QWidget" name="buttonDpadUpWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_23"> + <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="horizontalSpacerDpadUpLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonDpadUpGroup"> + <property name="title"> + <string>Up</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> + <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="buttonDpadUp"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Up</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerDpadUpRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonDpadLeftRightHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonDpadLeftGroup"> + <property name="title"> + <string>Left</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> + <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="buttonDpadLeft"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Left</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonDpadRightGroup"> + <property name="title"> + <string>Right</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> + <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="buttonDpadRight"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Right</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="buttonDpadDownWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_24"> + <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="horizontalSpacerDpadDownLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonDpadDownGroup"> + <property name="title"> + <string>Down</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> + <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="buttonDpadDown"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Down</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerDpadDownRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerBottomLeft_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </item> - <item row="3" column="1"> - <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> - <item> - <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickPressed"> - <property name="text"> - <string>Pressed:</string> + <item> + <widget class="QWidget" name="bottomMiddle" native="true"> + <layout class="QVBoxLayout" stretch="0,0,0"> + <property name="spacing"> + <number>6</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="QHBoxLayout" name="shoulderButtons"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QWidget" name="buttonShoulderButtonsLeft" native="true"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLeftVerticalLayout"> + <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="QGroupBox" name="buttonShoulderButtonsButtonLGroup"> + <property name="title"> + <string>L</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> + <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="buttonL"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>L</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="buttonShoulderButtonsButtonZLGroup"> + <property name="title"> + <string>ZL</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> + <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="buttonZL"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>ZL</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidgetLayout"> + <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="horizontalSpacerShoulderButtons1"> + <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> + <widget class="QWidget" name="buttonMiscButtonsMinusScreenshot" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsMinusScreenshotVerticalLayout"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonMiscButtonsMinusGroup"> + <property name="title"> + <string>Minus</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> + <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="buttonMinus"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Minus</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonMiscButtonsScreenshotGroup"> + <property name="title"> + <string>Capture</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> + <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="buttonScreenshot"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Capture</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="buttonMiscButtonsPlusHome" native="true"> + <layout class="QVBoxLayout" name="buttonMiscButtonsPlusHomeVerticalLayout"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonMiscButtonsPlusGroup"> + <property name="title"> + <string>Plus</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> + <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="buttonPlus"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Plus</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonMiscButtonsHomeGroup"> + <property name="title"> + <string>Home</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> + <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="buttonHome"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Home</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget3" native="true"> + <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget3Layout"> + <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="horizontalSpacerShoulderButtons2"> + <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> + <widget class="QWidget" name="buttonShoulderButtonsRight" native="true"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRightVerticalLayout"> + <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="QGroupBox" name="buttonShoulderButtonsRGroup"> + <property name="title"> + <string>R</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> + <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="buttonR"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>R</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="buttonShoulderButtonsZRGroup"> + <property name="title"> + <string>ZR</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> + <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="buttonZR"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>ZR</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget2" native="true"> + <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget2Layout"> + <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="horizontalSpacerShoulderButtons3"> + <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> + <widget class="QWidget" name="buttonShoulderButtonsSLSR" native="true"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRVerticalLayout"> + <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 alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonShoulderButtonsSLGroup"> + <property name="title"> + <string>SL</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> + <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="buttonSL"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>SL</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonShoulderButtonsSRGroup"> + <property name="title"> + <string>SR</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> + <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="buttonSR"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>SR</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </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> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonLStick"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="5" column="1" colspan="2"> - <layout class="QVBoxLayout" name="sliderLStickDeadzoneAndModifierVerticalLayout"> - <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> - </property> - <item> - <layout class="QHBoxLayout" name="sliderLStickDeadzoneAndModifierHorizontalLayout"> - <item> - <widget class="QLabel" name="labelLStickDeadzoneAndModifier"> - <property name="text"> - <string>Deadzone: 0</string> + <property name="topMargin"> + <number>0</number> </property> - <property name="alignment"> - <enum>Qt::AlignHCenter</enum> + <property name="rightMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QSlider" name="sliderLStickDeadzoneAndModifier"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </item> - <item row="6" column="1"> - <spacer name="LStick_verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - </item> - <item row="3" column="0"> - <widget class="QGroupBox" name="shoulderButtons"> - <property name="title"> - <string>Shoulder Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout"> - <item> - <widget class="QLabel" name="labelL"> - <property name="text"> - <string>L:</string> + <property name="bottomMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + </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"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <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>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</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>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</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>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout"> - <item> - <widget class="QLabel" name="labelR"> - <property name="text"> - <string>R:</string> + <item> + <widget class="QWidget" name="bottomRight" native="true"> + <layout class="QVBoxLayout" name="bottomRightLayout"> + <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="QGroupBox" name="faceButtons"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Face Buttons</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout"> - <item> - <widget class="QLabel" name="labelZL"> - <property name="text"> - <string>ZL:</string> + <property name="leftMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonZL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout"> - <item> - <widget class="QLabel" name="labelZR"> - <property name="text"> - <string>ZR:</string> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonZR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout"> - <item> - <widget class="QLabel" name="labelSL"> - <property name="text"> - <string>SL:</string> + <property name="rightMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonSL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="2"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout"> - <item> - <widget class="QLabel" name="labelSR"> - <property name="text"> - <string>SR:</string> + <property name="bottomMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonSR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item row="3" column="1"> - <widget class="QGroupBox" name="misc"> - <property name="title"> - <string>Misc.</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_6"> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout"> - <item> - <widget class="QLabel" name="labelMinus"> - <property name="text"> - <string>Minus:</string> + <item> + <widget class="QWidget" name="buttonFaceButtonsBWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <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="horizontalSpacerBLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonFaceButtonsXGroup"> + <property name="title"> + <string>X</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> + <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="buttonX"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerBRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsYAHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonFaceButtonsYGroup"> + <property name="title"> + <string>Y</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> + <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="buttonY"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonFaceButtonsAGroup"> + <property name="title"> + <string>A</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> + <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="buttonA"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>A</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="buttonFaceButtonsXWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <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="horizontalSpacerXLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonFaceButtonsBWidget_2"> + <property name="title"> + <string>B</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> + <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="buttonB"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>B</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerXRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerBottomRight"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="RStick"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Right Stick</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonMinus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="1"> - <spacer name="verticalSpacer_2"> - <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 row="0" column="0"> - <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout"> - <item> - <widget class="QLabel" name="labelPlus"> - <property name="text"> - <string>Plus:</string> + <property name="leftMargin"> + <number>3</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonPlus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout"> - <item> - <widget class="QLabel" name="labelHome"> - <property name="text"> - <string>Home:</string> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonHome"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> - <item> - <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout"> - <item> - <widget class="QLabel" name="labelScreenshot"> - <property name="text"> - <string>Screen Capture:</string> + <property name="rightMargin"> + <number>3</number> </property> - <property name="wordWrap"> - <bool>false</bool> + <property name="bottomMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="buttonScreenshot"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>80</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item> + <widget class="QWidget" name="buttonRStickUpWidget" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <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="horizontalSpacerRStickUpLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRStickUpGroup"> + <property name="title"> + <string>Up</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> + <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="buttonRStickUp"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Up</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerRStickUpRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonRStickLeftRightHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRStickLeftGroup"> + <property name="title"> + <string>Left</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> + <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="buttonRStickLeft"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Left</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRStickRightGroup"> + <property name="title"> + <string>Right</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> + <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="buttonRStickRight"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Right</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="buttonRStickDownWidget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <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="horizontalSpacerRStickDownLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="buttonRStickDownGroup"> + <property name="title"> + <string>Down</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> + <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="buttonRStickDown"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Down</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacerRStickDownRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="buttonRStickPressedModifierHorizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupRStickPressed"> + <property name="title"> + <string>Pressed</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> + <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="buttonRStick"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Pressed</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRStickModGroup"> + <property name="title"> + <string>Modifier</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> + <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="buttonRStickMod"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Modifier</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="buttonRStickRangeGroup"> + <property name="title"> + <string>Range</string> + </property> + <layout class="QHBoxLayout" name="buttonRStickRangeGroupHorizontalLayout"> + <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="QSpinBox" name="spinboxRStickRange"> + <property name="minimumSize"> + <size> + <width>55</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>50</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="sliderRStickDeadzoneModifierRangeVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickDeadzone"> + <property name="text"> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRStickDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="sliderRStickModifierRangeHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickModifierRange"> + <property name="text"> + <string>Modifier Range: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRStickModifierRange"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerBottomRight_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </item> </layout> </widget> </item> </layout> </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"/> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QPushButton" name="buttonClearAll"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Clear All</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRestoreDefaults"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Restore Defaults</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="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </item> </layout> </widget> - <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ConfigureInputPlayer</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>371</x> - <y>730</y> - </hint> - <hint type="destinationlabel"> - <x>229</x> - <y>375</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>ConfigureInputPlayer</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>371</x> - <y>730</y> - </hint> - <hint type="destinationlabel"> - <x>229</x> - <y>375</y> - </hint> - </hints> - </connection> - </connections> + <resources> + <include location="../../../dist/icons/controller/controller.qrc"/> + </resources> + <connections/> </ui> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp deleted file mode 100644 index 0e0e8f113..000000000 --- a/src/yuzu/configuration/configure_input_simple.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <tuple> - -#include "ui_configure_input_simple.h" -#include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_input_player.h" -#include "yuzu/configuration/configure_input_simple.h" -#include "yuzu/uisettings.h" - -namespace { - -template <typename Dialog, typename... Args> -void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { - caller->ApplyConfiguration(); - Dialog dialog(caller, std::forward<Args>(args)...); - - const auto res = dialog.exec(); - if (res == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} - -// OnProfileSelect functions should (when applicable): -// - Set controller types -// - Set controller enabled -// - Set docked mode -// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) -// -// OnProfileSelect function should NOT however: -// - Reset any button mappings -// - Open any dialogs -// - Block in any way - -constexpr std::size_t PLAYER_0_INDEX = 0; -constexpr std::size_t HANDHELD_INDEX = 8; - -void HandheldOnProfileSelect() { - Settings::values.players[HANDHELD_INDEX].connected = true; - Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; - - for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { - Settings::values.players[player].connected = false; - } - - Settings::values.use_docked_mode = false; - Settings::values.keyboard_enabled = false; - Settings::values.mouse_enabled = false; - Settings::values.debug_pad_enabled = false; - Settings::values.touchscreen.enabled = true; -} - -void DualJoyconsDockedOnProfileSelect() { - Settings::values.players[PLAYER_0_INDEX].connected = true; - Settings::values.players[PLAYER_0_INDEX].type = Settings::ControllerType::DualJoycon; - - for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { - Settings::values.players[player].connected = false; - } - - Settings::values.use_docked_mode = true; - Settings::values.keyboard_enabled = false; - Settings::values.mouse_enabled = false; - Settings::values.debug_pad_enabled = false; - Settings::values.touchscreen.enabled = true; -} - -// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure -// is clicked) -using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>; - -constexpr std::array<InputProfile, 3> INPUT_PROFILES{{ - {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, - [](ConfigureInputSimple* caller) { - CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); - }}, - {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect, - [](ConfigureInputSimple* caller) { - CallConfigureDialog<ConfigureInputPlayer>(caller, PLAYER_0_INDEX, false); - }}, - {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, -}}; - -} // namespace - -void ApplyInputProfileConfiguration(int profile_index) { - std::get<1>( - INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); -} - -ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { - ui->setupUi(this); - - for (const auto& profile : INPUT_PROFILES) { - const QString label = tr(std::get<0>(profile)); - ui->profile_combobox->addItem(label, label); - } - - connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - &ConfigureInputSimple::OnSelectProfile); - connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure); - - LoadConfiguration(); -} - -ConfigureInputSimple::~ConfigureInputSimple() = default; - -void ConfigureInputSimple::ApplyConfiguration() { - auto index = ui->profile_combobox->currentIndex(); - // Make the stored index for "Custom" very large so that if new profiles are added it - // doesn't change. - if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) { - index = std::numeric_limits<int>::max(); - } - - UISettings::values.profile_index = index; -} - -void ConfigureInputSimple::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) { - RetranslateUI(); - } - - QWidget::changeEvent(event); -} - -void ConfigureInputSimple::RetranslateUI() { - ui->retranslateUi(this); -} - -void ConfigureInputSimple::LoadConfiguration() { - const auto index = UISettings::values.profile_index; - if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) { - ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); - } else { - ui->profile_combobox->setCurrentIndex(index); - } -} - -void ConfigureInputSimple::OnSelectProfile(int index) { - const auto old_docked = Settings::values.use_docked_mode; - ApplyInputProfileConfiguration(index); - OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); -} - -void ConfigureInputSimple::OnConfigure() { - std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); -} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h deleted file mode 100644 index bb5050224..000000000 --- a/src/yuzu/configuration/configure_input_simple.h +++ /dev/null @@ -1,43 +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 <QWidget> - -class QPushButton; -class QString; -class QTimer; - -namespace Ui { -class ConfigureInputSimple; -} - -// Used by configuration loader to apply a profile if the input is invalid. -void ApplyInputProfileConfiguration(int profile_index); - -class ConfigureInputSimple : public QWidget { - Q_OBJECT - -public: - explicit ConfigureInputSimple(QWidget* parent = nullptr); - ~ConfigureInputSimple() override; - - /// Save all button configurations to settings file - void ApplyConfiguration(); - -private: - void changeEvent(QEvent* event) override; - void RetranslateUI(); - - /// Load configuration settings. - void LoadConfiguration(); - - void OnSelectProfile(int index); - void OnConfigure(); - - std::unique_ptr<Ui::ConfigureInputSimple> ui; -}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui deleted file mode 100644 index c4889caa9..000000000 --- a/src/yuzu/configuration/configure_input_simple.ui +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConfigureInputSimple</class> - <widget class="QWidget" name="ConfigureInputSimple"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>473</width> - <height>685</height> - </rect> - </property> - <property name="windowTitle"> - <string>ConfigureInputSimple</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QGroupBox" name="gridGroupBox"> - <property name="title"> - <string>Profile</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="2"> - <widget class="QPushButton" name="profile_configure"> - <property name="text"> - <string>Configure</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="3"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="profile_combobox"> - <property name="minimumSize"> - <size> - <width>250</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Choose a controller configuration:</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <resources/> - <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..c7d085151 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -0,0 +1,314 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <QCloseEvent> +#include <QLabel> +#include <QMessageBox> +#include <QPushButton> +#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; + } + 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); +} + +constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{ + {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")}, + {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, +}}; + +constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{ + {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")}, + {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, +}}; + +ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique<Ui::ConfigureMotionTouch>()) { + ui->setupUi(this); + for (const auto& [provider, name] : MotionProviders) { + ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider)); + } + for (const auto& [provider, name] : TouchProviders) { + ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider)); + } + + 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); + const std::string motion_engine = motion_param.Get("engine", "motion_emu"); + const std::string touch_engine = touch_param.Get("engine", "emu_window"); + + ui->motion_provider->setCurrentIndex( + ui->motion_provider->findData(QString::fromStdString(motion_engine))); + ui->touch_provider->setCurrentIndex( + ui->touch_provider->findData(QString::fromStdString(touch_engine))); + 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(Settings::values.udp_input_address)); + ui->udp_port->setText(QString::number(Settings::values.udp_input_port)); + ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index); +} + +void ConfigureMotionTouch::UpdateUiDisplay() { + const QString motion_engine = ui->motion_provider->currentData().toString(); + const QString touch_engine = ui->touch_provider->currentData().toString(); + const QString cemuhook_udp = QStringLiteral("cemuhookudp"); + + if (motion_engine == QStringLiteral("motion_emu")) { + ui->motion_sensitivity_label->setVisible(true); + ui->motion_sensitivity->setVisible(true); + } else { + ui->motion_sensitivity_label->setVisible(false); + ui->motion_sensitivity->setVisible(false); + } + + if (touch_engine == cemuhook_udp) { + 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)); + } else { + ui->touch_calibration->setVisible(false); + ui->touch_calibration_config->setVisible(false); + ui->touch_calibration_label->setVisible(false); + } + + if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) { + ui->udp_config_group_box->setVisible(true); + } else { + ui->udp_config_group_box->setVisible(false); + } +} + +void ConfigureMotionTouch::ConnectEvents() { + connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, + [this](int index) { UpdateUiDisplay(); }); + connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, + [this](int index) { UpdateUiDisplay(); }); + connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); + 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::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()), + static_cast<u8>(ui->udp_pad_index->currentIndex()), 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()), + static_cast<u8>(ui->udp_pad_index->currentIndex()), 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("Citra"), + 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; + } + + std::string motion_engine = ui->motion_provider->currentData().toString().toStdString(); + std::string touch_engine = ui->touch_provider->currentData().toString().toStdString(); + + Common::ParamPackage motion_param{}, touch_param{}; + motion_param.Set("engine", std::move(motion_engine)); + touch_param.Set("engine", std::move(touch_engine)); + + if (motion_engine == "motion_emu") { + motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value())); + } + + if (touch_engine == "cemuhookudp") { + 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.motion_device = motion_param.Serialize(); + 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_address = ui->udp_server->text().toStdString(); + Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt()); + Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex()); + input_subsystem->ReloadInputDevices(); + + accept(); +} diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h new file mode 100644 index 000000000..3d4b5d659 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.h @@ -0,0 +1,90 @@ +// 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 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 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(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr<Ui::ConfigureMotionTouch> ui; + + // 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..602cf8cd8 --- /dev/null +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -0,0 +1,327 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMotionTouch</class> + <widget class="QDialog" name="ConfigureMotionTouch"> + <property name="windowTitle"> + <string>Configure Motion / Touch</string> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>450</height> + </rect> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="motion_group_box"> + <property name="title"> + <string>Motion</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="motion_provider_label"> + <property name="text"> + <string>Motion Provider:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="motion_provider"/> + </item> + </layout> + </item> + <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_provider_label"> + <property name="text"> + <string>Touch Provider:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="touch_provider"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="touch_calibration_label"> + <property name="text"> + <string>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"> + <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"> + <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"> + <item> + <widget class="QLabel" name="udp_pad_index_label"> + <property name="text"> + <string>Pad:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="udp_pad_index"> + <item> + <property name="text"> + <string>Pad 1</string> + </property> + </item> + <item> + <property name="text"> + <string>Pad 2</string> + </property> + </item> + <item> + <property name="text"> + <string>Pad 3</string> + </property> + </item> + <item> + <property name="text"> + <string>Pad 4</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <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> + </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>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_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp index e0647ea5b..2af3afda8 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.cpp +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -18,6 +18,16 @@ static QString GetKeyName(int key_code) { switch (key_code) { + case Qt::LeftButton: + return QObject::tr("Click 0"); + case Qt::RightButton: + return QObject::tr("Click 1"); + case Qt::MiddleButton: + return QObject::tr("Click 2"); + case Qt::BackButton: + return QObject::tr("Click 3"); + case Qt::ForwardButton: + return QObject::tr("Click 4"); case Qt::Key_Shift: return QObject::tr("Shift"); case Qt::Key_Control: @@ -66,8 +76,10 @@ static QString ButtonToText(const Common::ParamPackage& param) { return QObject::tr("[unknown]"); } -ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) - : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), +ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), + ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -83,25 +95,29 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) } button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button, &QPushButton::clicked, [=] { + connect(button, &QPushButton::clicked, [=, this] { HandleClick( button_map[button_id], - [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + [=, this](const Common::ParamPackage& params) { + buttons_param[button_id] = params; + }, InputCommon::Polling::DeviceType::Button); }); - connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - }); + connect(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.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = + Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_mouse_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); } connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); @@ -184,9 +200,9 @@ void ConfigureMouseAdvanced::HandleClick( button->setText(tr("[press key]")); button->setFocus(); - // Keyboard keys can only be used as button devices - want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; - if (want_keyboard_keys) { + // Keyboard keys or mouse buttons can only be used as button devices + want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button; + if (want_keyboard_mouse) { const auto iter = std::find(button_map.begin(), button_map.end(), button); ASSERT(iter != button_map.end()); const auto index = std::distance(button_map.begin(), iter); @@ -195,27 +211,29 @@ void ConfigureMouseAdvanced::HandleClick( input_setter = new_input_setter; - device_pollers = InputCommon::Polling::GetPollers(type); + device_pollers = input_subsystem->GetPollers(type); for (auto& poller : device_pollers) { poller->Start(); } - grabKeyboard(); - grabMouse(); - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(50); // Check for new inputs every 50ms } void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) { - releaseKeyboard(); - releaseMouse(); timeout_timer->stop(); poll_timer->stop(); for (auto& poller : device_pollers) { poller->Stop(); } + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + if (!abort) { (*input_setter)(params); } @@ -224,13 +242,29 @@ void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params input_setter = std::nullopt; } +void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + 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; + } + + SetPollingResult({}, true); +} + void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { if (!input_setter || !event) { return; } if (event->key() != Qt::Key_Escape) { - if (want_keyboard_keys) { + if (want_keyboard_mouse) { SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, false); } else { diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h index 342b82412..65b6fca9a 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.h +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -8,12 +8,14 @@ #include <optional> #include <QDialog> -#include "core/settings.h" - class QCheckBox; class QPushButton; class QTimer; +namespace InputCommon { +class InputSubsystem; +} + namespace Ui { class ConfigureMouseAdvanced; } @@ -22,7 +24,7 @@ class ConfigureMouseAdvanced : public QDialog { Q_OBJECT public: - explicit ConfigureMouseAdvanced(QWidget* parent); + explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); ~ConfigureMouseAdvanced() override; void ApplyConfiguration(); @@ -49,11 +51,16 @@ private: /// Finish polling and configure input using the input_setter void SetPollingResult(const Common::ParamPackage& params, bool abort); + /// Handle mouse button press events. + void mousePressEvent(QMouseEvent* event) override; + /// Handle key press events. void keyPressEvent(QKeyEvent* event) override; std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; + InputCommon::InputSubsystem* input_subsystem; + /// This will be the the setting function when an input is awaiting configuration. std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; @@ -67,5 +74,5 @@ private: /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. - bool want_keyboard_keys = false; + bool want_keyboard_mouse = false; }; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui index 08245ecf0..74552fdbd 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -6,13 +6,18 @@ <rect> <x>0</x> <y>0</y> - <width>250</width> - <height>261</height> + <width>310</width> + <height>193</height> </rect> </property> <property name="windowTitle"> <string>Configure Mouse</string> </property> + <property name="styleSheet"> + <string notr="true">QPushButton { + min-width: 55px; +}</string> + </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QGroupBox" name="gridGroupBox"> @@ -20,81 +25,33 @@ <string>Mouse Buttons</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="4"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="3"> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <item row="3" column="5"> + <layout class="QVBoxLayout" name="verticalLayout_6"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="horizontalLayout_5"> <item> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="label_5"> <property name="text"> - <string>Right:</string> + <string>Forward:</string> </property> </widget> </item> </layout> </item> <item> - <widget class="QPushButton" name="right_button"> + <widget class="QPushButton" name="forward_button"> <property name="minimumSize"> <size> - <width>75</width> + <width>57</width> <height>0</height> </size> </property> - <property name="text"> - <string/> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="0"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Middle:</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QPushButton" name="middle_button"> <property name="text"> <string/> </property> @@ -123,6 +80,12 @@ </item> <item> <widget class="QPushButton" name="back_button"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> <property name="text"> <string/> </property> @@ -147,7 +110,7 @@ <widget class="QPushButton" name="left_button"> <property name="minimumSize"> <size> - <width>75</width> + <width>57</width> <height>0</height> </size> </property> @@ -158,21 +121,99 @@ </item> </layout> </item> - <item row="3" column="3"> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <item row="0" column="3"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_5"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QLabel" name="label_5"> + <widget class="QLabel" name="label_2"> <property name="text"> - <string>Forward:</string> + <string>Middle:</string> </property> </widget> </item> </layout> </item> <item> - <widget class="QPushButton" name="forward_button"> + <widget class="QPushButton" name="middle_button"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="6"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="5"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="right_button"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> <property name="text"> <string/> </property> @@ -180,6 +221,32 @@ </item> </layout> </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer_4"> + <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 row="0" column="4"> + <spacer name="horizontalSpacer_5"> + <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> @@ -187,15 +254,39 @@ <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> <widget class="QPushButton" name="buttonClearAll"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> <property name="text"> - <string>Clear All</string> + <string>Clear</string> </property> </widget> </item> <item> <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> <property name="text"> - <string>Restore Defaults</string> + <string>Defaults</string> </property> </widget> </item> @@ -206,21 +297,24 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>0</width> <height>20</height> </size> </property> </spacer> </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> </layout> </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> </layout> </widget> <resources/> diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 478d5d3a1..793fd8975 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { std::sort(disabled_addons.begin(), disabled_addons.end()); std::sort(current.begin(), current.end()); if (disabled_addons != current) { - FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); + Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); } Settings::values.disabled_addons[title_id] = disabled_addons; diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index f53423440..6334c4c50 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -34,7 +34,7 @@ constexpr std::array<u8, 107> backup_jpeg{ }; QString GetImagePath(Common::UUID uuid) { - const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; return QString::fromStdString(path); } @@ -282,7 +282,7 @@ void ConfigureProfileManager::SetUserImage() { } const auto raw_path = QString::fromStdString( - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); + Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010"); const QFileInfo raw_info{raw_path}; if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { QMessageBox::warning(this, tr("Error deleting file"), diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 68e02738b..9ad43ed8f 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -67,21 +67,21 @@ void ConfigureSystem::SetConfiguration() { const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); + ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->rng_seed_edit->setText(rng_seed); + + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + if (Settings::configuring_global) { 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()); ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue()); - - ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); - ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() && - Settings::values.rng_seed.UsingGlobal()); - ui->rng_seed_edit->setText(rng_seed); - - ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && - Settings::values.rng_seed.UsingGlobal()); - ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); } else { ConfigurationShared::SetPerGameSetting(ui->combo_language, &Settings::values.language_index); @@ -90,27 +90,14 @@ void ConfigureSystem::SetConfiguration() { &Settings::values.time_zone_index); ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); - if (Settings::values.rng_seed.UsingGlobal()) { - ui->rng_seed_checkbox->setCheckState(Qt::PartiallyChecked); - } else { - ui->rng_seed_checkbox->setCheckState( - Settings::values.rng_seed.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); - ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value()); - if (Settings::values.rng_seed.GetValue().has_value()) { - ui->rng_seed_edit->setText(rng_seed); - } - } - - if (Settings::values.custom_rtc.UsingGlobal()) { - ui->custom_rtc_checkbox->setCheckState(Qt::PartiallyChecked); - } else { - ui->custom_rtc_checkbox->setCheckState( - Settings::values.custom_rtc.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value()); - if (Settings::values.custom_rtc.GetValue().has_value()) { - ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); - } - } + ConfigurationShared::SetHighlight(ui->label_language, + !Settings::values.language_index.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->label_region, + !Settings::values.region_index.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->label_timezone, + !Settings::values.time_zone_index.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->label_sound, + !Settings::values.sound_index.UsingGlobal()); } } @@ -161,37 +148,44 @@ void ConfigureSystem::ApplyConfiguration() { ui->combo_time_zone); ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); - switch (ui->rng_seed_checkbox->checkState()) { - case Qt::Checked: - Settings::values.rng_seed.SetGlobal(false); - Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toULongLong(nullptr, 16)); - break; - case Qt::Unchecked: + switch (use_rng_seed) { + case ConfigurationShared::CheckState::On: + case ConfigurationShared::CheckState::Off: Settings::values.rng_seed.SetGlobal(false); - Settings::values.rng_seed.SetValue(std::nullopt); + if (ui->rng_seed_checkbox->isChecked()) { + Settings::values.rng_seed.SetValue( + ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + } else { + Settings::values.rng_seed.SetValue(std::nullopt); + } break; - case Qt::PartiallyChecked: + case ConfigurationShared::CheckState::Global: Settings::values.rng_seed.SetGlobal(false); Settings::values.rng_seed.SetValue(std::nullopt); Settings::values.rng_seed.SetGlobal(true); break; + case ConfigurationShared::CheckState::Count: + break; } - switch (ui->custom_rtc_checkbox->checkState()) { - case Qt::Checked: - Settings::values.custom_rtc.SetGlobal(false); - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); - break; - case Qt::Unchecked: + switch (use_custom_rtc) { + case ConfigurationShared::CheckState::On: + case ConfigurationShared::CheckState::Off: Settings::values.custom_rtc.SetGlobal(false); - Settings::values.custom_rtc.SetValue(std::nullopt); + 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); + } break; - case Qt::PartiallyChecked: + case ConfigurationShared::CheckState::Global: Settings::values.custom_rtc.SetGlobal(false); Settings::values.custom_rtc.SetValue(std::nullopt); Settings::values.custom_rtc.SetGlobal(true); break; + case ConfigurationShared::CheckState::Count: + break; } } @@ -229,10 +223,21 @@ void ConfigureSystem::SetupPerGameUI() { return; } - ConfigurationShared::InsertGlobalItem(ui->combo_language); - ConfigurationShared::InsertGlobalItem(ui->combo_region); - ConfigurationShared::InsertGlobalItem(ui->combo_time_zone); - ConfigurationShared::InsertGlobalItem(ui->combo_sound); - ui->rng_seed_checkbox->setTristate(true); - ui->custom_rtc_checkbox->setTristate(true); + ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language, + Settings::values.language_index.GetValue(true)); + ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region, + Settings::values.region_index.GetValue(true)); + ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone, + Settings::values.time_zone_index.GetValue(true)); + ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound, + Settings::values.sound_index.GetValue(true)); + + ConfigurationShared::SetColoredTristate( + ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), + Settings::values.rng_seed.GetValue().has_value(), + Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); + ConfigurationShared::SetColoredTristate( + ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(), + Settings::values.custom_rtc.GetValue().has_value(), + Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc); } diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index f317ef8b5..fc5cd2945 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -9,6 +9,10 @@ #include <QList> #include <QWidget> +namespace ConfigurationShared { +enum class CheckState; +} + namespace Ui { class ConfigureSystem; } @@ -41,4 +45,7 @@ private: int region_index = 0; int time_zone_index = 0; int sound_index = 0; + + ConfigurationShared::CheckState use_rng_seed; + ConfigurationShared::CheckState use_custom_rtc; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 9c8cca6dc..53b95658b 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -21,490 +21,494 @@ <property name="title"> <string>System Settings</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="3" column="0"> - <widget class="QLabel" name="label_sound"> - <property name="text"> - <string>Sound output mode</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_console_id"> - <property name="text"> - <string>Console ID:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="combo_language"> - <property name="toolTip"> - <string>Note: this can be overridden when region setting is auto-select</string> - </property> - <item> - <property name="text"> - <string>Japanese (日本語)</string> - </property> - </item> - <item> - <property name="text"> - <string>English</string> - </property> - </item> - <item> - <property name="text"> - <string>French (français)</string> - </property> - </item> - <item> - <property name="text"> - <string>German (Deutsch)</string> - </property> - </item> - <item> - <property name="text"> - <string>Italian (italiano)</string> - </property> - </item> - <item> - <property name="text"> - <string>Spanish (español)</string> - </property> - </item> - <item> - <property name="text"> - <string>Chinese</string> - </property> - </item> - <item> - <property name="text"> - <string>Korean (한국어)</string> - </property> - </item> - <item> - <property name="text"> - <string>Dutch (Nederlands)</string> - </property> - </item> - <item> - <property name="text"> - <string>Portuguese (português)</string> - </property> - </item> - <item> - <property name="text"> - <string>Russian (Русский)</string> - </property> - </item> - <item> - <property name="text"> - <string>Taiwanese</string> - </property> - </item> - <item> - <property name="text"> - <string>British English</string> - </property> - </item> - <item> - <property name="text"> - <string>Canadian French</string> - </property> - </item> - <item> - <property name="text"> - <string>Latin American Spanish</string> - </property> - </item> - <item> - <property name="text"> - <string>Simplified Chinese</string> - </property> - </item> - <item> - <property name="text"> - <string>Traditional Chinese (正體中文)</string> - </property> - </item> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_region"> - <property name="text"> - <string>Region:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="combo_region"> - <item> - <property name="text"> - <string>Japan</string> - </property> - </item> - <item> - <property name="text"> - <string>USA</string> - </property> - </item> - <item> - <property name="text"> - <string>Europe</string> - </property> - </item> - <item> - <property name="text"> - <string>Australia</string> - </property> - </item> - <item> - <property name="text"> - <string>China</string> - </property> - </item> - <item> - <property name="text"> - <string>Korea</string> - </property> - </item> - <item> - <property name="text"> - <string>Taiwan</string> - </property> - </item> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_timezone"> - <property name="text"> - <string>Time Zone:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="combo_time_zone"> - <item> - <property name="text"> - <string>Auto</string> - </property> - </item> - <item> - <property name="text"> - <string>Default</string> - </property> - </item> - <item> - <property name="text"> - <string>CET</string> - </property> - </item> - <item> - <property name="text"> - <string>CST6CDT</string> - </property> - </item> - <item> - <property name="text"> - <string>Cuba</string> - </property> - </item> - <item> - <property name="text"> - <string>EET</string> - </property> - </item> - <item> - <property name="text"> - <string>Egypt</string> - </property> - </item> - <item> - <property name="text"> - <string>Eire</string> - </property> - </item> - <item> - <property name="text"> - <string>EST</string> - </property> - </item> - <item> - <property name="text"> - <string>EST5EDT</string> - </property> - </item> - <item> - <property name="text"> - <string>GB</string> - </property> - </item> - <item> - <property name="text"> - <string>GB-Eire</string> - </property> - </item> - <item> - <property name="text"> - <string>GMT</string> - </property> - </item> - <item> - <property name="text"> - <string>GMT+0</string> - </property> - </item> - <item> - <property name="text"> - <string>GMT-0</string> - </property> - </item> - <item> - <property name="text"> - <string>GMT0</string> - </property> - </item> - <item> - <property name="text"> - <string>Greenwich</string> - </property> - </item> - <item> - <property name="text"> - <string>Hongkong</string> - </property> - </item> - <item> - <property name="text"> - <string>HST</string> - </property> - </item> - <item> - <property name="text"> - <string>Iceland</string> - </property> - </item> - <item> - <property name="text"> - <string>Iran</string> - </property> - </item> - <item> - <property name="text"> - <string>Israel</string> - </property> - </item> - <item> - <property name="text"> - <string>Jamaica</string> - </property> - </item> - <item> - <property name="text"> - <string>Japan</string> - </property> - </item> - <item> - <property name="text"> - <string>Kwajalein</string> - </property> - </item> - <item> - <property name="text"> - <string>Libya</string> - </property> - </item> - <item> - <property name="text"> - <string>MET</string> - </property> - </item> - <item> - <property name="text"> - <string>MST</string> - </property> - </item> - <item> - <property name="text"> - <string>MST7MDT</string> - </property> - </item> - <item> - <property name="text"> - <string>Navajo</string> - </property> - </item> - <item> - <property name="text"> - <string>NZ</string> - </property> - </item> - <item> - <property name="text"> - <string>NZ-CHAT</string> - </property> - </item> - <item> - <property name="text"> - <string>Poland</string> - </property> - </item> - <item> - <property name="text"> - <string>Portugal</string> - </property> - </item> - <item> - <property name="text"> - <string>PRC</string> - </property> - </item> - <item> - <property name="text"> - <string>PST8PDT</string> - </property> - </item> - <item> - <property name="text"> - <string>ROC</string> - </property> - </item> - <item> - <property name="text"> - <string>ROK</string> - </property> - </item> - <item> - <property name="text"> - <string>Singapore</string> - </property> - </item> - <item> - <property name="text"> - <string>Turkey</string> - </property> - </item> - <item> - <property name="text"> - <string>UCT</string> - </property> - </item> - <item> - <property name="text"> - <string>Universal</string> - </property> - </item> - <item> - <property name="text"> - <string>UTC</string> - </property> - </item> - <item> - <property name="text"> - <string>W-SU</string> - </property> - </item> - <item> - <property name="text"> - <string>WET</string> - </property> - </item> - <item> - <property name="text"> - <string>Zulu</string> - </property> - </item> - </widget> - </item> - <item row="6" column="0"> - <widget class="QCheckBox" name="rng_seed_checkbox"> - <property name="text"> - <string>RNG Seed</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QComboBox" name="combo_sound"> - <item> - <property name="text"> - <string>Mono</string> - </property> - </item> - <item> - <property name="text"> - <string>Stereo</string> - </property> - </item> - <item> - <property name="text"> - <string>Surround</string> - </property> - </item> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_language"> - <property name="text"> - <string>Language</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPushButton" name="button_regenerate_console_id"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="layoutDirection"> - <enum>Qt::RightToLeft</enum> - </property> - <property name="text"> - <string>Regenerate</string> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QCheckBox" name="custom_rtc_checkbox"> - <property name="text"> - <string>Custom RTC</string> - </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QDateTimeEdit" name="custom_rtc_edit"> - <property name="minimumDate"> - <date> - <year>1970</year> - <month>1</month> - <day>1</day> - </date> - </property> - <property name="displayFormat"> - <string>d MMM yyyy h:mm:ss AP</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <widget class="QLineEdit" name="rng_seed_edit"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="font"> - <font> - <family>Lucida Console</family> - </font> - </property> - <property name="inputMask"> - <string notr="true">HHHHHHHH</string> - </property> - <property name="maxLength"> - <number>8</number> - </property> - </widget> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QLabel" name="label_region"> + <property name="text"> + <string>Region:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="combo_time_zone"> + <item> + <property name="text"> + <string>Auto</string> + </property> + </item> + <item> + <property name="text"> + <string>Default</string> + </property> + </item> + <item> + <property name="text"> + <string>CET</string> + </property> + </item> + <item> + <property name="text"> + <string>CST6CDT</string> + </property> + </item> + <item> + <property name="text"> + <string>Cuba</string> + </property> + </item> + <item> + <property name="text"> + <string>EET</string> + </property> + </item> + <item> + <property name="text"> + <string>Egypt</string> + </property> + </item> + <item> + <property name="text"> + <string>Eire</string> + </property> + </item> + <item> + <property name="text"> + <string>EST</string> + </property> + </item> + <item> + <property name="text"> + <string>EST5EDT</string> + </property> + </item> + <item> + <property name="text"> + <string>GB</string> + </property> + </item> + <item> + <property name="text"> + <string>GB-Eire</string> + </property> + </item> + <item> + <property name="text"> + <string>GMT</string> + </property> + </item> + <item> + <property name="text"> + <string>GMT+0</string> + </property> + </item> + <item> + <property name="text"> + <string>GMT-0</string> + </property> + </item> + <item> + <property name="text"> + <string>GMT0</string> + </property> + </item> + <item> + <property name="text"> + <string>Greenwich</string> + </property> + </item> + <item> + <property name="text"> + <string>Hongkong</string> + </property> + </item> + <item> + <property name="text"> + <string>HST</string> + </property> + </item> + <item> + <property name="text"> + <string>Iceland</string> + </property> + </item> + <item> + <property name="text"> + <string>Iran</string> + </property> + </item> + <item> + <property name="text"> + <string>Israel</string> + </property> + </item> + <item> + <property name="text"> + <string>Jamaica</string> + </property> + </item> + <item> + <property name="text"> + <string>Japan</string> + </property> + </item> + <item> + <property name="text"> + <string>Kwajalein</string> + </property> + </item> + <item> + <property name="text"> + <string>Libya</string> + </property> + </item> + <item> + <property name="text"> + <string>MET</string> + </property> + </item> + <item> + <property name="text"> + <string>MST</string> + </property> + </item> + <item> + <property name="text"> + <string>MST7MDT</string> + </property> + </item> + <item> + <property name="text"> + <string>Navajo</string> + </property> + </item> + <item> + <property name="text"> + <string>NZ</string> + </property> + </item> + <item> + <property name="text"> + <string>NZ-CHAT</string> + </property> + </item> + <item> + <property name="text"> + <string>Poland</string> + </property> + </item> + <item> + <property name="text"> + <string>Portugal</string> + </property> + </item> + <item> + <property name="text"> + <string>PRC</string> + </property> + </item> + <item> + <property name="text"> + <string>PST8PDT</string> + </property> + </item> + <item> + <property name="text"> + <string>ROC</string> + </property> + </item> + <item> + <property name="text"> + <string>ROK</string> + </property> + </item> + <item> + <property name="text"> + <string>Singapore</string> + </property> + </item> + <item> + <property name="text"> + <string>Turkey</string> + </property> + </item> + <item> + <property name="text"> + <string>UCT</string> + </property> + </item> + <item> + <property name="text"> + <string>Universal</string> + </property> + </item> + <item> + <property name="text"> + <string>UTC</string> + </property> + </item> + <item> + <property name="text"> + <string>W-SU</string> + </property> + </item> + <item> + <property name="text"> + <string>WET</string> + </property> + </item> + <item> + <property name="text"> + <string>Zulu</string> + </property> + </item> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="combo_region"> + <item> + <property name="text"> + <string>Japan</string> + </property> + </item> + <item> + <property name="text"> + <string>USA</string> + </property> + </item> + <item> + <property name="text"> + <string>Europe</string> + </property> + </item> + <item> + <property name="text"> + <string>Australia</string> + </property> + </item> + <item> + <property name="text"> + <string>China</string> + </property> + </item> + <item> + <property name="text"> + <string>Korea</string> + </property> + </item> + <item> + <property name="text"> + <string>Taiwan</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_timezone"> + <property name="text"> + <string>Time Zone:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="combo_language"> + <property name="toolTip"> + <string>Note: this can be overridden when region setting is auto-select</string> + </property> + <item> + <property name="text"> + <string>Japanese (日本語)</string> + </property> + </item> + <item> + <property name="text"> + <string>English</string> + </property> + </item> + <item> + <property name="text"> + <string>French (français)</string> + </property> + </item> + <item> + <property name="text"> + <string>German (Deutsch)</string> + </property> + </item> + <item> + <property name="text"> + <string>Italian (italiano)</string> + </property> + </item> + <item> + <property name="text"> + <string>Spanish (español)</string> + </property> + </item> + <item> + <property name="text"> + <string>Chinese</string> + </property> + </item> + <item> + <property name="text"> + <string>Korean (한국어)</string> + </property> + </item> + <item> + <property name="text"> + <string>Dutch (Nederlands)</string> + </property> + </item> + <item> + <property name="text"> + <string>Portuguese (português)</string> + </property> + </item> + <item> + <property name="text"> + <string>Russian (Русский)</string> + </property> + </item> + <item> + <property name="text"> + <string>Taiwanese</string> + </property> + </item> + <item> + <property name="text"> + <string>British English</string> + </property> + </item> + <item> + <property name="text"> + <string>Canadian French</string> + </property> + </item> + <item> + <property name="text"> + <string>Latin American Spanish</string> + </property> + </item> + <item> + <property name="text"> + <string>Simplified Chinese</string> + </property> + </item> + <item> + <property name="text"> + <string>Traditional Chinese (正體中文)</string> + </property> + </item> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="custom_rtc_checkbox"> + <property name="text"> + <string>Custom RTC</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_language"> + <property name="text"> + <string>Language</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="rng_seed_checkbox"> + <property name="text"> + <string>RNG Seed</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="combo_sound"> + <item> + <property name="text"> + <string>Mono</string> + </property> + </item> + <item> + <property name="text"> + <string>Stereo</string> + </property> + </item> + <item> + <property name="text"> + <string>Surround</string> + </property> + </item> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_console_id"> + <property name="text"> + <string>Console ID:</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_sound"> + <property name="text"> + <string>Sound output mode</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QDateTimeEdit" name="custom_rtc_edit"> + <property name="minimumDate"> + <date> + <year>1970</year> + <month>1</month> + <day>1</day> + </date> + </property> + <property name="displayFormat"> + <string>d MMM yyyy h:mm:ss AP</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLineEdit" name="rng_seed_edit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Lucida Console</family> + </font> + </property> + <property name="inputMask"> + <string notr="true">HHHHHHHH</string> + </property> + <property name="maxLength"> + <number>8</number> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="button_regenerate_console_id"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="layoutDirection"> + <enum>Qt::RightToLeft</enum> + </property> + <property name="text"> + <string>Regenerate</string> + </property> + </widget> + </item> + </layout> </item> </layout> </widget> 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..f581e27e0 --- /dev/null +++ b/src/yuzu/configuration/configure_touch_from_button.ui @@ -0,0 +1,231 @@ +<?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> + <hints> + <hint type="sourcelabel"> + <x>249</x> + <y>428</y> + </hint> + <hint type="destinationlabel"> + <x>249</x> + <y>224</y> + </hint> + </hints> + </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_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 94424ee44..dbe3f78c8 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -4,8 +4,11 @@ #include <array> #include <utility> +#include <QFileDialog> +#include <QDirIterator> #include "common/common_types.h" +#include "common/file_util.h" #include "core/settings.h" #include "ui_configure_ui.h" #include "yuzu/configuration/configure_ui.h" @@ -29,6 +32,8 @@ constexpr std::array row_text_names{ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { ui->setupUi(this); + InitializeLanguageComboBox(); + for (const auto& theme : UISettings::themes) { ui->theme_combobox->addItem(QString::fromUtf8(theme.first), QString::fromUtf8(theme.second)); @@ -49,9 +54,21 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur // Update text ComboBoxes after user interaction. connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated), - [=]() { ConfigureUi::UpdateSecondRowComboBox(); }); + [this] { ConfigureUi::UpdateSecondRowComboBox(); }); connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated), - [=]() { ConfigureUi::UpdateFirstRowComboBox(); }); + [this] { ConfigureUi::UpdateFirstRowComboBox(); }); + + // Set screenshot path to user specification. + connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { + const QString& filename = + QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), + QString::fromStdString(Common::FS::GetUserPath( + Common::FS::UserPath::ScreenshotsDir))) + + QDir::separator(); + if (!filename.isEmpty()) { + ui->screenshot_path_edit->setText(filename); + } + }); } ConfigureUi::~ConfigureUi() = default; @@ -63,6 +80,10 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + + 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(); } @@ -72,9 +93,15 @@ void ConfigureUi::RequestGameListUpdate() { void ConfigureUi::SetConfiguration() { ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->language_combobox->setCurrentIndex( + ui->language_combobox->findData(UISettings::values.language)); ui->show_add_ons->setChecked(UISettings::values.show_add_ons); ui->icon_size_combobox->setCurrentIndex( ui->icon_size_combobox->findData(UISettings::values.icon_size)); + + ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); + ui->screenshot_path_edit->setText( + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir))); } void ConfigureUi::changeEvent(QEvent* event) { @@ -100,6 +127,25 @@ void ConfigureUi::RetranslateUI() { } } +void ConfigureUi::InitializeLanguageComboBox() { + ui->language_combobox->addItem(tr("<System>"), QString{}); + ui->language_combobox->addItem(tr("English"), QStringLiteral("en")); + QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + QString locale = it.next(); + locale.truncate(locale.lastIndexOf(QLatin1Char{'.'})); + locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1); + const QString lang = QLocale::languageToString(QLocale(locale).language()); + ui->language_combobox->addItem(lang, locale); + } + + // Unlike other configuration changes, interface language changes need to be reflected on the + // interface immediately. This is done by passing a signal to the main window, and then + // retranslating when passing back. + connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureUi::OnLanguageChanged); +} + void ConfigureUi::InitializeIconSizeComboBox() { for (const auto& size : default_icon_sizes) { ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first); @@ -147,3 +193,10 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) { ui->row_2_text_combobox->removeItem( ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData())); } + +void ConfigureUi::OnLanguageChanged(int index) { + if (index == -1) + return; + + emit LanguageChanged(ui->language_combobox->itemData(index).toString()); +} diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h index d471afe99..c30bcf6ff 100644 --- a/src/yuzu/configuration/configure_ui.h +++ b/src/yuzu/configuration/configure_ui.h @@ -20,6 +20,12 @@ public: void ApplyConfiguration(); +private slots: + void OnLanguageChanged(int index); + +signals: + void LanguageChanged(const QString& locale); + private: void RequestGameListUpdate(); @@ -28,6 +34,7 @@ private: void changeEvent(QEvent*) override; void RetranslateUI(); + void InitializeLanguageComboBox(); void InitializeIconSizeComboBox(); void InitializeRowComboBoxes(); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index bd5c5d3c2..d895b799f 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -6,119 +6,180 @@ <rect> <x>0</x> <y>0</y> - <width>300</width> - <height>377</height> + <width>363</width> + <height>391</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> - <layout class="QHBoxLayout" name="HorizontalLayout"> + <layout class="QVBoxLayout" name="verticalLayout"> <item> - <layout class="QVBoxLayout" name="VerticalLayout"> - <item> - <widget class="QGroupBox" name="GeneralGroupBox"> - <property name="title"> - <string>General</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <widget class="QGroupBox" name="general_groupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <layout class="QVBoxLayout" name="verticalLayout"> + <widget class="QLabel" name="label_change_language_info"> + <property name="text"> + <string>Note: Changing language will apply your configuration.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="language_label"> + <property name="text"> + <string>Interface language:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="language_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="theme_label"> + <property name="text"> + <string>Theme:</string> + </property> + </widget> + </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QLabel" name="theme_label"> - <property name="text"> - <string>Theme:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="theme_combobox"/> - </item> - </layout> + <widget class="QComboBox" name="theme_combobox"/> </item> </layout> </item> </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="GameListGroupBox"> - <property name="title"> - <string>Game List</string> - </property> - <layout class="QHBoxLayout" name="GameListHorizontalLayout"> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="GameListGroupBox"> + <property name="title"> + <string>Game List</string> + </property> + <layout class="QHBoxLayout" name="GameListHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> - <layout class="QVBoxLayout" name="GeneralVerticalLayout"> + <widget class="QCheckBox" name="show_add_ons"> + <property name="text"> + <string>Show Add-Ons Column</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> <item> - <widget class="QCheckBox" name="show_add_ons"> + <widget class="QLabel" name="icon_size_label"> <property name="text"> - <string>Show Add-Ons Column</string> + <string>Icon Size:</string> </property> </widget> </item> <item> - <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> - <item> - <widget class="QLabel" name="icon_size_label"> - <property name="text"> - <string>Icon Size:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="icon_size_combobox"/> - </item> - </layout> + <widget class="QComboBox" name="icon_size_combobox"/> </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="row_1_qhbox_layout"> <item> - <layout class="QHBoxLayout" name="row_1_qhbox_layout"> - <item> - <widget class="QLabel" name="row_1_label"> - <property name="text"> - <string>Row 1 Text:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="row_1_text_combobox"/> - </item> - </layout> + <widget class="QLabel" name="row_1_label"> + <property name="text"> + <string>Row 1 Text:</string> + </property> + </widget> </item> <item> - <layout class="QHBoxLayout" name="row_2_qhbox_layout"> - <item> - <widget class="QLabel" name="row_2_label"> - <property name="text"> - <string>Row 2 Text:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="row_2_text_combobox"/> - </item> - </layout> + <widget class="QComboBox" name="row_1_text_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="row_2_qhbox_layout"> + <item> + <widget class="QLabel" name="row_2_label"> + <property name="text"> + <string>Row 2 Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="row_2_text_combobox"/> </item> </layout> </item> </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="screenshots_GroupBox"> + <property name="title"> + <string>Screenshots</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QCheckBox" name="enable_screenshot_save_as"> + <property name="text"> + <string>Ask Where To Save Screenshots (Windows Only)</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Screenshots Path: </string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="screenshot_path_edit"/> + </item> + <item> + <widget class="QToolButton" name="screenshot_path_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> </item> </layout> </widget> diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 53049ffd6..0e26f765b 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -109,8 +109,7 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { MicroProfileSetDisplayMode(1); // Timers screen MicroProfileInitUI(); - connect(&update_timer, &QTimer::timeout, this, - static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update)); + connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update)); } void MicroProfileWidget::paintEvent(QPaintEvent* ev) { diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 9bb0a0109..3439cb333 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -2,9 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <fmt/format.h> #include "yuzu/debugger/wait_tree.h" +#include "yuzu/uisettings.h" #include "yuzu/util/util.h" #include "common/assert.h" @@ -19,11 +21,40 @@ #include "core/hle/kernel/thread.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}, + {Qt::GlobalColor::darkRed, Qt::GlobalColor::red}, + {Qt::GlobalColor::darkYellow, Qt::GlobalColor::yellow}, + {Qt::GlobalColor::red, Qt::GlobalColor::red}, + {Qt::GlobalColor::darkCyan, Qt::GlobalColor::cyan}, + {Qt::GlobalColor::gray, Qt::GlobalColor::gray}, +}}; + +bool IsDarkTheme() { + const auto& theme = UISettings::values.theme; + return theme == QStringLiteral("qdarkstyle") || + theme == QStringLiteral("qdarkstyle_midnight_blue") || + theme == QStringLiteral("colorful_dark") || + theme == QStringLiteral("colorful_midnight_blue"); +} + +} // namespace + WaitTreeItem::WaitTreeItem() = default; WaitTreeItem::~WaitTreeItem() = default; QColor WaitTreeItem::GetColor() const { - return QColor(Qt::GlobalColor::black); + if (IsDarkTheme()) { + return QColor(Qt::GlobalColor::white); + } else { + return QColor(Qt::GlobalColor::black); + } } std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const { @@ -263,36 +294,38 @@ 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(Qt::GlobalColor::darkGreen); + return QColor(WaitTreeColors[0][color_index]); case Kernel::ThreadStatus::Ready: if (!thread.IsPaused()) { if (thread.WasRunning()) { - return QColor(Qt::GlobalColor::darkGreen); + return QColor(WaitTreeColors[1][color_index]); } else { - return QColor(Qt::GlobalColor::darkBlue); + return QColor(WaitTreeColors[2][color_index]); } } else { - return QColor(Qt::GlobalColor::lightGray); + return QColor(WaitTreeColors[3][color_index]); } case Kernel::ThreadStatus::Paused: - return QColor(Qt::GlobalColor::lightGray); + return QColor(WaitTreeColors[4][color_index]); case Kernel::ThreadStatus::WaitHLEEvent: case Kernel::ThreadStatus::WaitIPC: - return QColor(Qt::GlobalColor::darkRed); + return QColor(WaitTreeColors[5][color_index]); case Kernel::ThreadStatus::WaitSleep: - return QColor(Qt::GlobalColor::darkYellow); + return QColor(WaitTreeColors[6][color_index]); case Kernel::ThreadStatus::WaitSynch: case Kernel::ThreadStatus::WaitMutex: case Kernel::ThreadStatus::WaitCondVar: case Kernel::ThreadStatus::WaitArb: - return QColor(Qt::GlobalColor::red); + return QColor(WaitTreeColors[7][color_index]); case Kernel::ThreadStatus::Dormant: - return QColor(Qt::GlobalColor::darkCyan); + return QColor(WaitTreeColors[8][color_index]); case Kernel::ThreadStatus::Dead: - return QColor(Qt::GlobalColor::gray); + return QColor(WaitTreeColors[9][color_index]); default: return WaitTreeItem::GetColor(); } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index ab7fc7a24..70d865112 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,7 +117,7 @@ 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); label_filter = new QLabel; @@ -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; + } + + auto* game_dir = item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + game_dir->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); @@ -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(); } @@ -397,22 +407,24 @@ 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)) { item_model->invisibleRootItem()->removeRow(child->row()); 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,30 +484,58 @@ 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_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); + QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = context_menu.addAction(tr("Open Transferable Shader Cache")); context_menu.addSeparator(); + QMenu* remove_menu = context_menu.addMenu(tr("Remove")); + QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); + QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); + QAction* remove_shader_cache = remove_menu->addAction(tr("Remove Shader Cache")); + QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); + remove_menu->addSeparator(); + QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); context_menu.addSeparator(); QAction* properties = context_menu.addAction(tr("Properties")); - open_save_location->setEnabled(program_id != 0); + open_save_location->setVisible(program_id != 0); + open_mod_location->setVisible(program_id != 0); + open_transferable_shader_cache->setVisible(program_id != 0); + remove_update->setVisible(program_id != 0); + remove_dlc->setVisible(program_id != 0); + remove_shader_cache->setVisible(program_id != 0); + remove_all_content->setVisible(program_id != 0); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); connect(open_save_location, &QAction::triggered, [this, program_id, path]() { - emit OpenFolderRequested(GameListOpenTarget::SaveData, path); + emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); }); - connect(open_lfs_location, &QAction::triggered, [this, program_id, path]() { - emit OpenFolderRequested(GameListOpenTarget::ModData, path); + connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { + emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); }); connect(open_transferable_shader_cache, &QAction::triggered, [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); + connect(remove_all_content, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game); + }); + connect(remove_update, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update); + }); + connect(remove_dlc, &QAction::triggered, [this, program_id]() { + emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent); + }); + connect(remove_shader_cache, &QAction::triggered, [this, program_id]() { + emit RemoveFileRequested(program_id, GameListRemoveTarget::ShaderCache); + }); + connect(remove_custom_config, &QAction::triggered, [this, program_id]() { + emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration); + }); connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); connect(copy_tid, &QAction::triggered, @@ -662,12 +702,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 a38cb2fc3..58059a3c4 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -39,6 +39,17 @@ enum class GameListOpenTarget { ModData, }; +enum class GameListRemoveTarget { + ShaderCache, + CustomConfiguration, +}; + +enum class InstalledEntryType { + Game, + Update, + AddOnContent, +}; + class GameList : public QWidget { Q_OBJECT @@ -56,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); @@ -71,10 +82,13 @@ public: static const QStringList supported_file_extensions; signals: - void GameChosen(QString game_path); + void GameChosen(const QString& game_path); void ShouldCancelWorker(); - void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path); + void OpenFolderRequested(u64 program_id, GameListOpenTarget target, + const std::string& game_path); void OpenTransferableShaderCacheRequested(u64 program_id); + void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); + void RemoveFileRequested(u64 program_id, GameListRemoveTarget target); void DumpRomFSRequested(u64 program_id, const std::string& game_path); void CopyTIDRequested(u64 program_id); void NavigateToGamedbEntryRequested(u64 program_id, @@ -85,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); @@ -117,8 +131,6 @@ private: friend class GameListSearchField; }; -Q_DECLARE_METATYPE(GameListOpenTarget); - class GameListPlaceholder : public QWidget { Q_OBJECT public: diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 0cd0054c8..248855aff 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); @@ -181,7 +185,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 +221,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) @@ -326,7 +330,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 2018150db..e0ce45fd9 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -39,12 +39,12 @@ QString GetGameListCachedObject(const std::string& filename, const std::string& return generator(); } - const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + - DIR_SEP + filename + '.' + ext; + const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + filename + '.' + ext; - FileUtil::CreateFullPath(path); + Common::FS::CreateFullPath(path); - if (!FileUtil::Exists(path)) { + if (!Common::FS::Exists(path)) { const auto str = generator(); QFile file{QString::fromStdString(path)}; @@ -70,14 +70,14 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject( return generator(); } - const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + - DIR_SEP + filename + ".jpeg"; - const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + - DIR_SEP + filename + ".appname.txt"; + const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + filename + ".jpeg"; + const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + + "game_list" + DIR_SEP + filename + ".appname.txt"; - FileUtil::CreateFullPath(path1); + Common::FS::CreateFullPath(path1); - if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) { + if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { const auto [icon, nacp] = generator(); QFile file1{QString::fromStdString(path1)}; @@ -208,7 +208,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri file_type_string, program_id), new GameListItemCompat(compatibility), new GameListItem(file_type_string), - new GameListItemSize(FileUtil::GetSize(path)), + new GameListItemSize(Common::FS::GetSize(path)), }; if (UISettings::values.show_add_ons) { @@ -289,7 +289,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } const std::string physical_name = directory + DIR_SEP + virtual_name; - const bool is_dir = FileUtil::IsDirectory(physical_name); + const bool is_dir = Common::FS::IsDirectory(physical_name); if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); @@ -345,11 +345,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa return true; }; - FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); + Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback); } void GameListWorker::run() { stop_processing = false; + provider->ClearAllEntries(); for (UISettings::GameDir& game_dir : game_dirs) { if (game_dir.path == QStringLiteral("SDMC")) { @@ -368,13 +369,12 @@ void GameListWorker::run() { watch_list.append(game_dir.path); auto* const game_list_dir = new GameListDir(game_dir); emit DirEntryReady(game_list_dir); - provider->ClearAllEntries(); - ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2, - game_list_dir); + ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), + game_dir.deep_scan ? 256 : 0, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0, game_list_dir); } - }; + } emit Finished(watch_list); } 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 9f758605a..e3de0f0e1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,6 +11,7 @@ #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" @@ -19,7 +20,9 @@ #include "configuration/configure_per_game.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" @@ -84,7 +87,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" @@ -94,6 +96,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "input_common/main.h" +#include "video_core/gpu.h" +#include "video_core/shader_notify.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/compatdb.h" @@ -175,8 +180,8 @@ static void InitializeLogging() { log_filter.ParseFilterString(Settings::values.log_filter); Log::SetGlobalFilter(log_filter); - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); + 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>()); @@ -184,11 +189,13 @@ static void InitializeLogging() { } GMainWindow::GMainWindow() - : config(new Config()), emu_thread(nullptr), - vfs(std::make_shared<FileSys::RealVfsFilesystem>()), - provider(std::make_unique<FileSys::ManualContentProvider>()) { + : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, + config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, + provider{std::make_unique<FileSys::ManualContentProvider>()} { InitializeLogging(); + LoadTranslation(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); @@ -278,18 +285,38 @@ 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(); + 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.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; } + index = dialog.GetIndex(); } @@ -305,8 +332,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) { @@ -469,7 +497,7 @@ void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui.action_Report_Compatibility->setVisible(true); #endif - render_window = new GRenderWindow(this, emu_thread.get()); + render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem); render_window->hide(); game_list = new GameList(vfs, provider.get(), this); @@ -498,6 +526,8 @@ void GMainWindow::InitializeWidgets() { message_label->setAlignment(Qt::AlignLeft); statusBar()->addPermanentWidget(message_label, 1); + shader_building_label = new QLabel(); + shader_building_label->setToolTip(tr("The amount of shaders currently being built")); emu_speed_label = new QLabel(); emu_speed_label->setToolTip( tr("Current emulation speed. Values higher or lower than 100% " @@ -510,7 +540,8 @@ void GMainWindow::InitializeWidgets() { tr("Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For " "full-speed emulation this should be at most 16.67 ms.")); - for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + for (auto& label : + {shader_building_label, emu_speed_label, game_fps_label, emu_frametime_label}) { label->setVisible(false); label->setFrameStyle(QFrame::NoFrame); label->setContentsMargins(4, 0, 4, 0); @@ -576,7 +607,7 @@ void GMainWindow::InitializeWidgets() { renderer_status_button->setObjectName(QStringLiteral("RendererStatusBarButton")); renderer_status_button->setCheckable(true); renderer_status_button->setFocusPolicy(Qt::NoFocus); - connect(renderer_status_button, &QPushButton::toggled, [=](bool checked) { + connect(renderer_status_button, &QPushButton::toggled, [this](bool checked) { renderer_status_button->setText(checked ? tr("VULKAN") : tr("OPENGL")); }); renderer_status_button->toggle(); @@ -588,7 +619,7 @@ void GMainWindow::InitializeWidgets() { #else renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); - connect(renderer_status_button, &QPushButton::clicked, [=] { + connect(renderer_status_button, &QPushButton::clicked, [this] { if (emulation_running) { return; } @@ -809,7 +840,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()); @@ -840,6 +871,9 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, &GMainWindow::OnTransferableShaderCacheOpenFile); + connect(game_list, &GameList::RemoveInstalledEntryRequested, this, + &GMainWindow::OnGameListRemoveInstalledEntry); + connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, @@ -884,6 +918,8 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Configure_Current_Game, &QAction::triggered, this, + &GMainWindow::OnConfigurePerGame); // View connect(ui.action_Single_Window_Mode, &QAction::triggered, this, @@ -953,13 +989,14 @@ 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 + nullptr, // E-Commerce + 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(); @@ -1029,7 +1066,7 @@ bool GMainWindow::LoadROM(const QString& filename) { } game_path = filename; - system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); + system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt"); return true; } @@ -1111,7 +1148,7 @@ void GMainWindow::BootGame(const QString& filename) { title_name = metadata.first->GetApplicationName(); } if (res != Loader::ResultStatus::Success || title_name.empty()) { - title_name = FileUtil::GetFilename(filename.toStdString()); + title_name = Common::FS::GetFilename(filename.toStdString()); } LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); UpdateWindowTitle(title_name, title_version); @@ -1157,17 +1194,19 @@ void GMainWindow::ShutdownGame() { ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(false); ui.action_Restart->setEnabled(false); + ui.action_Configure_Current_Game->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); ui.action_Load_Amiibo->setEnabled(false); ui.action_Capture_Screenshot->setEnabled(false); 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); @@ -1176,6 +1215,7 @@ void GMainWindow::ShutdownGame() { // Disable status bar updates status_bar_update_timer.stop(); + shader_building_label->setVisible(false); emu_speed_label->setVisible(false); game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); @@ -1228,28 +1268,36 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { BootGame(game_path); } -void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path) { +void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, + const std::string& game_path) { std::string path; QString open_target; - const auto v_file = Core::GetGameFileFromPath(vfs, game_path); - const auto loader = Loader::GetLoader(v_file); - FileSys::NACP control{}; - u64 program_id{}; + const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] { + FileSys::PatchManager pm{program_id}; + 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); - loader->ReadControlData(control); - loader->ReadProgramId(program_id); + FileSys::NACP nacp{}; + loader->ReadControlData(nacp); + return std::make_pair(nacp.GetDefaultNormalSaveSize(), nacp.GetDeviceSaveDataSize()); + } + }(); - const bool has_user_save{control.GetDefaultNormalSaveSize() > 0}; - const bool has_device_save{control.GetDeviceSaveDataSize() > 0}; + const bool has_user_save{user_save_size > 0}; + const bool has_device_save{device_save_size > 0}; ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?"); switch (target) { case GameListOpenTarget::SaveData: { open_target = tr("Save Data"); - const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); - ASSERT(program_id != 0); + const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir); if (has_user_save) { // User save data @@ -1284,16 +1332,16 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str FileSys::SaveDataType::SaveData, program_id, {}, 0); } - if (!FileUtil::Exists(path)) { - FileUtil::CreateFullPath(path); - FileUtil::CreateDir(path); + if (!Common::FS::Exists(path)) { + Common::FS::CreateFullPath(path); + Common::FS::CreateDir(path); } break; } case GameListOpenTarget::ModData: { open_target = tr("Mod Data"); - const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); + const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir); path = fmt::format("{}{:016X}", load_dir, program_id); break; } @@ -1314,14 +1362,12 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str } void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { - ASSERT(program_id != 0); - const QString shader_dir = - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); - const QString tranferable_shader_cache_folder_path = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); + const QString transferable_shader_cache_folder_path = shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); const QString transferable_shader_cache_file_path = - tranferable_shader_cache_folder_path + QDir::separator() + + transferable_shader_cache_folder_path + QDir::separator() + QString::fromStdString(fmt::format("{:016X}.bin", program_id)); if (!QFile::exists(transferable_shader_cache_file_path)) { @@ -1342,7 +1388,7 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { param << QDir::toNativeSeparators(transferable_shader_cache_file_path); QProcess::startDetached(explorer, param); #else - QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path)); + QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); #endif } @@ -1386,6 +1432,174 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src return true; } +void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { + const QString entry_type = [this, type] { + switch (type) { + case InstalledEntryType::Game: + return tr("Contents"); + case InstalledEntryType::Update: + return tr("Update"); + case InstalledEntryType::AddOnContent: + return tr("DLC"); + default: + return QString{}; + } + }(); + + if (QMessageBox::question( + this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + return; + } + + switch (type) { + case InstalledEntryType::Game: + RemoveBaseContent(program_id, entry_type); + [[fallthrough]]; + case InstalledEntryType::Update: + RemoveUpdateContent(program_id, entry_type); + if (type != InstalledEntryType::Game) { + break; + } + [[fallthrough]]; + case InstalledEntryType::AddOnContent: + RemoveAddOnContent(program_id, entry_type); + break; + } + Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + + DIR_SEP + "game_list"); + game_list->PopulateAsync(UISettings::values.game_dirs); +} + +void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); + const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); + + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed base game.")); + } else { + QMessageBox::warning( + this, tr("Error Removing %1").arg(entry_type), + tr("The base game is not installed in the NAND and cannot be removed.")); + } +} + +void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) { + const auto update_id = program_id | 0x800; + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); + const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); + + if (res) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the installed update.")); + } else { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There is no update installed for this title.")); + } +} + +void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) { + u32 count{}; + const auto& fs_controller = Core::System::GetInstance().GetFileSystemController(); + const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter( + FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + + for (const auto& entry : dlc_entries) { + if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + const auto res = + fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); + if (res) { + ++count; + } + } + } + + if (count == 0) { + QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), + tr("There are no DLC installed for this title.")); + return; + } + + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed %1 installed DLC.").arg(count)); +} + +void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) { + const QString question = [this, target] { + switch (target) { + case GameListRemoveTarget::ShaderCache: + return tr("Delete Transferable Shader Cache?"); + case GameListRemoveTarget::CustomConfiguration: + return tr("Remove Custom Game Configuration?"); + default: + return QString{}; + } + }(); + + if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) { + return; + } + + switch (target) { + case GameListRemoveTarget::ShaderCache: + RemoveTransferableShaderCache(program_id); + break; + case GameListRemoveTarget::CustomConfiguration: + RemoveCustomConfiguration(program_id); + break; + } +} + +void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { + const QString shader_dir = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); + const QString transferable_shader_cache_folder_path = + shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); + const QString transferable_shader_cache_file_path = + transferable_shader_cache_folder_path + QDir::separator() + + QString::fromStdString(fmt::format("{:016X}.bin", program_id)); + + if (!QFile::exists(transferable_shader_cache_file_path)) { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("A shader cache for this title does not exist.")); + return; + } + + if (QFile::remove(transferable_shader_cache_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the transferable shader cache.")); + } else { + QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), + tr("Failed to remove the transferable shader cache.")); + } +} + +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)); + + if (!QFile::exists(custom_config_file_path)) { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("A custom configuration for this title does not exist.")); + return; + } + + if (QFile::remove(custom_config_file_path)) { + QMessageBox::information(this, tr("Successfully Removed"), + tr("Successfully removed the custom game configuration.")); + } else { + QMessageBox::warning(this, tr("Error Removing Custom Configuration"), + tr("Failed to remove the custom game configuration.")); + } +} + void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { const auto failed = [this] { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), @@ -1414,7 +1628,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } const auto path = fmt::format( - "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id); + "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id); FileSys::VirtualFile romfs; @@ -1494,13 +1708,13 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, void GMainWindow::OnGameListOpenDirectory(const QString& directory) { QString path; if (directory == QStringLiteral("SDMC")) { - path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + + path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + "Nintendo/Contents/registered"); } else if (directory == QStringLiteral("UserNAND")) { - path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "user/Contents/registered"); } else if (directory == QStringLiteral("SysNAND")) { - path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "system/Contents/registered"); } else { path = directory; @@ -1514,8 +1728,10 @@ void GMainWindow::OnGameListOpenDirectory(const QString& directory) { void GMainWindow::OnGameListAddDirectory() { const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - if (dir_path.isEmpty()) + if (dir_path.isEmpty()) { return; + } + UISettings::GameDir game_dir{dir_path, false, true}; if (!UISettings::values.game_dirs.contains(game_dir)) { UISettings::values.game_dirs.append(game_dir); @@ -1542,26 +1758,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { return; } - ConfigurePerGame dialog(this, title_id); - dialog.LoadFromFile(v_file); - auto result = dialog.exec(); - if (result == QDialog::Accepted) { - dialog.ApplyConfiguration(); - - const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); - if (reload) { - game_list->PopulateAsync(UISettings::values.game_dirs); - } - - // Do not cause the global config to write local settings into the config file - Settings::RestoreGlobalState(); - - if (!Core::System::GetInstance().IsPoweredOn()) { - config->Save(); - } - } else { - Settings::RestoreGlobalState(); - } + OpenPerGameConfiguration(title_id, file); } void GMainWindow::OnMenuLoadFile() { @@ -1647,7 +1844,7 @@ void GMainWindow::OnMenuInstallToNAND() { ui.action_Install_File_NAND->setEnabled(false); - install_progress = new QProgressDialog(QStringLiteral(""), tr("Cancel"), 0, total_size, this); + install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowMaximizeButtonHint); install_progress->setAttribute(Qt::WA_DeleteOnClose, true); @@ -1697,18 +1894,18 @@ void GMainWindow::OnMenuInstallToNAND() { install_progress->close(); const QString install_results = - (new_files.isEmpty() ? QStringLiteral("") + (new_files.isEmpty() ? QString{} : tr("%n file(s) were newly installed\n", "", new_files.size())) + (overwritten_files.isEmpty() - ? QStringLiteral("") + ? QString{} : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) + - (failed_files.isEmpty() ? QStringLiteral("") + (failed_files.isEmpty() ? QString{} : tr("%n file(s) failed to install\n", "", failed_files.size())); QMessageBox::information(this, tr("Install Results"), install_results); + Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + + DIR_SEP + "game_list"); game_list->PopulateAsync(UISettings::values.game_dirs); - FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + - "game_list"); ui.action_Install_File_NAND->setEnabled(true); } @@ -1875,6 +2072,7 @@ 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"); @@ -1890,6 +2088,7 @@ void GMainWindow::OnStartGame() { ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); ui.action_Restart->setEnabled(true); + ui.action_Configure_Current_Game->setEnabled(true); ui.action_Report_Compatibility->setEnabled(true); discord_rpc->Update(); @@ -2041,7 +2240,10 @@ void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence; - ConfigureDialog configure_dialog(this, hotkey_registry); + ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get()); + connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, + &GMainWindow::OnLanguageChanged); + const auto result = configure_dialog.exec(); if (result != QDialog::Accepted) { return; @@ -2076,6 +2278,36 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); } +void GMainWindow::OnConfigurePerGame() { + const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + OpenPerGameConfiguration(title_id, game_path.toStdString()); +} + +void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { + const auto v_file = Core::GetGameFileFromPath(vfs, file_name); + + ConfigurePerGame dialog(this, title_id); + dialog.LoadFromFile(v_file); + auto result = dialog.exec(); + if (result == QDialog::Accepted) { + dialog.ApplyConfiguration(); + + const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); + if (reload) { + game_list->PopulateAsync(UISettings::values.game_dirs); + } + + // Do not cause the global config to write local settings into the config file + Settings::RestoreGlobalState(); + + if (!Core::System::GetInstance().IsPoweredOn()) { + config->Save(); + } + } else { + Settings::RestoreGlobalState(); + } +} + void GMainWindow::OnLoadAmiibo() { const QString extensions{QStringLiteral("*.bin")}; const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); @@ -2123,7 +2355,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { void GMainWindow::OnOpenYuzuFolder() { QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir)))); + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir)))); } void GMainWindow::OnAbout() { @@ -2132,27 +2364,38 @@ 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(); } } void GMainWindow::OnCaptureScreenshot() { OnPauseGame(); - QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path, - tr("PNG Image (*.png)")); - png_dialog.setAcceptMode(QFileDialog::AcceptSave); - png_dialog.setDefaultSuffix(QStringLiteral("png")); - if (png_dialog.exec()) { - const QString path = png_dialog.selectedFiles().first(); - if (!path.isEmpty()) { - UISettings::values.screenshot_path = QFileInfo(path).path(); - render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path); + + const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + const auto screenshot_path = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)); + const auto date = + QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); + QString filename = QStringLiteral("%1%2_%3.png") + .arg(screenshot_path) + .arg(title_id, 16, 16, QLatin1Char{'0'}) + .arg(date); + +#ifdef _WIN32 + if (UISettings::values.enable_screenshot_save_as) { + filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename, + tr("PNG Image (*.png)")); + if (filename.isEmpty()) { + OnStartGame(); + return; } } +#endif + render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, filename); OnStartGame(); } @@ -2186,6 +2429,16 @@ void GMainWindow::UpdateStatusBar() { } auto results = Core::System::GetInstance().GetAndResetPerfStats(); + auto& shader_notify = Core::System::GetInstance().GPU().ShaderNotify(); + const auto shaders_building = shader_notify.GetShadersBuilding(); + + if (shaders_building != 0) { + shader_building_label->setText( + tr("Building: %n shader(s)", "", static_cast<int>(shaders_building))); + shader_building_label->setVisible(true); + } else { + shader_building_label->setVisible(false); + } if (Settings::values.use_frame_limit.GetValue()) { emu_speed_label->setText(tr("Speed: %1% / %2%") @@ -2315,32 +2568,37 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { if (behavior == ReinitializeKeyBehavior::Warning) { const auto res = QMessageBox::information( this, tr("Confirm Key Rederivation"), - tr("You are about to force rederive all of your keys. \nIf you do not know what this " - "means or what you are doing, \nthis is a potentially destructive action. \nPlease " - "make sure this is what you want \nand optionally make backups.\n\nThis will delete " + tr("You are about to force rederive all of your keys. \nIf you do not know what " + "this " + "means or what you are doing, \nthis is a potentially destructive action. " + "\nPlease " + "make sure this is what you want \nand optionally make backups.\n\nThis will " + "delete " "your autogenerated key files and re-run the key derivation module."), QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); if (res == QMessageBox::Cancel) return; - FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + - "prod.keys_autogenerated"); - FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + - "console.keys_autogenerated"); - FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + - "title.keys_autogenerated"); + Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + + "prod.keys_autogenerated"); + Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + + "console.keys_autogenerated"); + Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + + "title.keys_autogenerated"); } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); if (keys.BaseDeriveNecessary()) { Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( - FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; + Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)}; 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; @@ -2600,6 +2858,43 @@ void GMainWindow::UpdateUITheme() { QIcon::setThemeSearchPaths(theme_paths); } +void GMainWindow::LoadTranslation() { + // If the selected language is English, no need to install any translation + if (UISettings::values.language == QStringLiteral("en")) { + return; + } + + bool loaded; + + if (UISettings::values.language.isEmpty()) { + // If the selected language is empty, use system locale + loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/")); + } else { + // Otherwise load from the specified file + loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/")); + } + + if (loaded) { + qApp->installTranslator(&translator); + } else { + UISettings::values.language = QStringLiteral("en"); + } +} + +void GMainWindow::OnLanguageChanged(const QString& locale) { + if (UISettings::values.language != QStringLiteral("en")) { + qApp->removeTranslator(&translator); + } + + UISettings::values.language = locale; + LoadTranslation(); + ui.retranslateUi(this); + UpdateWindowTitle(); + + if (emulation_running) + ui.action_Start->setText(tr("Continue")); +} + void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #ifdef USE_DISCORD_PRESENCE if (state) { @@ -2628,9 +2923,9 @@ int main(int argc, char* argv[]) { #ifdef __APPLE__ // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". - // But since we require the working directory to be the executable path for the location of the - // user folder in the Qt Frontend, we need to cd into that working directory - const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; + // But since we require the working directory to be the executable path for the location of + // the user folder in the Qt Frontend, we need to cd into that working directory + const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); #endif diff --git a/src/yuzu/main.h b/src/yuzu/main.h index adff65fb5..afcfa68a9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -10,6 +10,7 @@ #include <QMainWindow> #include <QTimer> +#include <QTranslator> #include "common/common_types.h" #include "core/core.h" @@ -31,18 +32,29 @@ class QPushButton; class QProgressDialog; class WaitTreeWidget; enum class GameListOpenTarget; +enum class GameListRemoveTarget; +enum class InstalledEntryType; class GameListPlaceholder; namespace Core::Frontend { +struct ControllerParameters; struct SoftwareKeyboardParameters; } // namespace Core::Frontend +namespace DiscordRPC { +class DiscordInterface; +} + namespace FileSys { class ContentProvider; class ManualContentProvider; class VfsFilesystem; } // namespace FileSys +namespace InputCommon { +class InputSubsystem; +} + enum class EmulatedDirectoryTarget { NAND, SDMC, @@ -59,10 +71,6 @@ enum class ReinitializeKeyBehavior { Warning, }; -namespace DiscordRPC { -class DiscordInterface; -} - class GMainWindow : public QMainWindow { Q_OBJECT @@ -83,8 +91,6 @@ public: GMainWindow(); ~GMainWindow() override; - std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; - bool DropAction(QDropEvent* event); void AcceptDropEvent(QDropEvent* event); @@ -111,9 +117,12 @@ signals: void UpdateInstallProgress(); + void ControllerSelectorReconfigureFinished(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); + void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); @@ -122,6 +131,8 @@ signals: public slots: void OnLoadComplete(); + void ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); @@ -195,8 +206,11 @@ private slots: void OnOpenFAQ(); /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); - void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); + void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, + const std::string& game_path); void OnTransferableShaderCacheOpenFile(u64 program_id); + void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); + void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target); void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, @@ -211,6 +225,7 @@ private slots: void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); + void OnConfigurePerGame(); void OnLoadAmiibo(); void OnOpenYuzuFolder(); void OnAbout(); @@ -225,8 +240,14 @@ private slots: void OnCaptureScreenshot(); void OnCoreError(Core::System::ResultStatus, std::string); void OnReinitializeKeys(ReinitializeKeyBehavior behavior); + void OnLanguageChanged(const QString& locale); private: + void RemoveBaseContent(u64 program_id, const QString& entry_type); + void RemoveUpdateContent(u64 program_id, const QString& entry_type); + void RemoveAddOnContent(u64 program_id, const QString& entry_type); + void RemoveTransferableShaderCache(u64 program_id); + void RemoveCustomConfiguration(u64 program_id); std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); InstallResult InstallNSPXCI(const QString& filename); InstallResult InstallNCA(const QString& filename); @@ -237,9 +258,14 @@ private: void HideMouseCursor(); void ShowMouseCursor(); void OpenURL(const QUrl& url); + void LoadTranslation(); + void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); Ui::MainWindow ui; + std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; + GRenderWindow* render_window; GameList* game_list; LoadingScreen* loading_screen; @@ -248,6 +274,7 @@ private: // Status bar elements QLabel* message_label = nullptr; + QLabel* shader_building_label = nullptr; QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; @@ -284,6 +311,8 @@ private: HotkeyRegistry hotkey_registry; + QTranslator translator; + // Install progress dialog QProgressDialog* install_progress; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index c3a1d715e..2f3792247 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -81,6 +81,7 @@ <addaction name="action_Restart"/> <addaction name="separator"/> <addaction name="action_Configure"/> + <addaction name="action_Configure_Current_Game"/> </widget> <widget class="QMenu" name="menu_View"> <property name="title"> @@ -287,6 +288,14 @@ <string>Capture Screenshot</string> </property> </action> + <action name="action_Configure_Current_Game"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Configure Current Game...</string> + </property> + </action> </widget> <resources/> <connections/> diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index 738c4b2fc..37499fc85 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -11,7 +11,10 @@ const Themes themes{{ {"Light Colorful", "colorful"}, {"Dark", "qdarkstyle"}, {"Dark Colorful", "colorful_dark"}, + {"Midnight Blue", "qdarkstyle_midnight_blue"}, + {"Midnight Blue Colorful", "colorful_midnight_blue"}, }}; Values values = {}; + } // namespace UISettings diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 830932d45..ce3945485 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -24,19 +24,19 @@ struct Shortcut { ContextualShortcut shortcut; }; -using Themes = std::array<std::pair<const char*, const char*>, 4>; +using Themes = std::array<std::pair<const char*, const char*>, 6>; extern const Themes themes; struct GameDir { QString path; - bool deep_scan; - bool expanded; + bool deep_scan = false; + bool expanded = false; bool operator==(const GameDir& rhs) const { return path == rhs.path; - }; + } bool operator!=(const GameDir& rhs) const { return !operator==(rhs); - }; + } }; struct Values { @@ -66,15 +66,16 @@ struct Values { // Discord RPC bool enable_discord_presence; + bool enable_screenshot_save_as; u16 screenshot_resolution_factor; QString roms_path; QString symbols_path; - QString screenshot_path; QString game_dir_deprecated; bool game_dir_deprecated_deepscan; QVector<UISettings::GameDir> game_dirs; QStringList recent_files; + QString language; QString theme; @@ -86,9 +87,6 @@ struct Values { // logging bool show_console; - // Controllers - int profile_index; - // Game List bool show_add_ons; uint32_t icon_size; @@ -99,6 +97,7 @@ struct Values { }; extern Values values; + } // namespace UISettings Q_DECLARE_METATYPE(UISettings::GameDir*); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 7773228c8..23448e747 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -16,9 +16,11 @@ #include "yuzu_cmd/config.h" #include "yuzu_cmd/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 = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini"; + sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini"; sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); Reload(); @@ -31,8 +33,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { if (sdl2_config->ParseError() < 0) { if (retry) { LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); - FileUtil::CreateFullPath(location); - FileUtil::WriteStringToFile(true, location, default_contents); + FS::CreateFullPath(location); + FS::WriteStringToFile(true, location, default_contents); sdl2_config = std::make_unique<INIReader>(location); // Reopen file return LoadINI(default_contents, false); @@ -286,6 +288,10 @@ void Config::ReadValues() { Settings::values.debug_pad_analogs[i] = default_param; } + Settings::values.vibration_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); + Settings::values.motion_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true); Settings::values.touchscreen.enabled = sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); Settings::values.touchscreen.device = @@ -315,21 +321,21 @@ void Config::ReadValues() { // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, - sdl2_config->Get("Data Storage", "nand_directory", - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, - sdl2_config->Get("Data Storage", "sdmc_directory", - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); - FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, - sdl2_config->Get("Data Storage", "load_directory", - FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); - FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, - sdl2_config->Get("Data Storage", "dump_directory", - FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); - FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, - sdl2_config->Get("Data Storage", "cache_directory", - FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + FS::GetUserPath( + FS::UserPath::NANDDir, + sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir))); + FS::GetUserPath( + FS::UserPath::SDMCDir, + sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir))); + FS::GetUserPath( + FS::UserPath::LoadDir, + sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir))); + FS::GetUserPath( + FS::UserPath::DumpDir, + sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); + FS::GetUserPath(FS::UserPath::CacheDir, + sdl2_config->Get("Data Storage", "cache_directory", + FS::GetUserPath(FS::UserPath::CacheDir))); Settings::values.gamecard_inserted = sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); Settings::values.gamecard_current_game = @@ -394,6 +400,10 @@ void Config::ReadValues() { static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); Settings::values.use_assembly_shaders.SetValue( sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false)); + Settings::values.use_asynchronous_shaders.SetValue( + sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); + Settings::values.use_asynchronous_shaders.SetValue( + sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); Settings::values.use_fast_gpu_time.SetValue( sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 5bed47fd7..aa9e40380 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -166,6 +166,10 @@ use_vsync = # 0 (default): Off, 1: On use_assembly_shaders = +# Whether to allow asynchronous shader building. +# 0 (default): Off, 1: On +use_asynchronous_shaders = + # Turns on the frame limiter, which will limit frames output to the target game speed # 0: Off, 1: On (default) use_frame_limit = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index e5e684206..521209622 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -13,23 +13,24 @@ #include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" -EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} { +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); } - InputCommon::Init(); + input_subsystem->Initialize(); SDL_SetMainReady(); } EmuWindow_SDL2::~EmuWindow_SDL2() { - InputCommon::Shutdown(); + input_subsystem->Shutdown(); SDL_Quit(); } void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - InputCommon::GetMotionEmu()->Tilt(x, y); + input_subsystem->GetMotionEmu()->Tilt(x, y); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { @@ -41,9 +42,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } } else if (button == SDL_BUTTON_RIGHT) { if (state == SDL_PRESSED) { - InputCommon::GetMotionEmu()->BeginTilt(x, y); + input_subsystem->GetMotionEmu()->BeginTilt(x, y); } else { - InputCommon::GetMotionEmu()->EndTilt(); + input_subsystem->GetMotionEmu()->EndTilt(); } } } @@ -79,9 +80,9 @@ void EmuWindow_SDL2::OnFingerUp() { void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { - InputCommon::GetKeyboard()->PressKey(key); + input_subsystem->GetKeyboard()->PressKey(key); } else if (state == SDL_RELEASED) { - InputCommon::GetKeyboard()->ReleaseKey(key); + input_subsystem->GetKeyboard()->ReleaseKey(key); } } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index fffac4252..53d756c3c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -14,9 +14,13 @@ namespace Core { class System; } +namespace InputCommon { +class InputSubsystem; +} + class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(Core::System& system, bool fullscreen); + explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem); ~EmuWindow_SDL2(); /// Polls window events @@ -28,9 +32,6 @@ public: /// Returns if window is shown (not minimized) bool IsShown() const override; - /// Presents the next frame - virtual void Present() = 0; - protected: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); @@ -62,9 +63,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; @@ -76,4 +74,7 @@ protected: /// Keeps track of how often to update the title bar during gameplay u32 last_time = 0; + + /// Input subsystem to use with this window. + InputCommon::InputSubsystem* input_subsystem; }; 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 e78025737..5f35233b5 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -87,8 +87,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen) - : EmuWindow_SDL2{system, fullscreen} { +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_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); @@ -162,13 +162,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 48bb41683..dba5c293c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h @@ -8,13 +8,15 @@ #include "core/frontend/emu_window.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" +namespace InputCommon { +class InputSubsystem; +} + class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen); + 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 cb8e68a39..3ba657c00 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -19,8 +19,8 @@ #include <SDL.h> #include <SDL_syswm.h> -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen) - : EmuWindow_SDL2{system, fullscreen} { +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 = @@ -73,7 +73,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 77a6ca72b..bdfdc3c6f 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h @@ -13,12 +13,14 @@ namespace Core { class System; } +namespace InputCommon { +class InputSubsystem; +} + class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen); - ~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 512b060a7..3a76c785f 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -23,12 +23,15 @@ #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/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" #include "core/telemetry_session.h" +#include "input_common/main.h" #include "video_core/renderer_base.h" #include "yuzu_cmd/config.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" @@ -37,8 +40,6 @@ #include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" #endif -#include "core/file_sys/registered_cache.h" - #ifdef _WIN32 // windows.h needs to be included before shellapi.h #include <windows.h> @@ -82,8 +83,8 @@ static void InitializeLogging() { Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); + 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>()); @@ -179,15 +180,16 @@ int main(int argc, char** argv) { Settings::Apply(); Core::System& system{Core::System::GetInstance()}; + InputCommon::InputSubsystem input_subsystem; 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); + 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); + emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem); break; #else LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); @@ -229,21 +231,20 @@ int main(int argc, char** argv) { } } - system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); + system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL"); // Core is loaded, start the GPU (makes the GPU contexts current to this thread) system.GPU().Start(); - system.Renderer().Rasterizer().LoadDiskResources(); + system.Renderer().Rasterizer().LoadDiskResources( + system.CurrentProcess()->GetTitleID(), false, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); - std::thread render_thread([&emu_window] { emu_window->Present(); }); system.Run(); while (emu_window->IsOpen()) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } system.Pause(); - render_thread.join(); - system.Shutdown(); detached_tasks.WaitForAllTasks(); diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index acb22885e..bc273fb51 100644 --- a/src/yuzu_tester/config.cpp +++ b/src/yuzu_tester/config.cpp @@ -15,10 +15,11 @@ #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 = - FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini"; + sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini"; sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); Reload(); @@ -31,8 +32,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { if (sdl2_config->ParseError() < 0) { if (retry) { LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); - FileUtil::CreateFullPath(location); - FileUtil::WriteStringToFile(true, default_contents, location); + FS::CreateFullPath(location); + FS::WriteStringToFile(true, default_contents, location); sdl2_config = std::make_unique<INIReader>(location); // Reopen file return LoadINI(default_contents, false); @@ -74,6 +75,8 @@ void Config::ReadValues() { Settings::values.debug_pad_analogs[i] = ""; } + Settings::values.vibration_enabled = true; + Settings::values.motion_enabled = true; Settings::values.touchscreen.enabled = ""; Settings::values.touchscreen.device = ""; Settings::values.touchscreen.finger = 0; @@ -87,12 +90,12 @@ void Config::ReadValues() { // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, - sdl2_config->Get("Data Storage", "nand_directory", - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, - sdl2_config->Get("Data Storage", "sdmc_directory", - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + 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>( diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp index 8584f6671..78f75fb38 100644 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp @@ -13,7 +13,6 @@ #include <glad/glad.h> -#include "common/assert.h" #include "common/logging/log.h" #include "common/scm_rev.h" #include "core/settings.h" @@ -53,7 +52,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() { exit(1); } - InputCommon::Init(); + input_subsystem->Initialize(); SDL_SetMainReady(); @@ -105,7 +104,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() { } EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { - InputCommon::Shutdown(); + input_subsystem->Shutdown(); SDL_GL_DeleteContext(gl_context); SDL_Quit(); } diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h index c13a82df2..a553b4b95 100644 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h @@ -8,6 +8,10 @@ struct SDL_Window; +namespace InputCommon { +class InputSubsystem; +} + class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow { public: explicit EmuWindow_SDL2_Hide(); @@ -25,6 +29,8 @@ 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; diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp index 083667baf..5798ce43a 100644 --- a/src/yuzu_tester/yuzu.cpp +++ b/src/yuzu_tester/yuzu.cpp @@ -79,8 +79,8 @@ static void InitializeLogging(bool console) { if (console) Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); + 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>()); @@ -251,10 +251,10 @@ int main(int argc, char** argv) { Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback); - system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester"); + system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", + "SDLHideTester"); system.GPU().Start(); - system.Renderer().Rasterizer().LoadDiskResources(); system.Run(); while (!finished) { |