diff options
53 files changed, 1048 insertions, 180 deletions
| diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme index 94d5ae8aa..19dc0369a 100644 --- a/dist/qt_themes/colorful_dark/icons/index.theme +++ b/dist/qt_themes/colorful_dark/icons/index.theme @@ -1,7 +1,7 @@  [Icon Theme]  Name=colorful_dark  Comment=Colorful theme (Dark style) -Inherits=default +Inherits=colorful  Directories=16x16  [16x16] diff --git a/dist/qt_themes/colorful_midnight_blue/icons/index.theme b/dist/qt_themes/colorful_midnight_blue/icons/index.theme index e23bfe6f9..dcb2c50d6 100644 --- a/dist/qt_themes/colorful_midnight_blue/icons/index.theme +++ b/dist/qt_themes/colorful_midnight_blue/icons/index.theme @@ -1,7 +1,7 @@  [Icon Theme]  Name=colorful_midnight_blue  Comment=Colorful theme (Midnight Blue style) -Inherits=default +Inherits=colorful  Directories=16x16  [16x16] diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index 70e540b06..a64037455 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -1257,10 +1257,6 @@ QComboBox::item:alternate {    background: #19232D;  } -QComboBox::item:checked { -  font-weight: bold; -} -  QComboBox::item:selected {    border: 0px solid transparent;  } diff --git a/dist/yuzu.bmp b/dist/yuzu.bmpBinary files differ new file mode 100644 index 000000000..66f2f696f --- /dev/null +++ b/dist/yuzu.bmp diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index d1d177b51..a0ae07752 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -15,6 +15,8 @@ add_library(audio_core STATIC      command_generator.cpp      command_generator.h      common.h +    delay_line.cpp +    delay_line.h      effect_context.cpp      effect_context.h      info_updater.cpp diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 5b1065520..437cc5ccd 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -2,6 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <cmath> +#include <numbers>  #include "audio_core/algorithm/interpolate.h"  #include "audio_core/command_generator.h"  #include "audio_core/effect_context.h" @@ -13,6 +15,20 @@ namespace AudioCore {  namespace {  constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;  constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; +using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; + +constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; +constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; +constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; +constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; +constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ +    0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, +    0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, +    0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; +constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ +    0.67096f, 0.61027f, 1.0f,     0.35680f, 0.68361f, 0.65978f, 0.51939f, +    0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, +    0.72867f, 0.69794f, 0.5464f,  0.24563f, 0.45214f, 0.44042f};  template <std::size_t N>  void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { @@ -65,6 +81,154 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {      }  } +float Pow10(float x) { +    if (x >= 0.0f) { +        return 1.0f; +    } else if (x <= -5.3f) { +        return 0.0f; +    } +    return std::pow(10.0f, x); +} + +float SinD(float degrees) { +    return std::sin(degrees * std::numbers::pi_v<float> / 180.0f); +} + +float CosD(float degrees) { +    return std::cos(degrees * std::numbers::pi_v<float> / 180.0f); +} + +float ToFloat(s32 sample) { +    return static_cast<float>(sample) / 65536.f; +} + +s32 ToS32(float sample) { +    constexpr auto min = -8388608.0f; +    constexpr auto max = 8388607.f; +    float rescaled_sample = sample * 65536.0f; +    if (rescaled_sample < min) { +        rescaled_sample = min; +    } +    if (rescaled_sample > max) { +        rescaled_sample = max; +    } +    return static_cast<s32>(rescaled_sample); +} + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +                                                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, +                                                           1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, +                                                           1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, +                                                           1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +template <std::size_t CHANNEL_COUNT> +void ApplyReverbGeneric(I3dl2ReverbState& state, +                        const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input, +                        const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output, +                        s32 sample_count) { + +    auto GetTapLookup = []() { +        if constexpr (CHANNEL_COUNT == 1) { +            return REVERB_TAP_INDEX_1CH; +        } else if constexpr (CHANNEL_COUNT == 2) { +            return REVERB_TAP_INDEX_2CH; +        } else if constexpr (CHANNEL_COUNT == 4) { +            return REVERB_TAP_INDEX_4CH; +        } else if constexpr (CHANNEL_COUNT == 6) { +            return REVERB_TAP_INDEX_6CH; +        } +    }; + +    const auto& tap_index_lut = GetTapLookup(); +    for (s32 sample = 0; sample < sample_count; sample++) { +        std::array<f32, CHANNEL_COUNT> out_samples{}; +        std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; +        std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; +        std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; + +        // Mix everything into a single sample +        s32 temp_mixed_sample = 0; +        for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { +            temp_mixed_sample += input[i][sample]; +        } +        const auto current_sample = ToFloat(temp_mixed_sample); +        const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); + +        for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { +            const auto tapped_samp = +                state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; +            out_samples[tap_index_lut[i]] += tapped_samp; + +            if constexpr (CHANNEL_COUNT == 6) { +                // handle lfe +                out_samples[5] += tapped_samp; +            } +        } + +        state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; +        state.early_delay_line.Tick(state.lowpass_0); + +        for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { +            out_samples[i] *= state.early_gain; +        } + +        // Two channel seems to apply a latet gain, we require to save this +        f32 filter{}; +        for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +            filter = state.fdn_delay_line[i].GetOutputSample(); +            const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; +            state.shelf_filter[i] = +                filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; +            fsamp[i] = computed; +        } + +        // Mixing matrix +        mixed[0] = fsamp[1] + fsamp[2]; +        mixed[1] = -fsamp[0] - fsamp[3]; +        mixed[2] = fsamp[0] - fsamp[3]; +        mixed[3] = fsamp[1] - fsamp[2]; + +        if constexpr (CHANNEL_COUNT == 2) { +            for (auto& mix : mixed) { +                mix *= (filter * state.late_gain); +            } +        } + +        for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +            const auto late = early_tap * state.late_gain; +            osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); +            osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); +            state.fdn_delay_line[i].Tick(osamp[i]); +        } + +        if constexpr (CHANNEL_COUNT == 1) { +            output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + +                                      (out_samples[0] + osamp[0] + osamp[1])); +        } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { +            for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { +                output[i][sample] = +                    ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); +            } +        } else if constexpr (CHANNEL_COUNT == 6) { +            const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); +            for (std::size_t i = 0; i < 4; i++) { +                output[i][sample] = +                    ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); +            } +            output[4][sample] = +                ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); +            output[5][sample] = +                ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); +        } +    } +} +  } // namespace  CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, @@ -271,11 +435,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic          }          // 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); +        // 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);      }  } @@ -376,21 +539,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {  void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,                                                          bool enabled) { -    if (!enabled) { +    auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); +    const auto& params = reverb->GetParams(); +    auto& state = reverb->GetState(); +    const auto channel_count = params.channel_count; + +    if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {          return;      } -    const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams(); -    const auto channel_count = params.channel_count; + +    std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{}; +    std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{}; + +    const auto status = params.status;      for (s32 i = 0; i < channel_count; i++) { -        // 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); -        }*/ -        auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); -        std::memset(output, 0, worker_params.sample_count * sizeof(s32)); +        input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); +        output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); +    } + +    if (enabled) { +        if (status == ParameterStatus::Initialized) { +            InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); +        } else if (status == ParameterStatus::Updating) { +            UpdateI3dl2Reverb(reverb->GetParams(), state, false); +        } +    } + +    if (enabled) { +        switch (channel_count) { +        case 1: +            ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); +            break; +        case 2: +            ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); +            break; +        case 4: +            ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); +            break; +        case 6: +            ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); +            break; +        } +    } else { +        for (s32 i = 0; i < channel_count; i++) { +            // Only copy if the buffer input and output do not match! +            if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { +                std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); +            } +        }      }  } @@ -528,6 +724,133 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3      return sample_count;  } +void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, +                                             std::vector<u8>& work_buffer) { +    // Reset state +    state.lowpass_0 = 0.0f; +    state.lowpass_1 = 0.0f; +    state.lowpass_2 = 0.0f; + +    state.early_delay_line.Reset(); +    state.early_tap_steps.fill(0); +    state.early_gain = 0.0f; +    state.late_gain = 0.0f; +    state.early_to_late_taps = 0; +    for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +        state.fdn_delay_line[i].Reset(); +        state.decay_delay_line0[i].Reset(); +        state.decay_delay_line1[i].Reset(); +    } +    state.last_reverb_echo = 0.0f; +    state.center_delay_line.Reset(); +    for (auto& coef : state.lpf_coefficients) { +        coef.fill(0.0f); +    } +    state.shelf_filter.fill(0.0f); +    state.dry_gain = 0.0f; + +    const auto sample_rate = info.sample_rate / 1000; +    f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); + +    s32 delay_samples{}; +    for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +        delay_samples = +            AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); +        state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); +        work_buffer_ptr += delay_samples + 1; + +        delay_samples = +            AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); +        state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); +        work_buffer_ptr += delay_samples + 1; + +        delay_samples = +            AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); +        state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); +        work_buffer_ptr += delay_samples + 1; +    } +    delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); +    state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); +    work_buffer_ptr += delay_samples + 1; + +    delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); +    state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); + +    UpdateI3dl2Reverb(info, state, true); +} + +void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, +                                         bool should_clear) { + +    state.dry_gain = info.dry_gain; +    state.shelf_filter.fill(0.0f); +    state.lowpass_0 = 0.0f; +    state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); +    state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); + +    const auto sample_rate = info.sample_rate / 1000; +    const f32 hf_gain = Pow10(info.room_hf / 2000.0f); +    if (hf_gain >= 1.0f) { +        state.lowpass_2 = 1.0f; +        state.lowpass_1 = 0.0f; +    } else { +        const auto a = 1.0f - hf_gain; +        const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / +                                                     static_cast<f32>(info.sample_rate))); +        const auto c = std::sqrt(b * b - 4.0f * a * a); + +        state.lowpass_1 = (b - c) / (2.0f * a); +        state.lowpass_2 = 1.0f - state.lowpass_1; +    } +    state.early_to_late_taps = AudioCommon::CalculateDelaySamples( +        sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); + +    state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; +    for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +        const auto length = +            FDN_MIN_DELAY_LINE_TIMES[i] + +            (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); +        state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); + +        const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + +                                         state.decay_delay_line0[i].GetDelay() + +                                         state.decay_delay_line1[i].GetDelay(); + +        float a = (-60.0f * static_cast<f32>(delay_sample_counts)) / +                  (info.decay_time * static_cast<f32>(info.sample_rate)); +        float b = a / info.hf_decay_ratio; +        float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) / +                  SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)); +        float d = Pow10((b - a) / 40.0f); +        float e = Pow10((b + a) / 40.0f) * 0.7071f; + +        state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); +        state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); +        state.lpf_coefficients[2][i] = (c - d) / (c + d); + +        state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); +        state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); +    } + +    if (should_clear) { +        for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { +            state.fdn_delay_line[i].Clear(); +            state.decay_delay_line0[i].Clear(); +            state.decay_delay_line1[i].Clear(); +        } +        state.early_delay_line.Clear(); +        state.center_delay_line.Clear(); +    } + +    const auto max_early_delay = state.early_delay_line.GetMaxDelay(); +    const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); +    for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { +        const auto length = AudioCommon::CalculateDelaySamples( +            sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); +        state.early_tap_steps[tap] = std::min(length, max_early_delay); +    } +} +  void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,                                                   s32 channel, s32 node_id) {      const auto last = static_cast<s32>(last_volume * 32768.0f); diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index b937350b1..2ebb755b0 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -21,6 +21,8 @@ class ServerMixInfo;  class EffectContext;  class EffectBase;  struct AuxInfoDSP; +struct I3dl2ReverbParams; +struct I3dl2ReverbState;  using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;  class CommandGenerator { @@ -80,6 +82,9 @@ private:      s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,                        u32 sample_count, u32 read_offset, u32 read_count); +    void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, +                               std::vector<u8>& work_buffer); +    void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);      // DSP Code      s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,                      s32 channel, std::size_t mix_offset); diff --git a/src/audio_core/common.h b/src/audio_core/common.h index ec59a3ba9..fe546c55d 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -33,6 +33,29 @@ constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this  // and our const ends up being 0x3f04, the 4 bytes are most  // likely the sample history  constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; +constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; +constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; +constexpr std::size_t I3DL2REVERB_TAPS = 20; +constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; +using Fractional = s32; + +template <typename T> +constexpr Fractional ToFractional(T x) { +    return static_cast<Fractional>(x * static_cast<T>(0x4000)); +} + +constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { +    return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); +} + +constexpr s32 FractionalToFixed(Fractional x) { +    const auto s = x & (1 << 13); +    return static_cast<s32>(x >> 14) + s; +} + +constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { +    return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); +}  static constexpr u32 VersionFromRevision(u32_le rev) {      // "REV7" -> 7 diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp new file mode 100644 index 000000000..f4e4dd8d2 --- /dev/null +++ b/src/audio_core/delay_line.cpp @@ -0,0 +1,104 @@ +#include <cstring> +#include "audio_core/delay_line.h" + +namespace AudioCore { +DelayLineBase::DelayLineBase() = default; +DelayLineBase::~DelayLineBase() = default; + +void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { +    buffer = src_buffer; +    buffer_end = buffer + max_delay_; +    max_delay = max_delay_; +    output = buffer; +    SetDelay(max_delay_); +    Clear(); +} + +void DelayLineBase::SetDelay(s32 new_delay) { +    if (max_delay < new_delay) { +        return; +    } +    delay = new_delay; +    input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); +} + +s32 DelayLineBase::GetDelay() const { +    return delay; +} + +s32 DelayLineBase::GetMaxDelay() const { +    return max_delay; +} + +f32 DelayLineBase::TapOut(s32 last_sample) { +    const float* ptr = input - (last_sample + 1); +    if (ptr < buffer) { +        ptr += (max_delay + 1); +    } + +    return *ptr; +} + +f32 DelayLineBase::Tick(f32 sample) { +    *(input++) = sample; +    const auto out_sample = *(output++); + +    if (buffer_end < input) { +        input = buffer; +    } + +    if (buffer_end < output) { +        output = buffer; +    } + +    return out_sample; +} + +float* DelayLineBase::GetInput() { +    return input; +} + +const float* DelayLineBase::GetInput() const { +    return input; +} + +f32 DelayLineBase::GetOutputSample() const { +    return *output; +} + +void DelayLineBase::Clear() { +    std::memset(buffer, 0, sizeof(float) * max_delay); +} + +void DelayLineBase::Reset() { +    buffer = nullptr; +    buffer_end = nullptr; +    max_delay = 0; +    input = nullptr; +    output = nullptr; +    delay = 0; +} + +DelayLineAllPass::DelayLineAllPass() = default; +DelayLineAllPass::~DelayLineAllPass() = default; + +void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { +    DelayLineBase::Initialize(delay_, src_buffer); +    SetCoefficient(coeffcient_); +} + +void DelayLineAllPass::SetCoefficient(float coeffcient_) { +    coefficient = coeffcient_; +} + +f32 DelayLineAllPass::Tick(f32 sample) { +    const auto temp = sample - coefficient * *output; +    return coefficient * temp + DelayLineBase::Tick(temp); +} + +void DelayLineAllPass::Reset() { +    coefficient = 0.0f; +    DelayLineBase::Reset(); +} + +} // namespace AudioCore diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h new file mode 100644 index 000000000..cafddd432 --- /dev/null +++ b/src/audio_core/delay_line.h @@ -0,0 +1,46 @@ +#pragma once + +#include "common/common_types.h" + +namespace AudioCore { + +class DelayLineBase { +public: +    DelayLineBase(); +    ~DelayLineBase(); + +    void Initialize(s32 max_delay_, float* src_buffer); +    void SetDelay(s32 new_delay); +    s32 GetDelay() const; +    s32 GetMaxDelay() const; +    f32 TapOut(s32 last_sample); +    f32 Tick(f32 sample); +    float* GetInput(); +    const float* GetInput() const; +    f32 GetOutputSample() const; +    void Clear(); +    void Reset(); + +protected: +    float* buffer{nullptr}; +    float* buffer_end{nullptr}; +    s32 max_delay{}; +    float* input{nullptr}; +    float* output{nullptr}; +    s32 delay{}; +}; + +class DelayLineAllPass final : public DelayLineBase { +public: +    DelayLineAllPass(); +    ~DelayLineAllPass(); + +    void Initialize(u32 delay, float coeffcient_, f32* src_buffer); +    void SetCoefficient(float coeffcient_); +    f32 Tick(f32 sample); +    void Reset(); + +private: +    float coefficient{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index f770b9608..89e4573c7 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -90,6 +90,14 @@ s32 EffectBase::GetProcessingOrder() const {      return processing_order;  } +std::vector<u8>& EffectBase::GetWorkBuffer() { +    return work_buffer; +} + +const std::vector<u8>& EffectBase::GetWorkBuffer() const { +    return work_buffer; +} +  EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}  EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; @@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {          usage = UsageState::Initialized;          params.status = ParameterStatus::Initialized;          skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; +        if (!skipped) { +            auto& cur_work_buffer = GetWorkBuffer(); +            // Has two buffers internally +            cur_work_buffer.resize(in_params.buffer_size * 2); +            std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); +        }      }  } @@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() {      GetParams().status = ParameterStatus::Updated;  } +I3dl2ReverbState& EffectI3dl2Reverb::GetState() { +    return state; +} + +const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { +    return state; +} +  EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}  EffectBiquadFilter::~EffectBiquadFilter() = default; diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h index c5e0b398c..5e0655dd7 100644 --- a/src/audio_core/effect_context.h +++ b/src/audio_core/effect_context.h @@ -8,6 +8,7 @@  #include <memory>  #include <vector>  #include "audio_core/common.h" +#include "audio_core/delay_line.h"  #include "common/common_funcs.h"  #include "common/common_types.h"  #include "common/swap.h" @@ -194,6 +195,8 @@ public:      [[nodiscard]] bool IsEnabled() const;      [[nodiscard]] s32 GetMixID() const;      [[nodiscard]] s32 GetProcessingOrder() const; +    [[nodiscard]] std::vector<u8>& GetWorkBuffer(); +    [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;  protected:      UsageState usage{UsageState::Invalid}; @@ -201,6 +204,7 @@ protected:      s32 mix_id{};      s32 processing_order{};      bool enabled = false; +    std::vector<u8> work_buffer{};  };  template <typename T> @@ -212,7 +216,7 @@ public:          return internal_params;      } -    const I3dl2ReverbParams& GetParams() const { +    const T& GetParams() const {          return internal_params;      } @@ -229,6 +233,27 @@ public:      void UpdateForCommandGeneration() override;  }; +struct I3dl2ReverbState { +    f32 lowpass_0{}; +    f32 lowpass_1{}; +    f32 lowpass_2{}; + +    DelayLineBase early_delay_line{}; +    std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; +    f32 early_gain{}; +    f32 late_gain{}; + +    u32 early_to_late_taps{}; +    std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; +    std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; +    std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; +    f32 last_reverb_echo{}; +    DelayLineBase center_delay_line{}; +    std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; +    std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; +    f32 dry_gain{}; +}; +  class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {  public:      explicit EffectI3dl2Reverb(); @@ -237,8 +262,12 @@ public:      void Update(EffectInfo::InParams& in_params) override;      void UpdateForCommandGeneration() override; +    I3dl2ReverbState& GetState(); +    const I3dl2ReverbState& GetState() const; +  private:      bool skipped = false; +    I3dl2ReverbState state{};  };  class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1662ec63d..28196d26a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -266,6 +266,7 @@ add_library(core STATIC      hle/service/am/applets/software_keyboard.h      hle/service/am/applets/web_browser.cpp      hle/service/am/applets/web_browser.h +    hle/service/am/applets/web_types.h      hle/service/am/idle.cpp      hle/service/am/idle.h      hle/service/am/omm.cpp @@ -400,6 +401,7 @@ add_library(core STATIC      hle/service/hid/controllers/xpad.h      hle/service/lbl/lbl.cpp      hle/service/lbl/lbl.h +    hle/service/ldn/errors.h      hle/service/ldn/ldn.cpp      hle/service/ldn/ldn.h      hle/service/ldr/ldr.cpp diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h index dff71d8d9..b0626a0f9 100644 --- a/src/core/frontend/applets/controller.h +++ b/src/core/frontend/applets/controller.h @@ -31,6 +31,7 @@ struct ControllerParameters {      bool allow_dual_joycons{};      bool allow_left_joycon{};      bool allow_right_joycon{}; +    bool allow_gamecube_controller{};  };  class ControllerApplet { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index bb77c2569..8e1fe9438 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1047,20 +1047,21 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {      const u64 offset{rp.Pop<u64>()};      const std::vector<u8> data{ctx.ReadBuffer()}; +    const std::size_t size{std::min(data.size(), backing.GetSize() - offset)}; -    LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); +    LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); -    if (data.size() > backing.GetSize() - offset) { +    if (offset > backing.GetSize()) {          LOG_ERROR(Service_AM,                    "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", -                  backing.GetSize(), data.size(), offset); +                  backing.GetSize(), size, offset);          IPC::ResponseBuilder rb{ctx, 2};          rb.Push(ERR_SIZE_OUT_OF_BOUNDS);          return;      } -    std::memcpy(backing.GetData().data() + offset, data.data(), data.size()); +    std::memcpy(backing.GetData().data() + offset, data.data(), size);      IPC::ResponseBuilder rb{ctx, 2};      rb.Push(RESULT_SUCCESS); @@ -1070,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      const u64 offset{rp.Pop<u64>()}; -    const std::size_t size{ctx.GetWriteBufferSize()}; +    const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)};      LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); -    if (size > backing.GetSize() - offset) { +    if (offset > backing.GetSize()) {          LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",                    backing.GetSize(), size, offset); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index dbf198345..70b9f3824 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -21,6 +21,7 @@  namespace Service::HID {  constexpr s32 HID_JOYSTICK_MAX = 0x7fff; +constexpr s32 HID_TRIGGER_MAX = 0x7fff;  [[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;  constexpr std::size_t NPAD_OFFSET = 0x9A00;  constexpr u32 BATTERY_FULL = 2; @@ -48,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(          return NPadControllerType::JoyRight;      case Settings::ControllerType::Handheld:          return NPadControllerType::Handheld; +    case Settings::ControllerType::GameCube: +        return NPadControllerType::GameCube;      default:          UNREACHABLE();          return NPadControllerType::ProController; @@ -67,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType(          return Settings::ControllerType::RightJoycon;      case NPadControllerType::Handheld:          return Settings::ControllerType::Handheld; +    case NPadControllerType::GameCube: +        return Settings::ControllerType::GameCube;      default:          UNREACHABLE();          return Settings::ControllerType::ProController; @@ -209,6 +214,13 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {          controller.assignment_mode = NpadAssignments::Single;          controller.footer_type = AppletFooterUiType::JoyRightHorizontal;          break; +    case NPadControllerType::GameCube: +        controller.style_set.gamecube.Assign(1); +        // The GC Controller behaves like a wired Pro Controller +        controller.device_type.fullkey.Assign(1); +        controller.system_properties.is_vertical.Assign(1); +        controller.system_properties.use_plus.Assign(1); +        break;      case NPadControllerType::Pokeball:          controller.style_set.palma.Assign(1);          controller.device_type.palma.Assign(1); @@ -259,6 +271,7 @@ void Controller_NPad::OnInit() {          style.joycon_right.Assign(1);          style.joycon_dual.Assign(1);          style.fullkey.Assign(1); +        style.gamecube.Assign(1);          style.palma.Assign(1);      } @@ -339,6 +352,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {      auto& pad_state = npad_pad_states[controller_idx].pad_states;      auto& lstick_entry = npad_pad_states[controller_idx].l_stick;      auto& rstick_entry = npad_pad_states[controller_idx].r_stick; +    auto& trigger_entry = npad_trigger_states[controller_idx];      const auto& button_state = buttons[controller_idx];      const auto& analog_state = sticks[controller_idx];      const auto [stick_l_x_f, stick_l_y_f] = @@ -404,6 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {          pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());          pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());      } + +    if (controller_type == NPadControllerType::GameCube) { +        trigger_entry.l_analog = static_cast<s32>( +            button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); +        trigger_entry.r_analog = static_cast<s32>( +            button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); +        pad_state.zl.Assign(false); +        pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); +        pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); +        pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); +    }  }  void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, @@ -418,6 +443,11 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*              &npad.joy_left_states,  &npad.joy_right_states, &npad.palma_states,              &npad.system_ext_states}; +        // There is the posibility to have more controllers with analog triggers +        const std::array<TriggerGeneric*, 1> controller_triggers{ +            &npad.gc_trigger_states, +        }; +          for (auto* main_controller : controller_npads) {              main_controller->common.entry_count = 16;              main_controller->common.total_entry_count = 17; @@ -435,6 +465,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*              cur_entry.timestamp2 = cur_entry.timestamp;          } +        for (auto* analog_trigger : controller_triggers) { +            analog_trigger->entry_count = 16; +            analog_trigger->total_entry_count = 17; + +            const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; + +            analog_trigger->timestamp = core_timing.GetCPUTicks(); +            analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17; + +            auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; + +            cur_entry.timestamp = last_entry.timestamp + 1; +            cur_entry.timestamp2 = cur_entry.timestamp; +        } +          const auto& controller_type = connected_controllers[i].type;          if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { @@ -444,6 +489,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*          RequestPadStateUpdate(npad_index);          auto& pad_state = npad_pad_states[npad_index]; +        auto& trigger_state = npad_trigger_states[npad_index];          auto& main_controller =              npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index]; @@ -456,6 +502,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*          auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index];          auto& libnx_entry =              npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; +        auto& trigger_entry = +            npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index];          libnx_entry.connection_status.raw = 0;          libnx_entry.connection_status.is_connected.Assign(1); @@ -524,6 +572,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*              libnx_entry.connection_status.is_right_connected.Assign(1);              break; +        case NPadControllerType::GameCube: +            main_controller.connection_status.raw = 0; +            main_controller.connection_status.is_connected.Assign(1); +            main_controller.connection_status.is_wired.Assign(1); +            main_controller.pad.pad_states.raw = pad_state.pad_states.raw; +            main_controller.pad.l_stick = pad_state.l_stick; +            main_controller.pad.r_stick = pad_state.r_stick; +            trigger_entry.l_analog = trigger_state.l_analog; +            trigger_entry.r_analog = trigger_state.r_analog; + +            libnx_entry.connection_status.is_wired.Assign(1); +            break;          case NPadControllerType::Pokeball:              pokeball_entry.connection_status.raw = 0;              pokeball_entry.connection_status.is_connected.Assign(1); @@ -674,6 +734,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing                  right_sixaxis_entry.orientation = motion_devices[1].orientation;              }              break; +        case NPadControllerType::GameCube:          case NPadControllerType::Pokeball:              break;          } @@ -1135,6 +1196,8 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const              return style.joycon_left;          case NPadControllerType::JoyRight:              return style.joycon_right; +        case NPadControllerType::GameCube: +            return style.gamecube;          case NPadControllerType::Pokeball:              return style.palma;          default: diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 48bab988c..bc2e6779d 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -51,6 +51,7 @@ public:          JoyDual,          JoyLeft,          JoyRight, +        GameCube,          Pokeball,      }; @@ -60,6 +61,7 @@ public:          JoyconDual = 5,          JoyconLeft = 6,          JoyconRight = 7, +        GameCube = 8,          Pokeball = 9,          MaxNpadType = 10,      }; @@ -389,6 +391,25 @@ private:      };      static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); +    struct TriggerState { +        s64_le timestamp{}; +        s64_le timestamp2{}; +        s32_le l_analog{}; +        s32_le r_analog{}; +    }; +    static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size"); + +    struct TriggerGeneric { +        INSERT_PADDING_BYTES(0x4); +        s64_le timestamp; +        INSERT_PADDING_BYTES(0x4); +        s64_le total_entry_count; +        s64_le last_entry_index; +        s64_le entry_count; +        std::array<TriggerState, 17> trigger{}; +    }; +    static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size"); +      struct NPadSystemProperties {          union {              s64_le raw{}; @@ -509,7 +530,9 @@ private:          AppletFooterUiType footer_type;          // nfc_states needs to be checked switchbrew does not match with HW          NfcXcdHandle nfc_states; -        INSERT_PADDING_BYTES(0xdef); +        INSERT_PADDING_BYTES(0x8); // Mutex +        TriggerGeneric gc_trigger_states; +        INSERT_PADDING_BYTES(0xc1f);      };      static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); @@ -560,6 +583,7 @@ private:      f32 sixaxis_fusion_parameter2{};      bool sixaxis_at_rest{true};      std::array<ControllerPad, 10> npad_pad_states{}; +    std::array<TriggerState, 10> npad_trigger_states{};      bool is_in_lr_assignment_mode{false};      Core::System& system;  }; diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h new file mode 100644 index 000000000..a718c5c66 --- /dev/null +++ b/src/core/hle/service/ldn/errors.h @@ -0,0 +1,13 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::LDN { + +constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22}; + +} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index ee908f399..c630d93cd 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -6,6 +6,7 @@  #include "core/hle/ipc_helpers.h"  #include "core/hle/result.h" +#include "core/hle/service/ldn/errors.h"  #include "core/hle/service/ldn/ldn.h"  #include "core/hle/service/sm/sm.h" @@ -103,7 +104,7 @@ public:          : ServiceFramework{system_, "IUserLocalCommunicationService"} {          // clang-format off          static const FunctionInfo functions[] = { -            {0, nullptr, "GetState"}, +            {0, &IUserLocalCommunicationService::GetState, "GetState"},              {1, nullptr, "GetNetworkInfo"},              {2, nullptr, "GetIpv4Address"},              {3, nullptr, "GetDisconnectReason"}, @@ -138,13 +139,38 @@ public:          RegisterHandlers(functions);      } -    void Initialize2(Kernel::HLERequestContext& ctx) { +    void GetState(Kernel::HLERequestContext& ctx) {          LOG_WARNING(Service_LDN, "(STUBBED) called"); -        // Result success seem make this services start network and continue. -        // If we just pass result error then it will stop and maybe try again and again. + +        IPC::ResponseBuilder rb{ctx, 3}; + +        // Indicate a network error, as we do not actually emulate LDN +        rb.Push(static_cast<u32>(State::Error)); + +        rb.Push(RESULT_SUCCESS); +    } + +    void Initialize2(Kernel::HLERequestContext& ctx) { +        LOG_DEBUG(Service_LDN, "called"); + +        is_initialized = true; +          IPC::ResponseBuilder rb{ctx, 2}; -        rb.Push(RESULT_UNKNOWN); +        rb.Push(RESULT_SUCCESS);      } + +private: +    enum class State { +        None, +        Initialized, +        AccessPointOpened, +        AccessPointCreated, +        StationOpened, +        StationConnected, +        Error, +    }; + +    bool is_initialized{};  };  class LDNS final : public ServiceFramework<LDNS> { diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index f67de37e3..a88ae452f 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -717,6 +717,13 @@ SDLState::SDLState() {      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());      } +// these hints are only defined on sdl2.0.9 or higher +#if SDL_VERSION_ATLEAST(2, 0, 9) +#if !SDL_VERSION_ATLEAST(2, 0, 12) +    // There are also hints to toggle the individual drivers if needed. +    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0"); +#endif +#endif      SDL_AddEventWatch(&SDLEventWatcher, this); diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 75486554b..a59f5d461 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -340,6 +340,7 @@ enum class ControllerType {      LeftJoycon,      RightJoycon,      Handheld, +    GameCube,  };  struct PlayerInput { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 31eb54123..12434db67 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -763,6 +763,37 @@ void Image::DownloadMemory(ImageBufferMap& map,      }  } +GLuint Image::StorageHandle() noexcept { +    switch (info.format) { +    case PixelFormat::A8B8G8R8_SRGB: +    case PixelFormat::B8G8R8A8_SRGB: +    case PixelFormat::BC1_RGBA_SRGB: +    case PixelFormat::BC2_SRGB: +    case PixelFormat::BC3_SRGB: +    case PixelFormat::BC7_SRGB: +    case PixelFormat::ASTC_2D_4X4_SRGB: +    case PixelFormat::ASTC_2D_8X8_SRGB: +    case PixelFormat::ASTC_2D_8X5_SRGB: +    case PixelFormat::ASTC_2D_5X4_SRGB: +    case PixelFormat::ASTC_2D_5X5_SRGB: +    case PixelFormat::ASTC_2D_10X8_SRGB: +    case PixelFormat::ASTC_2D_6X6_SRGB: +    case PixelFormat::ASTC_2D_10X10_SRGB: +    case PixelFormat::ASTC_2D_12X12_SRGB: +    case PixelFormat::ASTC_2D_8X6_SRGB: +    case PixelFormat::ASTC_2D_6X5_SRGB: +        if (store_view.handle != 0) { +            return store_view.handle; +        } +        store_view.Create(); +        glTextureView(store_view.handle, ImageTarget(info), texture.handle, GL_RGBA8, 0, +                      info.resources.levels, 0, info.resources.layers); +        return store_view.handle; +    default: +        return texture.handle; +    } +} +  void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {      // Compressed formats don't have a pixel format or type      const bool is_compressed = gl_format == GL_NONE; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 874cf54f4..a6172f009 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -145,6 +145,8 @@ public:      void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); +    GLuint StorageHandle() noexcept; +      GLuint Handle() const noexcept {          return texture.handle;      } @@ -155,8 +157,8 @@ private:      void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset);      OGLTexture texture; -    OGLTextureView store_view;      OGLBuffer buffer; +    OGLTextureView store_view;      GLenum gl_internal_format = GL_NONE;      GLenum gl_format = GL_NONE;      GLenum gl_type = GL_NONE; diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 1b58e8617..31ec68505 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -93,7 +93,7 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map,          glUniform1ui(7, params.block_height_mask);          glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,                            image.guest_size_bytes - swizzle.buffer_offset); -        glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, +        glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,                             GL_WRITE_ONLY, store_format);          glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers);      } @@ -134,7 +134,7 @@ void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map,          glUniform1ui(9, params.block_depth_mask);          glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,                            image.guest_size_bytes - swizzle.buffer_offset); -        glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, +        glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,                             GL_WRITE_ONLY, store_format);          glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z);      } @@ -164,7 +164,8 @@ void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map,      glUniform2i(LOC_DESTINATION, 0, 0);      glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block);      glUniform1ui(LOC_PITCH, pitch); -    glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, format); +    glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, +                       format);      for (const SwizzleParameters& swizzle : swizzles) {          const Extent3D num_tiles = swizzle.num_tiles;          const size_t input_offset = swizzle.buffer_offset + map.offset; @@ -195,9 +196,9 @@ void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const Im          glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z);          glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z); -        glBindImageTexture(BINDING_INPUT_IMAGE, src_image.Handle(), copy.src_subresource.base_level, -                           GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); -        glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.Handle(), +        glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(), +                           copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); +        glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(),                             copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);          glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth);      } diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 5be6dabd9..362278f01 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -12,14 +12,15 @@  #include "common/cityhash.h"  #include "common/common_types.h"  #include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/vk_state_tracker.h"  namespace Vulkan {  namespace { -constexpr std::size_t POINT = 0; -constexpr std::size_t LINE = 1; -constexpr std::size_t POLYGON = 2; +constexpr size_t POINT = 0; +constexpr size_t LINE = 1; +constexpr size_t POLYGON = 2;  constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {      POINT,   // Points      LINE,    // Lines @@ -40,10 +41,14 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {  } // Anonymous namespace -void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { -    const std::array enabled_lut = {regs.polygon_offset_point_enable, -                                    regs.polygon_offset_line_enable, -                                    regs.polygon_offset_fill_enable}; +void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, +                                 bool has_extended_dynamic_state) { +    const Maxwell& regs = maxwell3d.regs; +    const std::array enabled_lut{ +        regs.polygon_offset_point_enable, +        regs.polygon_offset_line_enable, +        regs.polygon_offset_fill_enable, +    };      const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());      raw1 = 0; @@ -64,45 +69,53 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta      raw2 = 0;      const auto test_func = -        regs.alpha_test_enabled == 1 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; +        regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always;      alpha_test_func.Assign(PackComparisonOp(test_func));      early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0);      alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref);      point_size = Common::BitCast<u32>(regs.point_size); -    for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { -        binding_divisors[index] = -            regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0; +    if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) { +        maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false; +        for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { +            const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index); +            binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0; +        }      } - -    for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { -        const auto& input = regs.vertex_attrib_format[index]; -        auto& attribute = attributes[index]; -        attribute.raw = 0; -        attribute.enabled.Assign(input.IsConstant() ? 0 : 1); -        attribute.buffer.Assign(input.buffer); -        attribute.offset.Assign(input.offset); -        attribute.type.Assign(static_cast<u32>(input.type.Value())); -        attribute.size.Assign(static_cast<u32>(input.size.Value())); -        attribute.binding_index_enabled.Assign(regs.vertex_array[index].IsEnabled() ? 1 : 0); +    if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) { +        maxwell3d.dirty.flags[Dirty::VertexAttributes] = false; +        for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { +            const auto& input = regs.vertex_attrib_format[index]; +            auto& attribute = attributes[index]; +            attribute.raw = 0; +            attribute.enabled.Assign(input.IsConstant() ? 0 : 1); +            attribute.buffer.Assign(input.buffer); +            attribute.offset.Assign(input.offset); +            attribute.type.Assign(static_cast<u32>(input.type.Value())); +            attribute.size.Assign(static_cast<u32>(input.size.Value())); +        }      } - -    for (std::size_t index = 0; index < std::size(attachments); ++index) { -        attachments[index].Fill(regs, index); +    if (maxwell3d.dirty.flags[Dirty::Blending]) { +        maxwell3d.dirty.flags[Dirty::Blending] = false; +        for (size_t index = 0; index < attachments.size(); ++index) { +            attachments[index].Refresh(regs, index); +        } +    } +    if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) { +        maxwell3d.dirty.flags[Dirty::ViewportSwizzles] = false; +        const auto& transform = regs.viewport_transform; +        std::ranges::transform(transform, viewport_swizzles.begin(), [](const auto& viewport) { +            return static_cast<u16>(viewport.swizzle.raw); +        });      } - -    const auto& transform = regs.viewport_transform; -    std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(), -                   [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); }); -      if (!has_extended_dynamic_state) {          no_extended_dynamic_state.Assign(1); -        dynamic_state.Fill(regs); +        dynamic_state.Refresh(regs);      }  } -void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { +void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) {      const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];      raw = 0; @@ -141,7 +154,7 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size      enable.Assign(1);  } -void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { +void FixedPipelineState::DynamicState::Refresh(const Maxwell& regs) {      u32 packed_front_face = PackFrontFace(regs.front_face);      if (regs.screen_y_control.triangle_rast_flip != 0) {          // Flip front face @@ -178,9 +191,9 @@ void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {      });  } -std::size_t FixedPipelineState::Hash() const noexcept { +size_t FixedPipelineState::Hash() const noexcept {      const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); -    return static_cast<std::size_t>(hash); +    return static_cast<size_t>(hash);  }  bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 465a55fdb..a0eb83a68 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -58,7 +58,7 @@ struct FixedPipelineState {              BitField<30, 1, u32> enable;          }; -        void Fill(const Maxwell& regs, std::size_t index); +        void Refresh(const Maxwell& regs, size_t index);          constexpr std::array<bool, 4> Mask() const noexcept {              return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; @@ -96,8 +96,6 @@ struct FixedPipelineState {          BitField<6, 14, u32> offset;          BitField<20, 3, u32> type;          BitField<23, 6, u32> size; -        // Not really an element of a vertex attribute, but it can be packed here -        BitField<29, 1, u32> binding_index_enabled;          constexpr Maxwell::VertexAttribute::Type Type() const noexcept {              return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); @@ -108,7 +106,7 @@ struct FixedPipelineState {          }      }; -    template <std::size_t Position> +    template <size_t Position>      union StencilFace {          BitField<Position + 0, 3, u32> action_stencil_fail;          BitField<Position + 3, 3, u32> action_depth_fail; @@ -152,7 +150,7 @@ struct FixedPipelineState {          // Vertex stride is a 12 bits value, we have 4 bits to spare per element          std::array<u16, Maxwell::NumVertexArrays> vertex_strides; -        void Fill(const Maxwell& regs); +        void Refresh(const Maxwell& regs);          Maxwell::ComparisonOp DepthTestFunc() const noexcept {              return UnpackComparisonOp(depth_test_func); @@ -199,9 +197,9 @@ struct FixedPipelineState {      std::array<u16, Maxwell::NumViewports> viewport_swizzles;      DynamicState dynamic_state; -    void Fill(const Maxwell& regs, bool has_extended_dynamic_state); +    void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state); -    std::size_t Hash() const noexcept; +    size_t Hash() const noexcept;      bool operator==(const FixedPipelineState& rhs) const noexcept; @@ -209,8 +207,8 @@ struct FixedPipelineState {          return !operator==(rhs);      } -    std::size_t Size() const noexcept { -        const std::size_t total_size = sizeof *this; +    size_t Size() const noexcept { +        const size_t total_size = sizeof *this;          return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));      }  }; @@ -224,7 +222,7 @@ namespace std {  template <>  struct hash<Vulkan::FixedPipelineState> { -    std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { +    size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {          return k.Hash();      }  }; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index d50dca604..fc6dd83eb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -221,9 +221,6 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const SPIRVProgram& program,      std::vector<VkVertexInputBindingDescription> vertex_bindings;      std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;      for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { -        if (state.attributes[index].binding_index_enabled == 0) { -            continue; -        }          const bool instanced = state.binding_divisors[index] != 0;          const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;          vertex_bindings.push_back({ diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 684d4e3a6..dfd38f575 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -267,8 +267,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {      query_cache.UpdateCounters(); -    GraphicsPipelineCacheKey key; -    key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported()); +    graphics_key.fixed_state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported());      std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; @@ -276,14 +275,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {      texture_cache.UpdateRenderTargets(false);      const auto shaders = pipeline_cache.GetShaders(); -    key.shaders = GetShaderAddresses(shaders); +    graphics_key.shaders = GetShaderAddresses(shaders); +      SetupShaderDescriptors(shaders, is_indexed);      const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); -    key.renderpass = framebuffer->RenderPass(); +    graphics_key.renderpass = framebuffer->RenderPass(); -    auto* const pipeline = -        pipeline_cache.GetGraphicsPipeline(key, framebuffer->NumColorBuffers(), async_shaders); +    VKGraphicsPipeline* const pipeline = pipeline_cache.GetGraphicsPipeline( +        graphics_key, framebuffer->NumColorBuffers(), async_shaders);      if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {          // Async graphics pipeline was not ready.          return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 7fc6741da..acea1ba2d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -20,6 +20,7 @@  #include "video_core/renderer_vulkan/vk_buffer_cache.h"  #include "video_core/renderer_vulkan/vk_descriptor_pool.h"  #include "video_core/renderer_vulkan/vk_fence_manager.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"  #include "video_core/renderer_vulkan/vk_pipeline_cache.h"  #include "video_core/renderer_vulkan/vk_query_cache.h"  #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -173,6 +174,8 @@ private:      VKUpdateDescriptorQueue update_descriptor_queue;      BlitImageHelper blit_image; +    GraphicsPipelineCacheKey graphics_key; +      TextureCacheRuntime texture_cache_runtime;      TextureCache texture_cache;      BufferCacheRuntime buffer_cache_runtime; diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index ee274ac59..a8bf7bda8 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -17,21 +17,21 @@ 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> { +    const u64 gpu_tick = master_semaphore.KnownGpuTick(); +    const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {          for (size_t iterator = begin; iterator < end; ++iterator) { -            if (master_semaphore.IsFree(ticks[iterator])) { +            if (gpu_tick >= ticks[iterator]) {                  ticks[iterator] = master_semaphore.CurrentTick();                  return iterator;              }          } -        return {}; +        return std::nullopt;      };      // Try to find a free resource from the hinted position to the end. -    auto found = search(free_iterator, ticks.size()); +    std::optional<size_t> found = search(hint_iterator, ticks.size());      if (!found) {          // Search from beginning to the hinted position. -        found = search(0, free_iterator); +        found = search(0, hint_iterator);          if (!found) {              // Both searches failed, the pool is full; handle it.              const size_t free_resource = ManageOverflow(); @@ -41,7 +41,7 @@ size_t ResourcePool::CommitResource() {          }      }      // Free iterator is hinted to the resource after the one that's been commited. -    free_iterator = (*found + 1) % ticks.size(); +    hint_iterator = (*found + 1) % ticks.size();      return *found;  } diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h index a018c7ec2..9d0bb3b4d 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.h +++ b/src/video_core/renderer_vulkan/vk_resource_pool.h @@ -36,7 +36,7 @@ private:      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 +    size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found      std::vector<u64> ticks;   ///< Ticks for each resource  }; diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index e81fad007..956f86845 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -18,9 +18,7 @@  #define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32)))  namespace Vulkan { -  namespace { -  using namespace Dirty;  using namespace VideoCommon::Dirty;  using Tegra::Engines::Maxwell3D; @@ -128,6 +126,34 @@ void SetupDirtyStencilTestEnable(Tables& tables) {      tables[0][OFF(stencil_enable)] = StencilTestEnable;  } +void SetupDirtyBlending(Tables& tables) { +    tables[0][OFF(color_mask_common)] = Blending; +    tables[0][OFF(independent_blend_enable)] = Blending; +    FillBlock(tables[0], OFF(color_mask), NUM(color_mask), Blending); +    FillBlock(tables[0], OFF(blend), NUM(blend), Blending); +    FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending); +} + +void SetupDirtyInstanceDivisors(Tables& tables) { +    static constexpr size_t divisor_offset = 3; +    for (size_t index = 0; index < Regs::NumVertexArrays; ++index) { +        tables[0][OFF(instanced_arrays) + index] = InstanceDivisors; +        tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] = +            InstanceDivisors; +    } +} + +void SetupDirtyVertexAttributes(Tables& tables) { +    FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes); +} + +void SetupDirtyViewportSwizzles(Tables& tables) { +    static constexpr size_t swizzle_offset = 6; +    for (size_t index = 0; index < Regs::NumViewports; ++index) { +        tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = +            ViewportSwizzles; +    } +}  } // Anonymous namespace  StateTracker::StateTracker(Tegra::GPU& gpu) @@ -148,6 +174,10 @@ StateTracker::StateTracker(Tegra::GPU& gpu)      SetupDirtyFrontFace(tables);      SetupDirtyStencilOp(tables);      SetupDirtyStencilTestEnable(tables); +    SetupDirtyBlending(tables); +    SetupDirtyInstanceDivisors(tables); +    SetupDirtyVertexAttributes(tables); +    SetupDirtyViewportSwizzles(tables);  }  } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index c335d2bdf..84e918a71 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -35,6 +35,11 @@ enum : u8 {      StencilOp,      StencilTestEnable, +    Blending, +    InstanceDivisors, +    VertexAttributes, +    ViewportSwizzles, +      Last  };  static_assert(Last <= std::numeric_limits<u8>::max()); diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp index 3b40db9bc..02adcf9c7 100644 --- a/src/video_core/shader/async_shaders.cpp +++ b/src/video_core/shader/async_shaders.cpp @@ -64,6 +64,7 @@ void AsyncShaders::FreeWorkers() {  void AsyncShaders::KillWorkers() {      is_thread_exiting.store(true); +    cv.notify_all();      for (auto& thread : worker_threads) {          thread.detach();      } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index fb9967c8f..b025ced1c 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -151,6 +151,7 @@ add_executable(yuzu      util/util.h      compatdb.cpp      compatdb.h +    yuzu.qrc      yuzu.rc  ) diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index c680fd2c2..b92cd6886 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -67,6 +67,8 @@ bool IsControllerCompatible(Settings::ControllerType controller_type,          return parameters.allow_right_joycon;      case Settings::ControllerType::Handheld:          return parameters.enable_single_mode && parameters.allow_handheld; +    case Settings::ControllerType::GameCube: +        return parameters.allow_gamecube_controller;      default:          return false;      } @@ -370,7 +372,7 @@ void QtControllerSelectorDialog::SetSupportedControllers() {              QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));      } -    if (parameters.allow_pro_controller) { +    if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {          ui->controllerSupported5->setStyleSheet(              QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));      } else { @@ -420,6 +422,10 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index                             Settings::ControllerType::Handheld);          emulated_controllers[player_index]->addItem(tr("Handheld"));      } + +    pairs.emplace_back(emulated_controllers[player_index]->count(), +                       Settings::ControllerType::GameCube); +    emulated_controllers[player_index]->addItem(tr("GameCube Controller"));  }  Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( @@ -461,6 +467,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)          switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),                                             player_index)) {          case Settings::ControllerType::ProController: +        case Settings::ControllerType::GameCube:              return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");          case Settings::ControllerType::DualJoyconDetached:              return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0635d13d0..3d6f64300 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -614,12 +614,6 @@ void Config::ReadDataStorageValues() {                                  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 = @@ -1218,9 +1212,6 @@ void Config::SaveDataStorageValues() {      WriteSetting(QStringLiteral("dump_directory"),                   QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)),                   QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); -    WriteSetting(QStringLiteral("cache_directory"), -                 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); diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 7ab4a80f7..bde2d4620 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -26,8 +26,6 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)              [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });      connect(ui->load_path_button, &QToolButton::pressed, this,              [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); -    connect(ui->cache_directory_button, &QToolButton::pressed, this, -            [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });      connect(ui->reset_game_list_cache, &QPushButton::pressed, this,              &ConfigureFilesystem::ResetMetadata); @@ -50,8 +48,6 @@ void ConfigureFilesystem::setConfiguration() {          QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));      ui->load_path_edit->setText(          QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); -    ui->cache_directory_edit->setText( -        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); @@ -72,9 +68,6 @@ void ConfigureFilesystem::applyConfiguration() {                              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();      Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); @@ -103,9 +96,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)      case DirectoryTarget::Load:          caption = tr("Select Mod Load Directory...");          break; -    case DirectoryTarget::Cache: -        caption = tr("Select Cache Directory..."); -        break;      }      QString str; diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h index a79303760..2147cd405 100644 --- a/src/yuzu/configuration/configure_filesystem.h +++ b/src/yuzu/configuration/configure_filesystem.h @@ -32,7 +32,6 @@ private:          Gamecard,          Dump,          Load, -        Cache,      };      void SetDirectory(DirectoryTarget target, QLineEdit* edit); diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 84bea0600..62b9abc7a 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -198,40 +198,7 @@          <string>Caching</string>         </property>         <layout class="QGridLayout" name="gridLayout_5"> -        <item row="0" column="0"> -         <widget class="QLabel" name="label_10"> -          <property name="text"> -           <string>Cache Directory</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <spacer name="horizontalSpacer_3"> -          <property name="orientation"> -           <enum>Qt::Horizontal</enum> -          </property> -          <property name="sizeType"> -           <enum>QSizePolicy::Fixed</enum> -          </property> -          <property name="sizeHint" stdset="0"> -           <size> -            <width>40</width> -            <height>20</height> -           </size> -          </property> -         </spacer> -        </item> -        <item row="0" column="2"> -         <widget class="QLineEdit" name="cache_directory_edit"/> -        </item> -        <item row="0" column="3"> -         <widget class="QToolButton" name="cache_directory_button"> -          <property name="text"> -           <string>...</string> -          </property> -         </widget> -        </item> -        <item row="1" column="0" colspan="4"> +        <item row="0" column="0" colspan="2">           <layout class="QHBoxLayout" name="horizontalLayout_2">            <item>             <widget class="QCheckBox" name="cache_game_list"> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index c9d19c948..21d0d3449 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -467,10 +467,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i      UpdateControllerIcon();      UpdateControllerAvailableButtons(); +    UpdateControllerEnabledButtons(); +    UpdateControllerButtonNames();      UpdateMotionButtons();      connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {          UpdateControllerIcon();          UpdateControllerAvailableButtons(); +        UpdateControllerEnabledButtons(); +        UpdateControllerButtonNames();          UpdateMotionButtons();      }); @@ -558,9 +562,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i              &ConfigureInputPlayer::SaveProfile);      LoadConfiguration(); - -    // TODO(wwylele): enable this when we actually emulate it -    ui->buttonHome->setEnabled(false);      ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);      ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());  } @@ -924,6 +925,12 @@ void ConfigureInputPlayer::SetConnectableControllers() {                                                       Settings::ControllerType::Handheld);              ui->comboControllerType->addItem(tr("Handheld"));          } + +        if (enable_all || npad_style_set.gamecube == 1) { +            index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), +                                                     Settings::ControllerType::GameCube); +            ui->comboControllerType->addItem(tr("GameCube Controller")); +        }      };      Core::System& system{Core::System::GetInstance()}; @@ -1014,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {      // List of all the widgets that will be hidden by any of the following layouts that need      // "unhidden" after the controller type changes -    const std::array<QWidget*, 9> layout_show = { +    const std::array<QWidget*, 11> layout_show = {          ui->buttonShoulderButtonsSLSR,          ui->horizontalSpacerShoulderButtonsWidget,          ui->horizontalSpacerShoulderButtonsWidget2, @@ -1024,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {          ui->buttonShoulderButtonsRight,          ui->buttonMiscButtonsPlusHome,          ui->bottomRight, +        ui->buttonMiscButtonsMinusGroup, +        ui->buttonMiscButtonsScreenshotGroup,      };      for (auto* widget : layout_show) { @@ -1056,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {              ui->bottomLeft,          };          break; +    case Settings::ControllerType::GameCube: +        layout_hidden = { +            ui->buttonShoulderButtonsSLSR, +            ui->horizontalSpacerShoulderButtonsWidget2, +            ui->buttonMiscButtonsMinusGroup, +            ui->buttonMiscButtonsScreenshotGroup, +        }; +        break;      }      for (auto* widget : layout_hidden) { @@ -1063,6 +1080,52 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {      }  } +void ConfigureInputPlayer::UpdateControllerEnabledButtons() { +    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); +    if (debug) { +        layout = Settings::ControllerType::ProController; +    } + +    // List of all the widgets that will be disabled by any of the following layouts that need +    // "enabled" after the controller type changes +    const std::array<QWidget*, 4> layout_enable = { +        ui->buttonHome, +        ui->buttonLStickPressedGroup, +        ui->groupRStickPressed, +        ui->buttonShoulderButtonsButtonLGroup, +    }; + +    for (auto* widget : layout_enable) { +        widget->setEnabled(true); +    } + +    std::vector<QWidget*> layout_disable; +    switch (layout) { +    case Settings::ControllerType::ProController: +    case Settings::ControllerType::DualJoyconDetached: +    case Settings::ControllerType::Handheld: +    case Settings::ControllerType::LeftJoycon: +    case Settings::ControllerType::RightJoycon: +        // TODO(wwylele): enable this when we actually emulate it +        layout_disable = { +            ui->buttonHome, +        }; +        break; +    case Settings::ControllerType::GameCube: +        layout_disable = { +            ui->buttonHome, +            ui->buttonLStickPressedGroup, +            ui->groupRStickPressed, +            ui->buttonShoulderButtonsButtonLGroup, +        }; +        break; +    } + +    for (auto* widget : layout_disable) { +        widget->setEnabled(false); +    } +} +  void ConfigureInputPlayer::UpdateMotionButtons() {      if (debug) {          // Motion isn't used with the debug controller, hide both groupboxes. @@ -1085,6 +1148,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {          ui->buttonMotionLeftGroup->hide();          ui->buttonMotionRightGroup->show();          break; +    case Settings::ControllerType::GameCube: +        // Hide both "Motion 1/2". +        ui->buttonMotionLeftGroup->hide(); +        ui->buttonMotionRightGroup->hide(); +        break;      case Settings::ControllerType::DualJoyconDetached:      default:          // Show both "Motion 1/2". @@ -1094,6 +1162,36 @@ void ConfigureInputPlayer::UpdateMotionButtons() {      }  } +void ConfigureInputPlayer::UpdateControllerButtonNames() { +    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); +    if (debug) { +        layout = Settings::ControllerType::ProController; +    } + +    switch (layout) { +    case Settings::ControllerType::ProController: +    case Settings::ControllerType::DualJoyconDetached: +    case Settings::ControllerType::Handheld: +    case Settings::ControllerType::LeftJoycon: +    case Settings::ControllerType::RightJoycon: +        ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus")); +        ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL")); +        ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR")); +        ui->buttonShoulderButtonsRGroup->setTitle(tr("R")); +        ui->LStick->setTitle(tr("Left Stick")); +        ui->RStick->setTitle(tr("Right Stick")); +        break; +    case Settings::ControllerType::GameCube: +        ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause")); +        ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L")); +        ui->buttonShoulderButtonsZRGroup->setTitle(tr("R")); +        ui->buttonShoulderButtonsRGroup->setTitle(tr("Z")); +        ui->LStick->setTitle(tr("Control Stick")); +        ui->RStick->setTitle(tr("C-Stick")); +        break; +    } +} +  void ConfigureInputPlayer::UpdateMappingWithDefaults() {      if (ui->comboDevices->currentIndex() == 0) {          return; diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index da2b89136..efe953fbc 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -143,9 +143,15 @@ private:      /// Hides and disables controller settings based on the current controller type.      void UpdateControllerAvailableButtons(); +    /// Disables controller settings based on the current controller type. +    void UpdateControllerEnabledButtons(); +      /// Shows or hides motion groupboxes based on the current controller type.      void UpdateMotionButtons(); +    /// Alters the button names based on the current controller type. +    void UpdateControllerButtonNames(); +      /// Gets the default controller mapping for this device and auto configures the input to match.      void UpdateMappingWithDefaults(); diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 0e8a964d2..61ba91cef 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -227,6 +227,9 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {      case Settings::ControllerType::RightJoycon:          DrawRightController(p, center);          break; +    case Settings::ControllerType::GameCube: +        DrawGCController(p, center); +        break;      case Settings::ControllerType::ProController:      default:          DrawProController(p, center); diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 85724a8f3..2731d948d 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -42,7 +42,7 @@ void ControllerDialog::refreshConfiguration() {  QAction* ControllerDialog::toggleViewAction() {      if (toggle_view_action == nullptr) { -        toggle_view_action = new QAction(windowTitle(), this); +        toggle_view_action = new QAction(tr("&Controller P1"), this);          toggle_view_action->setCheckable(true);          toggle_view_action->setChecked(isVisible());          connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 28a52a56c..0ba7c07cc 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -2770,7 +2770,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {                      .arg(errors));          } -        QProgressDialog prog; +        QProgressDialog prog(this);          prog.setRange(0, 0);          prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "                               "system's performance.")); @@ -2952,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) {  }  void GMainWindow::UpdateUITheme() { -    const QString default_icons = QStringLiteral(":/icons/default"); +    const QString default_icons = QStringLiteral("default");      const QString& current_theme = UISettings::values.theme;      const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);      QStringList theme_paths(default_theme_paths); @@ -2968,7 +2968,6 @@ void GMainWindow::UpdateUITheme() {              qApp->setStyleSheet({});              setStyleSheet({});          } -        theme_paths.append(default_icons);          QIcon::setThemeName(default_icons);      } else {          const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss")); @@ -2980,10 +2979,7 @@ void GMainWindow::UpdateUITheme() {          } else {              LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");          } - -        const QString theme_name = QStringLiteral(":/icons/") + current_theme; -        theme_paths.append({default_icons, theme_name}); -        QIcon::setThemeName(theme_name); +        QIcon::setThemeName(current_theme);      }      QIcon::setThemeSearchPaths(theme_paths); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index e2ad5baf6..048870687 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -14,8 +14,8 @@     <string>yuzu</string>    </property>    <property name="windowIcon"> -   <iconset> -    <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset> +   <iconset resource="yuzu.qrc"> +    <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset>    </property>    <property name="tabShape">     <enum>QTabWidget::Rounded</enum> @@ -303,6 +303,8 @@     </property>    </action>   </widget> - <resources/> + <resources> +  <include location="yuzu.qrc"/> + </resources>   <connections/>  </ui> diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc new file mode 100644 index 000000000..5733cac98 --- /dev/null +++ b/src/yuzu/yuzu.qrc @@ -0,0 +1,5 @@ +<RCC> +    <qresource prefix="/img"> +        <file alias="yuzu.ico">../../dist/yuzu.ico</file> +    </qresource> +</RCC> diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 0b3f2cb54..8461f8896 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -1,5 +1,15 @@  set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) +function(create_resource file output filename) +    # Read hex data from file +    file(READ ${file} filedata HEX) +    # Convert hex data for C compatibility +    string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) +    # Write data to output file +    set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE) +    file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n") +endfunction() +  add_executable(yuzu-cmd      config.cpp      config.h @@ -24,6 +34,9 @@ if (MSVC)  endif()  target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) +create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") +target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) +  target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)  if(UNIX AND NOT APPLE) diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index aa0a9f288..6d8bc5509 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -329,9 +329,6 @@ void Config::ReadValues() {      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 = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 39841aa28..7e391ab89 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -12,6 +12,7 @@  #include "input_common/mouse/mouse_input.h"  #include "input_common/sdl/sdl.h"  #include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/yuzu_icon.h"  EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)      : input_subsystem{input_subsystem_} { @@ -194,6 +195,22 @@ void EmuWindow_SDL2::WaitEvent() {      }  } +void EmuWindow_SDL2::SetWindowIcon() { +    SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); +    if (yuzu_icon_stream == nullptr) { +        LOG_WARNING(Frontend, "Failed to create yuzu icon stream."); +        return; +    } +    SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); +    if (window_icon == nullptr) { +        LOG_WARNING(Frontend, "Failed to read BMP from stream."); +        return; +    } +    // The icon is attached to the window pointer +    SDL_SetWindowIcon(render_window, window_icon); +    SDL_FreeSurface(window_icon); +} +  void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {      SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);  } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index a93141240..51a12a6a9 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -32,6 +32,9 @@ public:      /// Wait for the next event on the main thread.      void WaitEvent(); +    // Sets the window icon from yuzu.bmp +    void SetWindowIcon(); +  protected:      /// Called by WaitEvent when a key is pressed or released.      void OnKeyEvent(int key, u8 state); 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 deddea9ee..a02485c14 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -107,6 +107,8 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste      dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,                                      SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); +    SetWindowIcon(); +      if (fullscreen) {          Fullscreen();      } 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 3ba657c00..6f9b00461 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -35,6 +35,8 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste          std::exit(EXIT_FAILURE);      } +    SetWindowIcon(); +      switch (wm.subsystem) {  #ifdef SDL_VIDEO_DRIVER_WINDOWS      case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: | 
