diff options
Diffstat (limited to 'src')
49 files changed, 1046 insertions, 174 deletions
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 | |||
| 15 | command_generator.cpp | 15 | command_generator.cpp |
| 16 | command_generator.h | 16 | command_generator.h |
| 17 | common.h | 17 | common.h |
| 18 | delay_line.cpp | ||
| 19 | delay_line.h | ||
| 18 | effect_context.cpp | 20 | effect_context.cpp |
| 19 | effect_context.h | 21 | effect_context.h |
| 20 | info_updater.cpp | 22 | 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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <cmath> | ||
| 6 | #include <numbers> | ||
| 5 | #include "audio_core/algorithm/interpolate.h" | 7 | #include "audio_core/algorithm/interpolate.h" |
| 6 | #include "audio_core/command_generator.h" | 8 | #include "audio_core/command_generator.h" |
| 7 | #include "audio_core/effect_context.h" | 9 | #include "audio_core/effect_context.h" |
| @@ -13,6 +15,20 @@ namespace AudioCore { | |||
| 13 | namespace { | 15 | namespace { |
| 14 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; | 16 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; |
| 15 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; | 17 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; |
| 18 | using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; | ||
| 19 | |||
| 20 | constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; | ||
| 21 | constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; | ||
| 22 | constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; | ||
| 23 | constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; | ||
| 24 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ | ||
| 25 | 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, | ||
| 26 | 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, | ||
| 27 | 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; | ||
| 28 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ | ||
| 29 | 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, | ||
| 30 | 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, | ||
| 31 | 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; | ||
| 16 | 32 | ||
| 17 | template <std::size_t N> | 33 | template <std::size_t N> |
| 18 | void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { | 34 | 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) { | |||
| 65 | } | 81 | } |
| 66 | } | 82 | } |
| 67 | 83 | ||
| 84 | float Pow10(float x) { | ||
| 85 | if (x >= 0.0f) { | ||
| 86 | return 1.0f; | ||
| 87 | } else if (x <= -5.3f) { | ||
| 88 | return 0.0f; | ||
| 89 | } | ||
| 90 | return std::pow(10.0f, x); | ||
| 91 | } | ||
| 92 | |||
| 93 | float SinD(float degrees) { | ||
| 94 | return std::sin(degrees * std::numbers::pi_v<float> / 180.0f); | ||
| 95 | } | ||
| 96 | |||
| 97 | float CosD(float degrees) { | ||
| 98 | return std::cos(degrees * std::numbers::pi_v<float> / 180.0f); | ||
| 99 | } | ||
| 100 | |||
| 101 | float ToFloat(s32 sample) { | ||
| 102 | return static_cast<float>(sample) / 65536.f; | ||
| 103 | } | ||
| 104 | |||
| 105 | s32 ToS32(float sample) { | ||
| 106 | constexpr auto min = -8388608.0f; | ||
| 107 | constexpr auto max = 8388607.f; | ||
| 108 | float rescaled_sample = sample * 65536.0f; | ||
| 109 | if (rescaled_sample < min) { | ||
| 110 | rescaled_sample = min; | ||
| 111 | } | ||
| 112 | if (rescaled_sample > max) { | ||
| 113 | rescaled_sample = max; | ||
| 114 | } | ||
| 115 | return static_cast<s32>(rescaled_sample); | ||
| 116 | } | ||
| 117 | |||
| 118 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 119 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||
| 120 | |||
| 121 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, | ||
| 122 | 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; | ||
| 123 | |||
| 124 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 125 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 126 | |||
| 127 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 128 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 129 | |||
| 130 | template <std::size_t CHANNEL_COUNT> | ||
| 131 | void ApplyReverbGeneric(I3dl2ReverbState& state, | ||
| 132 | const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input, | ||
| 133 | const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output, | ||
| 134 | s32 sample_count) { | ||
| 135 | |||
| 136 | auto GetTapLookup = []() { | ||
| 137 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 138 | return REVERB_TAP_INDEX_1CH; | ||
| 139 | } else if constexpr (CHANNEL_COUNT == 2) { | ||
| 140 | return REVERB_TAP_INDEX_2CH; | ||
| 141 | } else if constexpr (CHANNEL_COUNT == 4) { | ||
| 142 | return REVERB_TAP_INDEX_4CH; | ||
| 143 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 144 | return REVERB_TAP_INDEX_6CH; | ||
| 145 | } | ||
| 146 | }; | ||
| 147 | |||
| 148 | const auto& tap_index_lut = GetTapLookup(); | ||
| 149 | for (s32 sample = 0; sample < sample_count; sample++) { | ||
| 150 | std::array<f32, CHANNEL_COUNT> out_samples{}; | ||
| 151 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; | ||
| 152 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; | ||
| 153 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; | ||
| 154 | |||
| 155 | // Mix everything into a single sample | ||
| 156 | s32 temp_mixed_sample = 0; | ||
| 157 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 158 | temp_mixed_sample += input[i][sample]; | ||
| 159 | } | ||
| 160 | const auto current_sample = ToFloat(temp_mixed_sample); | ||
| 161 | const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); | ||
| 162 | |||
| 163 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { | ||
| 164 | const auto tapped_samp = | ||
| 165 | state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; | ||
| 166 | out_samples[tap_index_lut[i]] += tapped_samp; | ||
| 167 | |||
| 168 | if constexpr (CHANNEL_COUNT == 6) { | ||
| 169 | // handle lfe | ||
| 170 | out_samples[5] += tapped_samp; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; | ||
| 175 | state.early_delay_line.Tick(state.lowpass_0); | ||
| 176 | |||
| 177 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 178 | out_samples[i] *= state.early_gain; | ||
| 179 | } | ||
| 180 | |||
| 181 | // Two channel seems to apply a latet gain, we require to save this | ||
| 182 | f32 filter{}; | ||
| 183 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 184 | filter = state.fdn_delay_line[i].GetOutputSample(); | ||
| 185 | const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; | ||
| 186 | state.shelf_filter[i] = | ||
| 187 | filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; | ||
| 188 | fsamp[i] = computed; | ||
| 189 | } | ||
| 190 | |||
| 191 | // Mixing matrix | ||
| 192 | mixed[0] = fsamp[1] + fsamp[2]; | ||
| 193 | mixed[1] = -fsamp[0] - fsamp[3]; | ||
| 194 | mixed[2] = fsamp[0] - fsamp[3]; | ||
| 195 | mixed[3] = fsamp[1] - fsamp[2]; | ||
| 196 | |||
| 197 | if constexpr (CHANNEL_COUNT == 2) { | ||
| 198 | for (auto& mix : mixed) { | ||
| 199 | mix *= (filter * state.late_gain); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 204 | const auto late = early_tap * state.late_gain; | ||
| 205 | osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); | ||
| 206 | osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); | ||
| 207 | state.fdn_delay_line[i].Tick(osamp[i]); | ||
| 208 | } | ||
| 209 | |||
| 210 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 211 | output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + | ||
| 212 | (out_samples[0] + osamp[0] + osamp[1])); | ||
| 213 | } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { | ||
| 214 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 215 | output[i][sample] = | ||
| 216 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 217 | } | ||
| 218 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 219 | const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); | ||
| 220 | for (std::size_t i = 0; i < 4; i++) { | ||
| 221 | output[i][sample] = | ||
| 222 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 223 | } | ||
| 224 | output[4][sample] = | ||
| 225 | ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); | ||
| 226 | output[5][sample] = | ||
| 227 | ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 68 | } // namespace | 232 | } // namespace |
| 69 | 233 | ||
| 70 | CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, | 234 | CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, |
| @@ -271,11 +435,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic | |||
| 271 | } | 435 | } |
| 272 | 436 | ||
| 273 | // Generate biquad filter | 437 | // Generate biquad filter |
| 274 | // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, | 438 | // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, |
| 275 | // dsp_state.biquad_filter_state, | 439 | // dsp_state.biquad_filter_state, |
| 276 | // mix_buffer_count + channel, mix_buffer_count + | 440 | // mix_buffer_count + channel, mix_buffer_count + channel, |
| 277 | // channel, worker_params.sample_count, | 441 | // worker_params.sample_count, voice_info.GetInParams().node_id); |
| 278 | // voice_info.GetInParams().node_id); | ||
| 279 | } | 442 | } |
| 280 | } | 443 | } |
| 281 | 444 | ||
| @@ -376,21 +539,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { | |||
| 376 | 539 | ||
| 377 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, | 540 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, |
| 378 | bool enabled) { | 541 | bool enabled) { |
| 379 | if (!enabled) { | 542 | auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); |
| 543 | const auto& params = reverb->GetParams(); | ||
| 544 | auto& state = reverb->GetState(); | ||
| 545 | const auto channel_count = params.channel_count; | ||
| 546 | |||
| 547 | if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { | ||
| 380 | return; | 548 | return; |
| 381 | } | 549 | } |
| 382 | const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams(); | 550 | |
| 383 | const auto channel_count = params.channel_count; | 551 | std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
| 552 | std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 553 | |||
| 554 | const auto status = params.status; | ||
| 384 | for (s32 i = 0; i < channel_count; i++) { | 555 | for (s32 i = 0; i < channel_count; i++) { |
| 385 | // TODO(ogniK): Actually implement reverb | 556 | input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); |
| 386 | /* | 557 | output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); |
| 387 | if (params.input[i] != params.output[i]) { | 558 | } |
| 388 | const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); | 559 | |
| 389 | auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); | 560 | if (enabled) { |
| 390 | ApplyMix<1>(output, input, 32768, worker_params.sample_count); | 561 | if (status == ParameterStatus::Initialized) { |
| 391 | }*/ | 562 | InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); |
| 392 | auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); | 563 | } else if (status == ParameterStatus::Updating) { |
| 393 | std::memset(output, 0, worker_params.sample_count * sizeof(s32)); | 564 | UpdateI3dl2Reverb(reverb->GetParams(), state, false); |
| 565 | } | ||
| 566 | } | ||
| 567 | |||
| 568 | if (enabled) { | ||
| 569 | switch (channel_count) { | ||
| 570 | case 1: | ||
| 571 | ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); | ||
| 572 | break; | ||
| 573 | case 2: | ||
| 574 | ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); | ||
| 575 | break; | ||
| 576 | case 4: | ||
| 577 | ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); | ||
| 578 | break; | ||
| 579 | case 6: | ||
| 580 | ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); | ||
| 581 | break; | ||
| 582 | } | ||
| 583 | } else { | ||
| 584 | for (s32 i = 0; i < channel_count; i++) { | ||
| 585 | // Only copy if the buffer input and output do not match! | ||
| 586 | if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { | ||
| 587 | std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); | ||
| 588 | } | ||
| 589 | } | ||
| 394 | } | 590 | } |
| 395 | } | 591 | } |
| 396 | 592 | ||
| @@ -528,6 +724,133 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3 | |||
| 528 | return sample_count; | 724 | return sample_count; |
| 529 | } | 725 | } |
| 530 | 726 | ||
| 727 | void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 728 | std::vector<u8>& work_buffer) { | ||
| 729 | // Reset state | ||
| 730 | state.lowpass_0 = 0.0f; | ||
| 731 | state.lowpass_1 = 0.0f; | ||
| 732 | state.lowpass_2 = 0.0f; | ||
| 733 | |||
| 734 | state.early_delay_line.Reset(); | ||
| 735 | state.early_tap_steps.fill(0); | ||
| 736 | state.early_gain = 0.0f; | ||
| 737 | state.late_gain = 0.0f; | ||
| 738 | state.early_to_late_taps = 0; | ||
| 739 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 740 | state.fdn_delay_line[i].Reset(); | ||
| 741 | state.decay_delay_line0[i].Reset(); | ||
| 742 | state.decay_delay_line1[i].Reset(); | ||
| 743 | } | ||
| 744 | state.last_reverb_echo = 0.0f; | ||
| 745 | state.center_delay_line.Reset(); | ||
| 746 | for (auto& coef : state.lpf_coefficients) { | ||
| 747 | coef.fill(0.0f); | ||
| 748 | } | ||
| 749 | state.shelf_filter.fill(0.0f); | ||
| 750 | state.dry_gain = 0.0f; | ||
| 751 | |||
| 752 | const auto sample_rate = info.sample_rate / 1000; | ||
| 753 | f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); | ||
| 754 | |||
| 755 | s32 delay_samples{}; | ||
| 756 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 757 | delay_samples = | ||
| 758 | AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); | ||
| 759 | state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); | ||
| 760 | work_buffer_ptr += delay_samples + 1; | ||
| 761 | |||
| 762 | delay_samples = | ||
| 763 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); | ||
| 764 | state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 765 | work_buffer_ptr += delay_samples + 1; | ||
| 766 | |||
| 767 | delay_samples = | ||
| 768 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); | ||
| 769 | state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 770 | work_buffer_ptr += delay_samples + 1; | ||
| 771 | } | ||
| 772 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); | ||
| 773 | state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 774 | work_buffer_ptr += delay_samples + 1; | ||
| 775 | |||
| 776 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); | ||
| 777 | state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 778 | |||
| 779 | UpdateI3dl2Reverb(info, state, true); | ||
| 780 | } | ||
| 781 | |||
| 782 | void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 783 | bool should_clear) { | ||
| 784 | |||
| 785 | state.dry_gain = info.dry_gain; | ||
| 786 | state.shelf_filter.fill(0.0f); | ||
| 787 | state.lowpass_0 = 0.0f; | ||
| 788 | state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); | ||
| 789 | state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); | ||
| 790 | |||
| 791 | const auto sample_rate = info.sample_rate / 1000; | ||
| 792 | const f32 hf_gain = Pow10(info.room_hf / 2000.0f); | ||
| 793 | if (hf_gain >= 1.0f) { | ||
| 794 | state.lowpass_2 = 1.0f; | ||
| 795 | state.lowpass_1 = 0.0f; | ||
| 796 | } else { | ||
| 797 | const auto a = 1.0f - hf_gain; | ||
| 798 | const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / | ||
| 799 | static_cast<f32>(info.sample_rate))); | ||
| 800 | const auto c = std::sqrt(b * b - 4.0f * a * a); | ||
| 801 | |||
| 802 | state.lowpass_1 = (b - c) / (2.0f * a); | ||
| 803 | state.lowpass_2 = 1.0f - state.lowpass_1; | ||
| 804 | } | ||
| 805 | state.early_to_late_taps = AudioCommon::CalculateDelaySamples( | ||
| 806 | sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); | ||
| 807 | |||
| 808 | state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; | ||
| 809 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 810 | const auto length = | ||
| 811 | FDN_MIN_DELAY_LINE_TIMES[i] + | ||
| 812 | (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); | ||
| 813 | state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); | ||
| 814 | |||
| 815 | const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + | ||
| 816 | state.decay_delay_line0[i].GetDelay() + | ||
| 817 | state.decay_delay_line1[i].GetDelay(); | ||
| 818 | |||
| 819 | float a = (-60.0f * static_cast<f32>(delay_sample_counts)) / | ||
| 820 | (info.decay_time * static_cast<f32>(info.sample_rate)); | ||
| 821 | float b = a / info.hf_decay_ratio; | ||
| 822 | float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) / | ||
| 823 | SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)); | ||
| 824 | float d = Pow10((b - a) / 40.0f); | ||
| 825 | float e = Pow10((b + a) / 40.0f) * 0.7071f; | ||
| 826 | |||
| 827 | state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); | ||
| 828 | state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); | ||
| 829 | state.lpf_coefficients[2][i] = (c - d) / (c + d); | ||
| 830 | |||
| 831 | state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); | ||
| 832 | state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); | ||
| 833 | } | ||
| 834 | |||
| 835 | if (should_clear) { | ||
| 836 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 837 | state.fdn_delay_line[i].Clear(); | ||
| 838 | state.decay_delay_line0[i].Clear(); | ||
| 839 | state.decay_delay_line1[i].Clear(); | ||
| 840 | } | ||
| 841 | state.early_delay_line.Clear(); | ||
| 842 | state.center_delay_line.Clear(); | ||
| 843 | } | ||
| 844 | |||
| 845 | const auto max_early_delay = state.early_delay_line.GetMaxDelay(); | ||
| 846 | const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); | ||
| 847 | for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { | ||
| 848 | const auto length = AudioCommon::CalculateDelaySamples( | ||
| 849 | sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); | ||
| 850 | state.early_tap_steps[tap] = std::min(length, max_early_delay); | ||
| 851 | } | ||
| 852 | } | ||
| 853 | |||
| 531 | void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, | 854 | void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, |
| 532 | s32 channel, s32 node_id) { | 855 | s32 channel, s32 node_id) { |
| 533 | const auto last = static_cast<s32>(last_volume * 32768.0f); | 856 | 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; | |||
| 21 | class EffectContext; | 21 | class EffectContext; |
| 22 | class EffectBase; | 22 | class EffectBase; |
| 23 | struct AuxInfoDSP; | 23 | struct AuxInfoDSP; |
| 24 | struct I3dl2ReverbParams; | ||
| 25 | struct I3dl2ReverbState; | ||
| 24 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | 26 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; |
| 25 | 27 | ||
| 26 | class CommandGenerator { | 28 | class CommandGenerator { |
| @@ -80,6 +82,9 @@ private: | |||
| 80 | s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, | 82 | s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, |
| 81 | u32 sample_count, u32 read_offset, u32 read_count); | 83 | u32 sample_count, u32 read_offset, u32 read_count); |
| 82 | 84 | ||
| 85 | void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 86 | std::vector<u8>& work_buffer); | ||
| 87 | void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); | ||
| 83 | // DSP Code | 88 | // DSP Code |
| 84 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, | 89 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, |
| 85 | s32 channel, std::size_t mix_offset); | 90 | 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 | |||
| 33 | // and our const ends up being 0x3f04, the 4 bytes are most | 33 | // and our const ends up being 0x3f04, the 4 bytes are most |
| 34 | // likely the sample history | 34 | // likely the sample history |
| 35 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | 35 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; |
| 36 | constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; | ||
| 37 | constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; | ||
| 38 | constexpr std::size_t I3DL2REVERB_TAPS = 20; | ||
| 39 | constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; | ||
| 40 | using Fractional = s32; | ||
| 41 | |||
| 42 | template <typename T> | ||
| 43 | constexpr Fractional ToFractional(T x) { | ||
| 44 | return static_cast<Fractional>(x * static_cast<T>(0x4000)); | ||
| 45 | } | ||
| 46 | |||
| 47 | constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { | ||
| 48 | return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); | ||
| 49 | } | ||
| 50 | |||
| 51 | constexpr s32 FractionalToFixed(Fractional x) { | ||
| 52 | const auto s = x & (1 << 13); | ||
| 53 | return static_cast<s32>(x >> 14) + s; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { | ||
| 57 | return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); | ||
| 58 | } | ||
| 36 | 59 | ||
| 37 | static constexpr u32 VersionFromRevision(u32_le rev) { | 60 | static constexpr u32 VersionFromRevision(u32_le rev) { |
| 38 | // "REV7" -> 7 | 61 | // "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 @@ | |||
| 1 | #include <cstring> | ||
| 2 | #include "audio_core/delay_line.h" | ||
| 3 | |||
| 4 | namespace AudioCore { | ||
| 5 | DelayLineBase::DelayLineBase() = default; | ||
| 6 | DelayLineBase::~DelayLineBase() = default; | ||
| 7 | |||
| 8 | void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { | ||
| 9 | buffer = src_buffer; | ||
| 10 | buffer_end = buffer + max_delay_; | ||
| 11 | max_delay = max_delay_; | ||
| 12 | output = buffer; | ||
| 13 | SetDelay(max_delay_); | ||
| 14 | Clear(); | ||
| 15 | } | ||
| 16 | |||
| 17 | void DelayLineBase::SetDelay(s32 new_delay) { | ||
| 18 | if (max_delay < new_delay) { | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | delay = new_delay; | ||
| 22 | input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); | ||
| 23 | } | ||
| 24 | |||
| 25 | s32 DelayLineBase::GetDelay() const { | ||
| 26 | return delay; | ||
| 27 | } | ||
| 28 | |||
| 29 | s32 DelayLineBase::GetMaxDelay() const { | ||
| 30 | return max_delay; | ||
| 31 | } | ||
| 32 | |||
| 33 | f32 DelayLineBase::TapOut(s32 last_sample) { | ||
| 34 | const float* ptr = input - (last_sample + 1); | ||
| 35 | if (ptr < buffer) { | ||
| 36 | ptr += (max_delay + 1); | ||
| 37 | } | ||
| 38 | |||
| 39 | return *ptr; | ||
| 40 | } | ||
| 41 | |||
| 42 | f32 DelayLineBase::Tick(f32 sample) { | ||
| 43 | *(input++) = sample; | ||
| 44 | const auto out_sample = *(output++); | ||
| 45 | |||
| 46 | if (buffer_end < input) { | ||
| 47 | input = buffer; | ||
| 48 | } | ||
| 49 | |||
| 50 | if (buffer_end < output) { | ||
| 51 | output = buffer; | ||
| 52 | } | ||
| 53 | |||
| 54 | return out_sample; | ||
| 55 | } | ||
| 56 | |||
| 57 | float* DelayLineBase::GetInput() { | ||
| 58 | return input; | ||
| 59 | } | ||
| 60 | |||
| 61 | const float* DelayLineBase::GetInput() const { | ||
| 62 | return input; | ||
| 63 | } | ||
| 64 | |||
| 65 | f32 DelayLineBase::GetOutputSample() const { | ||
| 66 | return *output; | ||
| 67 | } | ||
| 68 | |||
| 69 | void DelayLineBase::Clear() { | ||
| 70 | std::memset(buffer, 0, sizeof(float) * max_delay); | ||
| 71 | } | ||
| 72 | |||
| 73 | void DelayLineBase::Reset() { | ||
| 74 | buffer = nullptr; | ||
| 75 | buffer_end = nullptr; | ||
| 76 | max_delay = 0; | ||
| 77 | input = nullptr; | ||
| 78 | output = nullptr; | ||
| 79 | delay = 0; | ||
| 80 | } | ||
| 81 | |||
| 82 | DelayLineAllPass::DelayLineAllPass() = default; | ||
| 83 | DelayLineAllPass::~DelayLineAllPass() = default; | ||
| 84 | |||
| 85 | void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { | ||
| 86 | DelayLineBase::Initialize(delay_, src_buffer); | ||
| 87 | SetCoefficient(coeffcient_); | ||
| 88 | } | ||
| 89 | |||
| 90 | void DelayLineAllPass::SetCoefficient(float coeffcient_) { | ||
| 91 | coefficient = coeffcient_; | ||
| 92 | } | ||
| 93 | |||
| 94 | f32 DelayLineAllPass::Tick(f32 sample) { | ||
| 95 | const auto temp = sample - coefficient * *output; | ||
| 96 | return coefficient * temp + DelayLineBase::Tick(temp); | ||
| 97 | } | ||
| 98 | |||
| 99 | void DelayLineAllPass::Reset() { | ||
| 100 | coefficient = 0.0f; | ||
| 101 | DelayLineBase::Reset(); | ||
| 102 | } | ||
| 103 | |||
| 104 | } // 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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "common/common_types.h" | ||
| 4 | |||
| 5 | namespace AudioCore { | ||
| 6 | |||
| 7 | class DelayLineBase { | ||
| 8 | public: | ||
| 9 | DelayLineBase(); | ||
| 10 | ~DelayLineBase(); | ||
| 11 | |||
| 12 | void Initialize(s32 max_delay_, float* src_buffer); | ||
| 13 | void SetDelay(s32 new_delay); | ||
| 14 | s32 GetDelay() const; | ||
| 15 | s32 GetMaxDelay() const; | ||
| 16 | f32 TapOut(s32 last_sample); | ||
| 17 | f32 Tick(f32 sample); | ||
| 18 | float* GetInput(); | ||
| 19 | const float* GetInput() const; | ||
| 20 | f32 GetOutputSample() const; | ||
| 21 | void Clear(); | ||
| 22 | void Reset(); | ||
| 23 | |||
| 24 | protected: | ||
| 25 | float* buffer{nullptr}; | ||
| 26 | float* buffer_end{nullptr}; | ||
| 27 | s32 max_delay{}; | ||
| 28 | float* input{nullptr}; | ||
| 29 | float* output{nullptr}; | ||
| 30 | s32 delay{}; | ||
| 31 | }; | ||
| 32 | |||
| 33 | class DelayLineAllPass final : public DelayLineBase { | ||
| 34 | public: | ||
| 35 | DelayLineAllPass(); | ||
| 36 | ~DelayLineAllPass(); | ||
| 37 | |||
| 38 | void Initialize(u32 delay, float coeffcient_, f32* src_buffer); | ||
| 39 | void SetCoefficient(float coeffcient_); | ||
| 40 | f32 Tick(f32 sample); | ||
| 41 | void Reset(); | ||
| 42 | |||
| 43 | private: | ||
| 44 | float coefficient{}; | ||
| 45 | }; | ||
| 46 | } // 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 { | |||
| 90 | return processing_order; | 90 | return processing_order; |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | std::vector<u8>& EffectBase::GetWorkBuffer() { | ||
| 94 | return work_buffer; | ||
| 95 | } | ||
| 96 | |||
| 97 | const std::vector<u8>& EffectBase::GetWorkBuffer() const { | ||
| 98 | return work_buffer; | ||
| 99 | } | ||
| 100 | |||
| 93 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} | 101 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} |
| 94 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; | 102 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; |
| 95 | 103 | ||
| @@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { | |||
| 117 | usage = UsageState::Initialized; | 125 | usage = UsageState::Initialized; |
| 118 | params.status = ParameterStatus::Initialized; | 126 | params.status = ParameterStatus::Initialized; |
| 119 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | 127 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; |
| 128 | if (!skipped) { | ||
| 129 | auto& cur_work_buffer = GetWorkBuffer(); | ||
| 130 | // Has two buffers internally | ||
| 131 | cur_work_buffer.resize(in_params.buffer_size * 2); | ||
| 132 | std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); | ||
| 133 | } | ||
| 120 | } | 134 | } |
| 121 | } | 135 | } |
| 122 | 136 | ||
| @@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() { | |||
| 129 | GetParams().status = ParameterStatus::Updated; | 143 | GetParams().status = ParameterStatus::Updated; |
| 130 | } | 144 | } |
| 131 | 145 | ||
| 146 | I3dl2ReverbState& EffectI3dl2Reverb::GetState() { | ||
| 147 | return state; | ||
| 148 | } | ||
| 149 | |||
| 150 | const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { | ||
| 151 | return state; | ||
| 152 | } | ||
| 153 | |||
| 132 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} | 154 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} |
| 133 | EffectBiquadFilter::~EffectBiquadFilter() = default; | 155 | EffectBiquadFilter::~EffectBiquadFilter() = default; |
| 134 | 156 | ||
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 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include "audio_core/common.h" | 10 | #include "audio_core/common.h" |
| 11 | #include "audio_core/delay_line.h" | ||
| 11 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 12 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 13 | #include "common/swap.h" | 14 | #include "common/swap.h" |
| @@ -194,6 +195,8 @@ public: | |||
| 194 | [[nodiscard]] bool IsEnabled() const; | 195 | [[nodiscard]] bool IsEnabled() const; |
| 195 | [[nodiscard]] s32 GetMixID() const; | 196 | [[nodiscard]] s32 GetMixID() const; |
| 196 | [[nodiscard]] s32 GetProcessingOrder() const; | 197 | [[nodiscard]] s32 GetProcessingOrder() const; |
| 198 | [[nodiscard]] std::vector<u8>& GetWorkBuffer(); | ||
| 199 | [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; | ||
| 197 | 200 | ||
| 198 | protected: | 201 | protected: |
| 199 | UsageState usage{UsageState::Invalid}; | 202 | UsageState usage{UsageState::Invalid}; |
| @@ -201,6 +204,7 @@ protected: | |||
| 201 | s32 mix_id{}; | 204 | s32 mix_id{}; |
| 202 | s32 processing_order{}; | 205 | s32 processing_order{}; |
| 203 | bool enabled = false; | 206 | bool enabled = false; |
| 207 | std::vector<u8> work_buffer{}; | ||
| 204 | }; | 208 | }; |
| 205 | 209 | ||
| 206 | template <typename T> | 210 | template <typename T> |
| @@ -212,7 +216,7 @@ public: | |||
| 212 | return internal_params; | 216 | return internal_params; |
| 213 | } | 217 | } |
| 214 | 218 | ||
| 215 | const I3dl2ReverbParams& GetParams() const { | 219 | const T& GetParams() const { |
| 216 | return internal_params; | 220 | return internal_params; |
| 217 | } | 221 | } |
| 218 | 222 | ||
| @@ -229,6 +233,27 @@ public: | |||
| 229 | void UpdateForCommandGeneration() override; | 233 | void UpdateForCommandGeneration() override; |
| 230 | }; | 234 | }; |
| 231 | 235 | ||
| 236 | struct I3dl2ReverbState { | ||
| 237 | f32 lowpass_0{}; | ||
| 238 | f32 lowpass_1{}; | ||
| 239 | f32 lowpass_2{}; | ||
| 240 | |||
| 241 | DelayLineBase early_delay_line{}; | ||
| 242 | std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; | ||
| 243 | f32 early_gain{}; | ||
| 244 | f32 late_gain{}; | ||
| 245 | |||
| 246 | u32 early_to_late_taps{}; | ||
| 247 | std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; | ||
| 248 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; | ||
| 249 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; | ||
| 250 | f32 last_reverb_echo{}; | ||
| 251 | DelayLineBase center_delay_line{}; | ||
| 252 | std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; | ||
| 253 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; | ||
| 254 | f32 dry_gain{}; | ||
| 255 | }; | ||
| 256 | |||
| 232 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | 257 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { |
| 233 | public: | 258 | public: |
| 234 | explicit EffectI3dl2Reverb(); | 259 | explicit EffectI3dl2Reverb(); |
| @@ -237,8 +262,12 @@ public: | |||
| 237 | void Update(EffectInfo::InParams& in_params) override; | 262 | void Update(EffectInfo::InParams& in_params) override; |
| 238 | void UpdateForCommandGeneration() override; | 263 | void UpdateForCommandGeneration() override; |
| 239 | 264 | ||
| 265 | I3dl2ReverbState& GetState(); | ||
| 266 | const I3dl2ReverbState& GetState() const; | ||
| 267 | |||
| 240 | private: | 268 | private: |
| 241 | bool skipped = false; | 269 | bool skipped = false; |
| 270 | I3dl2ReverbState state{}; | ||
| 242 | }; | 271 | }; |
| 243 | 272 | ||
| 244 | class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { | 273 | 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 | |||
| 266 | hle/service/am/applets/software_keyboard.h | 266 | hle/service/am/applets/software_keyboard.h |
| 267 | hle/service/am/applets/web_browser.cpp | 267 | hle/service/am/applets/web_browser.cpp |
| 268 | hle/service/am/applets/web_browser.h | 268 | hle/service/am/applets/web_browser.h |
| 269 | hle/service/am/applets/web_types.h | ||
| 269 | hle/service/am/idle.cpp | 270 | hle/service/am/idle.cpp |
| 270 | hle/service/am/idle.h | 271 | hle/service/am/idle.h |
| 271 | hle/service/am/omm.cpp | 272 | hle/service/am/omm.cpp |
| @@ -400,6 +401,7 @@ add_library(core STATIC | |||
| 400 | hle/service/hid/controllers/xpad.h | 401 | hle/service/hid/controllers/xpad.h |
| 401 | hle/service/lbl/lbl.cpp | 402 | hle/service/lbl/lbl.cpp |
| 402 | hle/service/lbl/lbl.h | 403 | hle/service/lbl/lbl.h |
| 404 | hle/service/ldn/errors.h | ||
| 403 | hle/service/ldn/ldn.cpp | 405 | hle/service/ldn/ldn.cpp |
| 404 | hle/service/ldn/ldn.h | 406 | hle/service/ldn/ldn.h |
| 405 | hle/service/ldr/ldr.cpp | 407 | 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 { | |||
| 31 | bool allow_dual_joycons{}; | 31 | bool allow_dual_joycons{}; |
| 32 | bool allow_left_joycon{}; | 32 | bool allow_left_joycon{}; |
| 33 | bool allow_right_joycon{}; | 33 | bool allow_right_joycon{}; |
| 34 | bool allow_gamecube_controller{}; | ||
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 36 | class ControllerApplet { | 37 | 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) { | |||
| 1047 | 1047 | ||
| 1048 | const u64 offset{rp.Pop<u64>()}; | 1048 | const u64 offset{rp.Pop<u64>()}; |
| 1049 | const std::vector<u8> data{ctx.ReadBuffer()}; | 1049 | const std::vector<u8> data{ctx.ReadBuffer()}; |
| 1050 | const std::size_t size{std::min(data.size(), backing.GetSize() - offset)}; | ||
| 1050 | 1051 | ||
| 1051 | LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); | 1052 | LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); |
| 1052 | 1053 | ||
| 1053 | if (data.size() > backing.GetSize() - offset) { | 1054 | if (offset > backing.GetSize()) { |
| 1054 | LOG_ERROR(Service_AM, | 1055 | LOG_ERROR(Service_AM, |
| 1055 | "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", | 1056 | "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", |
| 1056 | backing.GetSize(), data.size(), offset); | 1057 | backing.GetSize(), size, offset); |
| 1057 | 1058 | ||
| 1058 | IPC::ResponseBuilder rb{ctx, 2}; | 1059 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1059 | rb.Push(ERR_SIZE_OUT_OF_BOUNDS); | 1060 | rb.Push(ERR_SIZE_OUT_OF_BOUNDS); |
| 1060 | return; | 1061 | return; |
| 1061 | } | 1062 | } |
| 1062 | 1063 | ||
| 1063 | std::memcpy(backing.GetData().data() + offset, data.data(), data.size()); | 1064 | std::memcpy(backing.GetData().data() + offset, data.data(), size); |
| 1064 | 1065 | ||
| 1065 | IPC::ResponseBuilder rb{ctx, 2}; | 1066 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1066 | rb.Push(RESULT_SUCCESS); | 1067 | rb.Push(RESULT_SUCCESS); |
| @@ -1070,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { | |||
| 1070 | IPC::RequestParser rp{ctx}; | 1071 | IPC::RequestParser rp{ctx}; |
| 1071 | 1072 | ||
| 1072 | const u64 offset{rp.Pop<u64>()}; | 1073 | const u64 offset{rp.Pop<u64>()}; |
| 1073 | const std::size_t size{ctx.GetWriteBufferSize()}; | 1074 | const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)}; |
| 1074 | 1075 | ||
| 1075 | LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); | 1076 | LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); |
| 1076 | 1077 | ||
| 1077 | if (size > backing.GetSize() - offset) { | 1078 | if (offset > backing.GetSize()) { |
| 1078 | LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", | 1079 | LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", |
| 1079 | backing.GetSize(), size, offset); | 1080 | backing.GetSize(), size, offset); |
| 1080 | 1081 | ||
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 @@ | |||
| 21 | 21 | ||
| 22 | namespace Service::HID { | 22 | namespace Service::HID { |
| 23 | constexpr s32 HID_JOYSTICK_MAX = 0x7fff; | 23 | constexpr s32 HID_JOYSTICK_MAX = 0x7fff; |
| 24 | constexpr s32 HID_TRIGGER_MAX = 0x7fff; | ||
| 24 | [[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; | 25 | [[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; |
| 25 | constexpr std::size_t NPAD_OFFSET = 0x9A00; | 26 | constexpr std::size_t NPAD_OFFSET = 0x9A00; |
| 26 | constexpr u32 BATTERY_FULL = 2; | 27 | constexpr u32 BATTERY_FULL = 2; |
| @@ -48,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad( | |||
| 48 | return NPadControllerType::JoyRight; | 49 | return NPadControllerType::JoyRight; |
| 49 | case Settings::ControllerType::Handheld: | 50 | case Settings::ControllerType::Handheld: |
| 50 | return NPadControllerType::Handheld; | 51 | return NPadControllerType::Handheld; |
| 52 | case Settings::ControllerType::GameCube: | ||
| 53 | return NPadControllerType::GameCube; | ||
| 51 | default: | 54 | default: |
| 52 | UNREACHABLE(); | 55 | UNREACHABLE(); |
| 53 | return NPadControllerType::ProController; | 56 | return NPadControllerType::ProController; |
| @@ -67,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType( | |||
| 67 | return Settings::ControllerType::RightJoycon; | 70 | return Settings::ControllerType::RightJoycon; |
| 68 | case NPadControllerType::Handheld: | 71 | case NPadControllerType::Handheld: |
| 69 | return Settings::ControllerType::Handheld; | 72 | return Settings::ControllerType::Handheld; |
| 73 | case NPadControllerType::GameCube: | ||
| 74 | return Settings::ControllerType::GameCube; | ||
| 70 | default: | 75 | default: |
| 71 | UNREACHABLE(); | 76 | UNREACHABLE(); |
| 72 | return Settings::ControllerType::ProController; | 77 | return Settings::ControllerType::ProController; |
| @@ -209,6 +214,13 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { | |||
| 209 | controller.assignment_mode = NpadAssignments::Single; | 214 | controller.assignment_mode = NpadAssignments::Single; |
| 210 | controller.footer_type = AppletFooterUiType::JoyRightHorizontal; | 215 | controller.footer_type = AppletFooterUiType::JoyRightHorizontal; |
| 211 | break; | 216 | break; |
| 217 | case NPadControllerType::GameCube: | ||
| 218 | controller.style_set.gamecube.Assign(1); | ||
| 219 | // The GC Controller behaves like a wired Pro Controller | ||
| 220 | controller.device_type.fullkey.Assign(1); | ||
| 221 | controller.system_properties.is_vertical.Assign(1); | ||
| 222 | controller.system_properties.use_plus.Assign(1); | ||
| 223 | break; | ||
| 212 | case NPadControllerType::Pokeball: | 224 | case NPadControllerType::Pokeball: |
| 213 | controller.style_set.palma.Assign(1); | 225 | controller.style_set.palma.Assign(1); |
| 214 | controller.device_type.palma.Assign(1); | 226 | controller.device_type.palma.Assign(1); |
| @@ -259,6 +271,7 @@ void Controller_NPad::OnInit() { | |||
| 259 | style.joycon_right.Assign(1); | 271 | style.joycon_right.Assign(1); |
| 260 | style.joycon_dual.Assign(1); | 272 | style.joycon_dual.Assign(1); |
| 261 | style.fullkey.Assign(1); | 273 | style.fullkey.Assign(1); |
| 274 | style.gamecube.Assign(1); | ||
| 262 | style.palma.Assign(1); | 275 | style.palma.Assign(1); |
| 263 | } | 276 | } |
| 264 | 277 | ||
| @@ -339,6 +352,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { | |||
| 339 | auto& pad_state = npad_pad_states[controller_idx].pad_states; | 352 | auto& pad_state = npad_pad_states[controller_idx].pad_states; |
| 340 | auto& lstick_entry = npad_pad_states[controller_idx].l_stick; | 353 | auto& lstick_entry = npad_pad_states[controller_idx].l_stick; |
| 341 | auto& rstick_entry = npad_pad_states[controller_idx].r_stick; | 354 | auto& rstick_entry = npad_pad_states[controller_idx].r_stick; |
| 355 | auto& trigger_entry = npad_trigger_states[controller_idx]; | ||
| 342 | const auto& button_state = buttons[controller_idx]; | 356 | const auto& button_state = buttons[controller_idx]; |
| 343 | const auto& analog_state = sticks[controller_idx]; | 357 | const auto& analog_state = sticks[controller_idx]; |
| 344 | const auto [stick_l_x_f, stick_l_y_f] = | 358 | const auto [stick_l_x_f, stick_l_y_f] = |
| @@ -404,6 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { | |||
| 404 | pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); | 418 | pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); |
| 405 | pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); | 419 | pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); |
| 406 | } | 420 | } |
| 421 | |||
| 422 | if (controller_type == NPadControllerType::GameCube) { | ||
| 423 | trigger_entry.l_analog = static_cast<s32>( | ||
| 424 | button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); | ||
| 425 | trigger_entry.r_analog = static_cast<s32>( | ||
| 426 | button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0); | ||
| 427 | pad_state.zl.Assign(false); | ||
| 428 | pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); | ||
| 429 | pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); | ||
| 430 | pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); | ||
| 431 | } | ||
| 407 | } | 432 | } |
| 408 | 433 | ||
| 409 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, | 434 | 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* | |||
| 418 | &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states, | 443 | &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states, |
| 419 | &npad.system_ext_states}; | 444 | &npad.system_ext_states}; |
| 420 | 445 | ||
| 446 | // There is the posibility to have more controllers with analog triggers | ||
| 447 | const std::array<TriggerGeneric*, 1> controller_triggers{ | ||
| 448 | &npad.gc_trigger_states, | ||
| 449 | }; | ||
| 450 | |||
| 421 | for (auto* main_controller : controller_npads) { | 451 | for (auto* main_controller : controller_npads) { |
| 422 | main_controller->common.entry_count = 16; | 452 | main_controller->common.entry_count = 16; |
| 423 | main_controller->common.total_entry_count = 17; | 453 | main_controller->common.total_entry_count = 17; |
| @@ -435,6 +465,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* | |||
| 435 | cur_entry.timestamp2 = cur_entry.timestamp; | 465 | cur_entry.timestamp2 = cur_entry.timestamp; |
| 436 | } | 466 | } |
| 437 | 467 | ||
| 468 | for (auto* analog_trigger : controller_triggers) { | ||
| 469 | analog_trigger->entry_count = 16; | ||
| 470 | analog_trigger->total_entry_count = 17; | ||
| 471 | |||
| 472 | const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; | ||
| 473 | |||
| 474 | analog_trigger->timestamp = core_timing.GetCPUTicks(); | ||
| 475 | analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17; | ||
| 476 | |||
| 477 | auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index]; | ||
| 478 | |||
| 479 | cur_entry.timestamp = last_entry.timestamp + 1; | ||
| 480 | cur_entry.timestamp2 = cur_entry.timestamp; | ||
| 481 | } | ||
| 482 | |||
| 438 | const auto& controller_type = connected_controllers[i].type; | 483 | const auto& controller_type = connected_controllers[i].type; |
| 439 | 484 | ||
| 440 | if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { | 485 | 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* | |||
| 444 | 489 | ||
| 445 | RequestPadStateUpdate(npad_index); | 490 | RequestPadStateUpdate(npad_index); |
| 446 | auto& pad_state = npad_pad_states[npad_index]; | 491 | auto& pad_state = npad_pad_states[npad_index]; |
| 492 | auto& trigger_state = npad_trigger_states[npad_index]; | ||
| 447 | 493 | ||
| 448 | auto& main_controller = | 494 | auto& main_controller = |
| 449 | npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index]; | 495 | 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* | |||
| 456 | auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index]; | 502 | auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index]; |
| 457 | auto& libnx_entry = | 503 | auto& libnx_entry = |
| 458 | npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; | 504 | npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; |
| 505 | auto& trigger_entry = | ||
| 506 | npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index]; | ||
| 459 | 507 | ||
| 460 | libnx_entry.connection_status.raw = 0; | 508 | libnx_entry.connection_status.raw = 0; |
| 461 | libnx_entry.connection_status.is_connected.Assign(1); | 509 | libnx_entry.connection_status.is_connected.Assign(1); |
| @@ -524,6 +572,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* | |||
| 524 | 572 | ||
| 525 | libnx_entry.connection_status.is_right_connected.Assign(1); | 573 | libnx_entry.connection_status.is_right_connected.Assign(1); |
| 526 | break; | 574 | break; |
| 575 | case NPadControllerType::GameCube: | ||
| 576 | main_controller.connection_status.raw = 0; | ||
| 577 | main_controller.connection_status.is_connected.Assign(1); | ||
| 578 | main_controller.connection_status.is_wired.Assign(1); | ||
| 579 | main_controller.pad.pad_states.raw = pad_state.pad_states.raw; | ||
| 580 | main_controller.pad.l_stick = pad_state.l_stick; | ||
| 581 | main_controller.pad.r_stick = pad_state.r_stick; | ||
| 582 | trigger_entry.l_analog = trigger_state.l_analog; | ||
| 583 | trigger_entry.r_analog = trigger_state.r_analog; | ||
| 584 | |||
| 585 | libnx_entry.connection_status.is_wired.Assign(1); | ||
| 586 | break; | ||
| 527 | case NPadControllerType::Pokeball: | 587 | case NPadControllerType::Pokeball: |
| 528 | pokeball_entry.connection_status.raw = 0; | 588 | pokeball_entry.connection_status.raw = 0; |
| 529 | pokeball_entry.connection_status.is_connected.Assign(1); | 589 | pokeball_entry.connection_status.is_connected.Assign(1); |
| @@ -674,6 +734,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing | |||
| 674 | right_sixaxis_entry.orientation = motion_devices[1].orientation; | 734 | right_sixaxis_entry.orientation = motion_devices[1].orientation; |
| 675 | } | 735 | } |
| 676 | break; | 736 | break; |
| 737 | case NPadControllerType::GameCube: | ||
| 677 | case NPadControllerType::Pokeball: | 738 | case NPadControllerType::Pokeball: |
| 678 | break; | 739 | break; |
| 679 | } | 740 | } |
| @@ -1135,6 +1196,8 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const | |||
| 1135 | return style.joycon_left; | 1196 | return style.joycon_left; |
| 1136 | case NPadControllerType::JoyRight: | 1197 | case NPadControllerType::JoyRight: |
| 1137 | return style.joycon_right; | 1198 | return style.joycon_right; |
| 1199 | case NPadControllerType::GameCube: | ||
| 1200 | return style.gamecube; | ||
| 1138 | case NPadControllerType::Pokeball: | 1201 | case NPadControllerType::Pokeball: |
| 1139 | return style.palma; | 1202 | return style.palma; |
| 1140 | default: | 1203 | 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: | |||
| 51 | JoyDual, | 51 | JoyDual, |
| 52 | JoyLeft, | 52 | JoyLeft, |
| 53 | JoyRight, | 53 | JoyRight, |
| 54 | GameCube, | ||
| 54 | Pokeball, | 55 | Pokeball, |
| 55 | }; | 56 | }; |
| 56 | 57 | ||
| @@ -60,6 +61,7 @@ public: | |||
| 60 | JoyconDual = 5, | 61 | JoyconDual = 5, |
| 61 | JoyconLeft = 6, | 62 | JoyconLeft = 6, |
| 62 | JoyconRight = 7, | 63 | JoyconRight = 7, |
| 64 | GameCube = 8, | ||
| 63 | Pokeball = 9, | 65 | Pokeball = 9, |
| 64 | MaxNpadType = 10, | 66 | MaxNpadType = 10, |
| 65 | }; | 67 | }; |
| @@ -389,6 +391,25 @@ private: | |||
| 389 | }; | 391 | }; |
| 390 | static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); | 392 | static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); |
| 391 | 393 | ||
| 394 | struct TriggerState { | ||
| 395 | s64_le timestamp{}; | ||
| 396 | s64_le timestamp2{}; | ||
| 397 | s32_le l_analog{}; | ||
| 398 | s32_le r_analog{}; | ||
| 399 | }; | ||
| 400 | static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size"); | ||
| 401 | |||
| 402 | struct TriggerGeneric { | ||
| 403 | INSERT_PADDING_BYTES(0x4); | ||
| 404 | s64_le timestamp; | ||
| 405 | INSERT_PADDING_BYTES(0x4); | ||
| 406 | s64_le total_entry_count; | ||
| 407 | s64_le last_entry_index; | ||
| 408 | s64_le entry_count; | ||
| 409 | std::array<TriggerState, 17> trigger{}; | ||
| 410 | }; | ||
| 411 | static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size"); | ||
| 412 | |||
| 392 | struct NPadSystemProperties { | 413 | struct NPadSystemProperties { |
| 393 | union { | 414 | union { |
| 394 | s64_le raw{}; | 415 | s64_le raw{}; |
| @@ -509,7 +530,9 @@ private: | |||
| 509 | AppletFooterUiType footer_type; | 530 | AppletFooterUiType footer_type; |
| 510 | // nfc_states needs to be checked switchbrew does not match with HW | 531 | // nfc_states needs to be checked switchbrew does not match with HW |
| 511 | NfcXcdHandle nfc_states; | 532 | NfcXcdHandle nfc_states; |
| 512 | INSERT_PADDING_BYTES(0xdef); | 533 | INSERT_PADDING_BYTES(0x8); // Mutex |
| 534 | TriggerGeneric gc_trigger_states; | ||
| 535 | INSERT_PADDING_BYTES(0xc1f); | ||
| 513 | }; | 536 | }; |
| 514 | static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); | 537 | static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); |
| 515 | 538 | ||
| @@ -560,6 +583,7 @@ private: | |||
| 560 | f32 sixaxis_fusion_parameter2{}; | 583 | f32 sixaxis_fusion_parameter2{}; |
| 561 | bool sixaxis_at_rest{true}; | 584 | bool sixaxis_at_rest{true}; |
| 562 | std::array<ControllerPad, 10> npad_pad_states{}; | 585 | std::array<ControllerPad, 10> npad_pad_states{}; |
| 586 | std::array<TriggerState, 10> npad_trigger_states{}; | ||
| 563 | bool is_in_lr_assignment_mode{false}; | 587 | bool is_in_lr_assignment_mode{false}; |
| 564 | Core::System& system; | 588 | Core::System& system; |
| 565 | }; | 589 | }; |
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 @@ | |||
| 1 | // Copyright 2021 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "core/hle/result.h" | ||
| 8 | |||
| 9 | namespace Service::LDN { | ||
| 10 | |||
| 11 | constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22}; | ||
| 12 | |||
| 13 | } // 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 @@ | |||
| 6 | 6 | ||
| 7 | #include "core/hle/ipc_helpers.h" | 7 | #include "core/hle/ipc_helpers.h" |
| 8 | #include "core/hle/result.h" | 8 | #include "core/hle/result.h" |
| 9 | #include "core/hle/service/ldn/errors.h" | ||
| 9 | #include "core/hle/service/ldn/ldn.h" | 10 | #include "core/hle/service/ldn/ldn.h" |
| 10 | #include "core/hle/service/sm/sm.h" | 11 | #include "core/hle/service/sm/sm.h" |
| 11 | 12 | ||
| @@ -103,7 +104,7 @@ public: | |||
| 103 | : ServiceFramework{system_, "IUserLocalCommunicationService"} { | 104 | : ServiceFramework{system_, "IUserLocalCommunicationService"} { |
| 104 | // clang-format off | 105 | // clang-format off |
| 105 | static const FunctionInfo functions[] = { | 106 | static const FunctionInfo functions[] = { |
| 106 | {0, nullptr, "GetState"}, | 107 | {0, &IUserLocalCommunicationService::GetState, "GetState"}, |
| 107 | {1, nullptr, "GetNetworkInfo"}, | 108 | {1, nullptr, "GetNetworkInfo"}, |
| 108 | {2, nullptr, "GetIpv4Address"}, | 109 | {2, nullptr, "GetIpv4Address"}, |
| 109 | {3, nullptr, "GetDisconnectReason"}, | 110 | {3, nullptr, "GetDisconnectReason"}, |
| @@ -138,13 +139,38 @@ public: | |||
| 138 | RegisterHandlers(functions); | 139 | RegisterHandlers(functions); |
| 139 | } | 140 | } |
| 140 | 141 | ||
| 141 | void Initialize2(Kernel::HLERequestContext& ctx) { | 142 | void GetState(Kernel::HLERequestContext& ctx) { |
| 142 | LOG_WARNING(Service_LDN, "(STUBBED) called"); | 143 | LOG_WARNING(Service_LDN, "(STUBBED) called"); |
| 143 | // Result success seem make this services start network and continue. | 144 | |
| 144 | // If we just pass result error then it will stop and maybe try again and again. | 145 | IPC::ResponseBuilder rb{ctx, 3}; |
| 146 | |||
| 147 | // Indicate a network error, as we do not actually emulate LDN | ||
| 148 | rb.Push(static_cast<u32>(State::Error)); | ||
| 149 | |||
| 150 | rb.Push(RESULT_SUCCESS); | ||
| 151 | } | ||
| 152 | |||
| 153 | void Initialize2(Kernel::HLERequestContext& ctx) { | ||
| 154 | LOG_DEBUG(Service_LDN, "called"); | ||
| 155 | |||
| 156 | is_initialized = true; | ||
| 157 | |||
| 145 | IPC::ResponseBuilder rb{ctx, 2}; | 158 | IPC::ResponseBuilder rb{ctx, 2}; |
| 146 | rb.Push(RESULT_UNKNOWN); | 159 | rb.Push(RESULT_SUCCESS); |
| 147 | } | 160 | } |
| 161 | |||
| 162 | private: | ||
| 163 | enum class State { | ||
| 164 | None, | ||
| 165 | Initialized, | ||
| 166 | AccessPointOpened, | ||
| 167 | AccessPointCreated, | ||
| 168 | StationOpened, | ||
| 169 | StationConnected, | ||
| 170 | Error, | ||
| 171 | }; | ||
| 172 | |||
| 173 | bool is_initialized{}; | ||
| 148 | }; | 174 | }; |
| 149 | 175 | ||
| 150 | class LDNS final : public ServiceFramework<LDNS> { | 176 | 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() { | |||
| 717 | if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { | 717 | if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { |
| 718 | LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); | 718 | LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); |
| 719 | } | 719 | } |
| 720 | // these hints are only defined on sdl2.0.9 or higher | ||
| 721 | #if SDL_VERSION_ATLEAST(2, 0, 9) | ||
| 722 | #if !SDL_VERSION_ATLEAST(2, 0, 12) | ||
| 723 | // There are also hints to toggle the individual drivers if needed. | ||
| 724 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0"); | ||
| 725 | #endif | ||
| 726 | #endif | ||
| 720 | 727 | ||
| 721 | SDL_AddEventWatch(&SDLEventWatcher, this); | 728 | SDL_AddEventWatch(&SDLEventWatcher, this); |
| 722 | 729 | ||
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 { | |||
| 340 | LeftJoycon, | 340 | LeftJoycon, |
| 341 | RightJoycon, | 341 | RightJoycon, |
| 342 | Handheld, | 342 | Handheld, |
| 343 | GameCube, | ||
| 343 | }; | 344 | }; |
| 344 | 345 | ||
| 345 | struct PlayerInput { | 346 | 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, | |||
| 763 | } | 763 | } |
| 764 | } | 764 | } |
| 765 | 765 | ||
| 766 | GLuint Image::StorageHandle() noexcept { | ||
| 767 | switch (info.format) { | ||
| 768 | case PixelFormat::A8B8G8R8_SRGB: | ||
| 769 | case PixelFormat::B8G8R8A8_SRGB: | ||
| 770 | case PixelFormat::BC1_RGBA_SRGB: | ||
| 771 | case PixelFormat::BC2_SRGB: | ||
| 772 | case PixelFormat::BC3_SRGB: | ||
| 773 | case PixelFormat::BC7_SRGB: | ||
| 774 | case PixelFormat::ASTC_2D_4X4_SRGB: | ||
| 775 | case PixelFormat::ASTC_2D_8X8_SRGB: | ||
| 776 | case PixelFormat::ASTC_2D_8X5_SRGB: | ||
| 777 | case PixelFormat::ASTC_2D_5X4_SRGB: | ||
| 778 | case PixelFormat::ASTC_2D_5X5_SRGB: | ||
| 779 | case PixelFormat::ASTC_2D_10X8_SRGB: | ||
| 780 | case PixelFormat::ASTC_2D_6X6_SRGB: | ||
| 781 | case PixelFormat::ASTC_2D_10X10_SRGB: | ||
| 782 | case PixelFormat::ASTC_2D_12X12_SRGB: | ||
| 783 | case PixelFormat::ASTC_2D_8X6_SRGB: | ||
| 784 | case PixelFormat::ASTC_2D_6X5_SRGB: | ||
| 785 | if (store_view.handle != 0) { | ||
| 786 | return store_view.handle; | ||
| 787 | } | ||
| 788 | store_view.Create(); | ||
| 789 | glTextureView(store_view.handle, ImageTarget(info), texture.handle, GL_RGBA8, 0, | ||
| 790 | info.resources.levels, 0, info.resources.layers); | ||
| 791 | return store_view.handle; | ||
| 792 | default: | ||
| 793 | return texture.handle; | ||
| 794 | } | ||
| 795 | } | ||
| 796 | |||
| 766 | void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) { | 797 | void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) { |
| 767 | // Compressed formats don't have a pixel format or type | 798 | // Compressed formats don't have a pixel format or type |
| 768 | const bool is_compressed = gl_format == GL_NONE; | 799 | 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: | |||
| 145 | 145 | ||
| 146 | void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); | 146 | void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); |
| 147 | 147 | ||
| 148 | GLuint StorageHandle() noexcept; | ||
| 149 | |||
| 148 | GLuint Handle() const noexcept { | 150 | GLuint Handle() const noexcept { |
| 149 | return texture.handle; | 151 | return texture.handle; |
| 150 | } | 152 | } |
| @@ -155,8 +157,8 @@ private: | |||
| 155 | void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); | 157 | void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); |
| 156 | 158 | ||
| 157 | OGLTexture texture; | 159 | OGLTexture texture; |
| 158 | OGLTextureView store_view; | ||
| 159 | OGLBuffer buffer; | 160 | OGLBuffer buffer; |
| 161 | OGLTextureView store_view; | ||
| 160 | GLenum gl_internal_format = GL_NONE; | 162 | GLenum gl_internal_format = GL_NONE; |
| 161 | GLenum gl_format = GL_NONE; | 163 | GLenum gl_format = GL_NONE; |
| 162 | GLenum gl_type = GL_NONE; | 164 | 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, | |||
| 93 | glUniform1ui(7, params.block_height_mask); | 93 | glUniform1ui(7, params.block_height_mask); |
| 94 | glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, | 94 | glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, |
| 95 | image.guest_size_bytes - swizzle.buffer_offset); | 95 | image.guest_size_bytes - swizzle.buffer_offset); |
| 96 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, | 96 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, |
| 97 | GL_WRITE_ONLY, store_format); | 97 | GL_WRITE_ONLY, store_format); |
| 98 | glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers); | 98 | glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers); |
| 99 | } | 99 | } |
| @@ -134,7 +134,7 @@ void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map, | |||
| 134 | glUniform1ui(9, params.block_depth_mask); | 134 | glUniform1ui(9, params.block_depth_mask); |
| 135 | glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, | 135 | glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, |
| 136 | image.guest_size_bytes - swizzle.buffer_offset); | 136 | image.guest_size_bytes - swizzle.buffer_offset); |
| 137 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, | 137 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0, |
| 138 | GL_WRITE_ONLY, store_format); | 138 | GL_WRITE_ONLY, store_format); |
| 139 | glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z); | 139 | glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z); |
| 140 | } | 140 | } |
| @@ -164,7 +164,8 @@ void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map, | |||
| 164 | glUniform2i(LOC_DESTINATION, 0, 0); | 164 | glUniform2i(LOC_DESTINATION, 0, 0); |
| 165 | glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block); | 165 | glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block); |
| 166 | glUniform1ui(LOC_PITCH, pitch); | 166 | glUniform1ui(LOC_PITCH, pitch); |
| 167 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, format); | 167 | glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, |
| 168 | format); | ||
| 168 | for (const SwizzleParameters& swizzle : swizzles) { | 169 | for (const SwizzleParameters& swizzle : swizzles) { |
| 169 | const Extent3D num_tiles = swizzle.num_tiles; | 170 | const Extent3D num_tiles = swizzle.num_tiles; |
| 170 | const size_t input_offset = swizzle.buffer_offset + map.offset; | 171 | 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 | |||
| 195 | 196 | ||
| 196 | glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z); | 197 | glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z); |
| 197 | glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z); | 198 | glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z); |
| 198 | glBindImageTexture(BINDING_INPUT_IMAGE, src_image.Handle(), copy.src_subresource.base_level, | 199 | glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(), |
| 199 | GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); | 200 | copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); |
| 200 | glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.Handle(), | 201 | glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(), |
| 201 | copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI); | 202 | copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI); |
| 202 | glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth); | 203 | glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth); |
| 203 | } | 204 | } |
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 @@ | |||
| 12 | #include "common/cityhash.h" | 12 | #include "common/cityhash.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "video_core/renderer_vulkan/fixed_pipeline_state.h" | 14 | #include "video_core/renderer_vulkan/fixed_pipeline_state.h" |
| 15 | #include "video_core/renderer_vulkan/vk_state_tracker.h" | ||
| 15 | 16 | ||
| 16 | namespace Vulkan { | 17 | namespace Vulkan { |
| 17 | 18 | ||
| 18 | namespace { | 19 | namespace { |
| 19 | 20 | ||
| 20 | constexpr std::size_t POINT = 0; | 21 | constexpr size_t POINT = 0; |
| 21 | constexpr std::size_t LINE = 1; | 22 | constexpr size_t LINE = 1; |
| 22 | constexpr std::size_t POLYGON = 2; | 23 | constexpr size_t POLYGON = 2; |
| 23 | constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { | 24 | constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { |
| 24 | POINT, // Points | 25 | POINT, // Points |
| 25 | LINE, // Lines | 26 | LINE, // Lines |
| @@ -40,10 +41,14 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { | |||
| 40 | 41 | ||
| 41 | } // Anonymous namespace | 42 | } // Anonymous namespace |
| 42 | 43 | ||
| 43 | void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { | 44 | void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, |
| 44 | const std::array enabled_lut = {regs.polygon_offset_point_enable, | 45 | bool has_extended_dynamic_state) { |
| 45 | regs.polygon_offset_line_enable, | 46 | const Maxwell& regs = maxwell3d.regs; |
| 46 | regs.polygon_offset_fill_enable}; | 47 | const std::array enabled_lut{ |
| 48 | regs.polygon_offset_point_enable, | ||
| 49 | regs.polygon_offset_line_enable, | ||
| 50 | regs.polygon_offset_fill_enable, | ||
| 51 | }; | ||
| 47 | const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); | 52 | const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); |
| 48 | 53 | ||
| 49 | raw1 = 0; | 54 | raw1 = 0; |
| @@ -64,45 +69,53 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta | |||
| 64 | 69 | ||
| 65 | raw2 = 0; | 70 | raw2 = 0; |
| 66 | const auto test_func = | 71 | const auto test_func = |
| 67 | regs.alpha_test_enabled == 1 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; | 72 | regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; |
| 68 | alpha_test_func.Assign(PackComparisonOp(test_func)); | 73 | alpha_test_func.Assign(PackComparisonOp(test_func)); |
| 69 | early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0); | 74 | early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0); |
| 70 | 75 | ||
| 71 | alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref); | 76 | alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref); |
| 72 | point_size = Common::BitCast<u32>(regs.point_size); | 77 | point_size = Common::BitCast<u32>(regs.point_size); |
| 73 | 78 | ||
| 74 | for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { | 79 | if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) { |
| 75 | binding_divisors[index] = | 80 | maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false; |
| 76 | regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0; | 81 | for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { |
| 82 | const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index); | ||
| 83 | binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0; | ||
| 84 | } | ||
| 77 | } | 85 | } |
| 78 | 86 | if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) { | |
| 79 | for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { | 87 | maxwell3d.dirty.flags[Dirty::VertexAttributes] = false; |
| 80 | const auto& input = regs.vertex_attrib_format[index]; | 88 | for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { |
| 81 | auto& attribute = attributes[index]; | 89 | const auto& input = regs.vertex_attrib_format[index]; |
| 82 | attribute.raw = 0; | 90 | auto& attribute = attributes[index]; |
| 83 | attribute.enabled.Assign(input.IsConstant() ? 0 : 1); | 91 | attribute.raw = 0; |
| 84 | attribute.buffer.Assign(input.buffer); | 92 | attribute.enabled.Assign(input.IsConstant() ? 0 : 1); |
| 85 | attribute.offset.Assign(input.offset); | 93 | attribute.buffer.Assign(input.buffer); |
| 86 | attribute.type.Assign(static_cast<u32>(input.type.Value())); | 94 | attribute.offset.Assign(input.offset); |
| 87 | attribute.size.Assign(static_cast<u32>(input.size.Value())); | 95 | attribute.type.Assign(static_cast<u32>(input.type.Value())); |
| 88 | attribute.binding_index_enabled.Assign(regs.vertex_array[index].IsEnabled() ? 1 : 0); | 96 | attribute.size.Assign(static_cast<u32>(input.size.Value())); |
| 97 | } | ||
| 89 | } | 98 | } |
| 90 | 99 | if (maxwell3d.dirty.flags[Dirty::Blending]) { | |
| 91 | for (std::size_t index = 0; index < std::size(attachments); ++index) { | 100 | maxwell3d.dirty.flags[Dirty::Blending] = false; |
| 92 | attachments[index].Fill(regs, index); | 101 | for (size_t index = 0; index < attachments.size(); ++index) { |
| 102 | attachments[index].Refresh(regs, index); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) { | ||
| 106 | maxwell3d.dirty.flags[Dirty::ViewportSwizzles] = false; | ||
| 107 | const auto& transform = regs.viewport_transform; | ||
| 108 | std::ranges::transform(transform, viewport_swizzles.begin(), [](const auto& viewport) { | ||
| 109 | return static_cast<u16>(viewport.swizzle.raw); | ||
| 110 | }); | ||
| 93 | } | 111 | } |
| 94 | |||
| 95 | const auto& transform = regs.viewport_transform; | ||
| 96 | std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(), | ||
| 97 | [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); }); | ||
| 98 | |||
| 99 | if (!has_extended_dynamic_state) { | 112 | if (!has_extended_dynamic_state) { |
| 100 | no_extended_dynamic_state.Assign(1); | 113 | no_extended_dynamic_state.Assign(1); |
| 101 | dynamic_state.Fill(regs); | 114 | dynamic_state.Refresh(regs); |
| 102 | } | 115 | } |
| 103 | } | 116 | } |
| 104 | 117 | ||
| 105 | void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { | 118 | void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) { |
| 106 | const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; | 119 | const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; |
| 107 | 120 | ||
| 108 | raw = 0; | 121 | raw = 0; |
| @@ -141,7 +154,7 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size | |||
| 141 | enable.Assign(1); | 154 | enable.Assign(1); |
| 142 | } | 155 | } |
| 143 | 156 | ||
| 144 | void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { | 157 | void FixedPipelineState::DynamicState::Refresh(const Maxwell& regs) { |
| 145 | u32 packed_front_face = PackFrontFace(regs.front_face); | 158 | u32 packed_front_face = PackFrontFace(regs.front_face); |
| 146 | if (regs.screen_y_control.triangle_rast_flip != 0) { | 159 | if (regs.screen_y_control.triangle_rast_flip != 0) { |
| 147 | // Flip front face | 160 | // Flip front face |
| @@ -178,9 +191,9 @@ void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { | |||
| 178 | }); | 191 | }); |
| 179 | } | 192 | } |
| 180 | 193 | ||
| 181 | std::size_t FixedPipelineState::Hash() const noexcept { | 194 | size_t FixedPipelineState::Hash() const noexcept { |
| 182 | const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); | 195 | const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); |
| 183 | return static_cast<std::size_t>(hash); | 196 | return static_cast<size_t>(hash); |
| 184 | } | 197 | } |
| 185 | 198 | ||
| 186 | bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { | 199 | 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 { | |||
| 58 | BitField<30, 1, u32> enable; | 58 | BitField<30, 1, u32> enable; |
| 59 | }; | 59 | }; |
| 60 | 60 | ||
| 61 | void Fill(const Maxwell& regs, std::size_t index); | 61 | void Refresh(const Maxwell& regs, size_t index); |
| 62 | 62 | ||
| 63 | constexpr std::array<bool, 4> Mask() const noexcept { | 63 | constexpr std::array<bool, 4> Mask() const noexcept { |
| 64 | return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; | 64 | return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; |
| @@ -96,8 +96,6 @@ struct FixedPipelineState { | |||
| 96 | BitField<6, 14, u32> offset; | 96 | BitField<6, 14, u32> offset; |
| 97 | BitField<20, 3, u32> type; | 97 | BitField<20, 3, u32> type; |
| 98 | BitField<23, 6, u32> size; | 98 | BitField<23, 6, u32> size; |
| 99 | // Not really an element of a vertex attribute, but it can be packed here | ||
| 100 | BitField<29, 1, u32> binding_index_enabled; | ||
| 101 | 99 | ||
| 102 | constexpr Maxwell::VertexAttribute::Type Type() const noexcept { | 100 | constexpr Maxwell::VertexAttribute::Type Type() const noexcept { |
| 103 | return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); | 101 | return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); |
| @@ -108,7 +106,7 @@ struct FixedPipelineState { | |||
| 108 | } | 106 | } |
| 109 | }; | 107 | }; |
| 110 | 108 | ||
| 111 | template <std::size_t Position> | 109 | template <size_t Position> |
| 112 | union StencilFace { | 110 | union StencilFace { |
| 113 | BitField<Position + 0, 3, u32> action_stencil_fail; | 111 | BitField<Position + 0, 3, u32> action_stencil_fail; |
| 114 | BitField<Position + 3, 3, u32> action_depth_fail; | 112 | BitField<Position + 3, 3, u32> action_depth_fail; |
| @@ -152,7 +150,7 @@ struct FixedPipelineState { | |||
| 152 | // Vertex stride is a 12 bits value, we have 4 bits to spare per element | 150 | // Vertex stride is a 12 bits value, we have 4 bits to spare per element |
| 153 | std::array<u16, Maxwell::NumVertexArrays> vertex_strides; | 151 | std::array<u16, Maxwell::NumVertexArrays> vertex_strides; |
| 154 | 152 | ||
| 155 | void Fill(const Maxwell& regs); | 153 | void Refresh(const Maxwell& regs); |
| 156 | 154 | ||
| 157 | Maxwell::ComparisonOp DepthTestFunc() const noexcept { | 155 | Maxwell::ComparisonOp DepthTestFunc() const noexcept { |
| 158 | return UnpackComparisonOp(depth_test_func); | 156 | return UnpackComparisonOp(depth_test_func); |
| @@ -199,9 +197,9 @@ struct FixedPipelineState { | |||
| 199 | std::array<u16, Maxwell::NumViewports> viewport_swizzles; | 197 | std::array<u16, Maxwell::NumViewports> viewport_swizzles; |
| 200 | DynamicState dynamic_state; | 198 | DynamicState dynamic_state; |
| 201 | 199 | ||
| 202 | void Fill(const Maxwell& regs, bool has_extended_dynamic_state); | 200 | void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state); |
| 203 | 201 | ||
| 204 | std::size_t Hash() const noexcept; | 202 | size_t Hash() const noexcept; |
| 205 | 203 | ||
| 206 | bool operator==(const FixedPipelineState& rhs) const noexcept; | 204 | bool operator==(const FixedPipelineState& rhs) const noexcept; |
| 207 | 205 | ||
| @@ -209,8 +207,8 @@ struct FixedPipelineState { | |||
| 209 | return !operator==(rhs); | 207 | return !operator==(rhs); |
| 210 | } | 208 | } |
| 211 | 209 | ||
| 212 | std::size_t Size() const noexcept { | 210 | size_t Size() const noexcept { |
| 213 | const std::size_t total_size = sizeof *this; | 211 | const size_t total_size = sizeof *this; |
| 214 | return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState)); | 212 | return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState)); |
| 215 | } | 213 | } |
| 216 | }; | 214 | }; |
| @@ -224,7 +222,7 @@ namespace std { | |||
| 224 | 222 | ||
| 225 | template <> | 223 | template <> |
| 226 | struct hash<Vulkan::FixedPipelineState> { | 224 | struct hash<Vulkan::FixedPipelineState> { |
| 227 | std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { | 225 | size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { |
| 228 | return k.Hash(); | 226 | return k.Hash(); |
| 229 | } | 227 | } |
| 230 | }; | 228 | }; |
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, | |||
| 221 | std::vector<VkVertexInputBindingDescription> vertex_bindings; | 221 | std::vector<VkVertexInputBindingDescription> vertex_bindings; |
| 222 | std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; | 222 | std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; |
| 223 | for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { | 223 | for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { |
| 224 | if (state.attributes[index].binding_index_enabled == 0) { | ||
| 225 | continue; | ||
| 226 | } | ||
| 227 | const bool instanced = state.binding_divisors[index] != 0; | 224 | const bool instanced = state.binding_divisors[index] != 0; |
| 228 | const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; | 225 | const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; |
| 229 | vertex_bindings.push_back({ | 226 | 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) { | |||
| 267 | 267 | ||
| 268 | query_cache.UpdateCounters(); | 268 | query_cache.UpdateCounters(); |
| 269 | 269 | ||
| 270 | GraphicsPipelineCacheKey key; | 270 | graphics_key.fixed_state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported()); |
| 271 | key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported()); | ||
| 272 | 271 | ||
| 273 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; | 272 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; |
| 274 | 273 | ||
| @@ -276,14 +275,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { | |||
| 276 | texture_cache.UpdateRenderTargets(false); | 275 | texture_cache.UpdateRenderTargets(false); |
| 277 | 276 | ||
| 278 | const auto shaders = pipeline_cache.GetShaders(); | 277 | const auto shaders = pipeline_cache.GetShaders(); |
| 279 | key.shaders = GetShaderAddresses(shaders); | 278 | graphics_key.shaders = GetShaderAddresses(shaders); |
| 279 | |||
| 280 | SetupShaderDescriptors(shaders, is_indexed); | 280 | SetupShaderDescriptors(shaders, is_indexed); |
| 281 | 281 | ||
| 282 | const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); | 282 | const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); |
| 283 | key.renderpass = framebuffer->RenderPass(); | 283 | graphics_key.renderpass = framebuffer->RenderPass(); |
| 284 | 284 | ||
| 285 | auto* const pipeline = | 285 | VKGraphicsPipeline* const pipeline = pipeline_cache.GetGraphicsPipeline( |
| 286 | pipeline_cache.GetGraphicsPipeline(key, framebuffer->NumColorBuffers(), async_shaders); | 286 | graphics_key, framebuffer->NumColorBuffers(), async_shaders); |
| 287 | if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) { | 287 | if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) { |
| 288 | // Async graphics pipeline was not ready. | 288 | // Async graphics pipeline was not ready. |
| 289 | return; | 289 | 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 @@ | |||
| 20 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" | 20 | #include "video_core/renderer_vulkan/vk_buffer_cache.h" |
| 21 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | 21 | #include "video_core/renderer_vulkan/vk_descriptor_pool.h" |
| 22 | #include "video_core/renderer_vulkan/vk_fence_manager.h" | 22 | #include "video_core/renderer_vulkan/vk_fence_manager.h" |
| 23 | #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" | ||
| 23 | #include "video_core/renderer_vulkan/vk_pipeline_cache.h" | 24 | #include "video_core/renderer_vulkan/vk_pipeline_cache.h" |
| 24 | #include "video_core/renderer_vulkan/vk_query_cache.h" | 25 | #include "video_core/renderer_vulkan/vk_query_cache.h" |
| 25 | #include "video_core/renderer_vulkan/vk_scheduler.h" | 26 | #include "video_core/renderer_vulkan/vk_scheduler.h" |
| @@ -173,6 +174,8 @@ private: | |||
| 173 | VKUpdateDescriptorQueue update_descriptor_queue; | 174 | VKUpdateDescriptorQueue update_descriptor_queue; |
| 174 | BlitImageHelper blit_image; | 175 | BlitImageHelper blit_image; |
| 175 | 176 | ||
| 177 | GraphicsPipelineCacheKey graphics_key; | ||
| 178 | |||
| 176 | TextureCacheRuntime texture_cache_runtime; | 179 | TextureCacheRuntime texture_cache_runtime; |
| 177 | TextureCache texture_cache; | 180 | TextureCache texture_cache; |
| 178 | BufferCacheRuntime buffer_cache_runtime; | 181 | 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; | |||
| 17 | size_t ResourcePool::CommitResource() { | 17 | size_t ResourcePool::CommitResource() { |
| 18 | // Refresh semaphore to query updated results | 18 | // Refresh semaphore to query updated results |
| 19 | master_semaphore.Refresh(); | 19 | master_semaphore.Refresh(); |
| 20 | 20 | const u64 gpu_tick = master_semaphore.KnownGpuTick(); | |
| 21 | const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> { | 21 | const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> { |
| 22 | for (size_t iterator = begin; iterator < end; ++iterator) { | 22 | for (size_t iterator = begin; iterator < end; ++iterator) { |
| 23 | if (master_semaphore.IsFree(ticks[iterator])) { | 23 | if (gpu_tick >= ticks[iterator]) { |
| 24 | ticks[iterator] = master_semaphore.CurrentTick(); | 24 | ticks[iterator] = master_semaphore.CurrentTick(); |
| 25 | return iterator; | 25 | return iterator; |
| 26 | } | 26 | } |
| 27 | } | 27 | } |
| 28 | return {}; | 28 | return std::nullopt; |
| 29 | }; | 29 | }; |
| 30 | // Try to find a free resource from the hinted position to the end. | 30 | // Try to find a free resource from the hinted position to the end. |
| 31 | auto found = search(free_iterator, ticks.size()); | 31 | std::optional<size_t> found = search(hint_iterator, ticks.size()); |
| 32 | if (!found) { | 32 | if (!found) { |
| 33 | // Search from beginning to the hinted position. | 33 | // Search from beginning to the hinted position. |
| 34 | found = search(0, free_iterator); | 34 | found = search(0, hint_iterator); |
| 35 | if (!found) { | 35 | if (!found) { |
| 36 | // Both searches failed, the pool is full; handle it. | 36 | // Both searches failed, the pool is full; handle it. |
| 37 | const size_t free_resource = ManageOverflow(); | 37 | const size_t free_resource = ManageOverflow(); |
| @@ -41,7 +41,7 @@ size_t ResourcePool::CommitResource() { | |||
| 41 | } | 41 | } |
| 42 | } | 42 | } |
| 43 | // Free iterator is hinted to the resource after the one that's been commited. | 43 | // Free iterator is hinted to the resource after the one that's been commited. |
| 44 | free_iterator = (*found + 1) % ticks.size(); | 44 | hint_iterator = (*found + 1) % ticks.size(); |
| 45 | return *found; | 45 | return *found; |
| 46 | } | 46 | } |
| 47 | 47 | ||
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: | |||
| 36 | 36 | ||
| 37 | MasterSemaphore& master_semaphore; | 37 | MasterSemaphore& master_semaphore; |
| 38 | size_t grow_step = 0; ///< Number of new resources created after an overflow | 38 | size_t grow_step = 0; ///< Number of new resources created after an overflow |
| 39 | size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found | 39 | size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found |
| 40 | std::vector<u64> ticks; ///< Ticks for each resource | 40 | std::vector<u64> ticks; ///< Ticks for each resource |
| 41 | }; | 41 | }; |
| 42 | 42 | ||
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 @@ | |||
| 18 | #define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32))) | 18 | #define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32))) |
| 19 | 19 | ||
| 20 | namespace Vulkan { | 20 | namespace Vulkan { |
| 21 | |||
| 22 | namespace { | 21 | namespace { |
| 23 | |||
| 24 | using namespace Dirty; | 22 | using namespace Dirty; |
| 25 | using namespace VideoCommon::Dirty; | 23 | using namespace VideoCommon::Dirty; |
| 26 | using Tegra::Engines::Maxwell3D; | 24 | using Tegra::Engines::Maxwell3D; |
| @@ -128,6 +126,34 @@ void SetupDirtyStencilTestEnable(Tables& tables) { | |||
| 128 | tables[0][OFF(stencil_enable)] = StencilTestEnable; | 126 | tables[0][OFF(stencil_enable)] = StencilTestEnable; |
| 129 | } | 127 | } |
| 130 | 128 | ||
| 129 | void SetupDirtyBlending(Tables& tables) { | ||
| 130 | tables[0][OFF(color_mask_common)] = Blending; | ||
| 131 | tables[0][OFF(independent_blend_enable)] = Blending; | ||
| 132 | FillBlock(tables[0], OFF(color_mask), NUM(color_mask), Blending); | ||
| 133 | FillBlock(tables[0], OFF(blend), NUM(blend), Blending); | ||
| 134 | FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending); | ||
| 135 | } | ||
| 136 | |||
| 137 | void SetupDirtyInstanceDivisors(Tables& tables) { | ||
| 138 | static constexpr size_t divisor_offset = 3; | ||
| 139 | for (size_t index = 0; index < Regs::NumVertexArrays; ++index) { | ||
| 140 | tables[0][OFF(instanced_arrays) + index] = InstanceDivisors; | ||
| 141 | tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] = | ||
| 142 | InstanceDivisors; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | void SetupDirtyVertexAttributes(Tables& tables) { | ||
| 147 | FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes); | ||
| 148 | } | ||
| 149 | |||
| 150 | void SetupDirtyViewportSwizzles(Tables& tables) { | ||
| 151 | static constexpr size_t swizzle_offset = 6; | ||
| 152 | for (size_t index = 0; index < Regs::NumViewports; ++index) { | ||
| 153 | tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = | ||
| 154 | ViewportSwizzles; | ||
| 155 | } | ||
| 156 | } | ||
| 131 | } // Anonymous namespace | 157 | } // Anonymous namespace |
| 132 | 158 | ||
| 133 | StateTracker::StateTracker(Tegra::GPU& gpu) | 159 | StateTracker::StateTracker(Tegra::GPU& gpu) |
| @@ -148,6 +174,10 @@ StateTracker::StateTracker(Tegra::GPU& gpu) | |||
| 148 | SetupDirtyFrontFace(tables); | 174 | SetupDirtyFrontFace(tables); |
| 149 | SetupDirtyStencilOp(tables); | 175 | SetupDirtyStencilOp(tables); |
| 150 | SetupDirtyStencilTestEnable(tables); | 176 | SetupDirtyStencilTestEnable(tables); |
| 177 | SetupDirtyBlending(tables); | ||
| 178 | SetupDirtyInstanceDivisors(tables); | ||
| 179 | SetupDirtyVertexAttributes(tables); | ||
| 180 | SetupDirtyViewportSwizzles(tables); | ||
| 151 | } | 181 | } |
| 152 | 182 | ||
| 153 | } // namespace Vulkan | 183 | } // 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 { | |||
| 35 | StencilOp, | 35 | StencilOp, |
| 36 | StencilTestEnable, | 36 | StencilTestEnable, |
| 37 | 37 | ||
| 38 | Blending, | ||
| 39 | InstanceDivisors, | ||
| 40 | VertexAttributes, | ||
| 41 | ViewportSwizzles, | ||
| 42 | |||
| 38 | Last | 43 | Last |
| 39 | }; | 44 | }; |
| 40 | static_assert(Last <= std::numeric_limits<u8>::max()); | 45 | 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() { | |||
| 64 | 64 | ||
| 65 | void AsyncShaders::KillWorkers() { | 65 | void AsyncShaders::KillWorkers() { |
| 66 | is_thread_exiting.store(true); | 66 | is_thread_exiting.store(true); |
| 67 | cv.notify_all(); | ||
| 67 | for (auto& thread : worker_threads) { | 68 | for (auto& thread : worker_threads) { |
| 68 | thread.detach(); | 69 | thread.detach(); |
| 69 | } | 70 | } |
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 | |||
| 151 | util/util.h | 151 | util/util.h |
| 152 | compatdb.cpp | 152 | compatdb.cpp |
| 153 | compatdb.h | 153 | compatdb.h |
| 154 | yuzu.qrc | ||
| 154 | yuzu.rc | 155 | yuzu.rc |
| 155 | ) | 156 | ) |
| 156 | 157 | ||
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, | |||
| 67 | return parameters.allow_right_joycon; | 67 | return parameters.allow_right_joycon; |
| 68 | case Settings::ControllerType::Handheld: | 68 | case Settings::ControllerType::Handheld: |
| 69 | return parameters.enable_single_mode && parameters.allow_handheld; | 69 | return parameters.enable_single_mode && parameters.allow_handheld; |
| 70 | case Settings::ControllerType::GameCube: | ||
| 71 | return parameters.allow_gamecube_controller; | ||
| 70 | default: | 72 | default: |
| 71 | return false; | 73 | return false; |
| 72 | } | 74 | } |
| @@ -370,7 +372,7 @@ void QtControllerSelectorDialog::SetSupportedControllers() { | |||
| 370 | QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); | 372 | QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); |
| 371 | } | 373 | } |
| 372 | 374 | ||
| 373 | if (parameters.allow_pro_controller) { | 375 | if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { |
| 374 | ui->controllerSupported5->setStyleSheet( | 376 | ui->controllerSupported5->setStyleSheet( |
| 375 | QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); | 377 | QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); |
| 376 | } else { | 378 | } else { |
| @@ -420,6 +422,10 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index | |||
| 420 | Settings::ControllerType::Handheld); | 422 | Settings::ControllerType::Handheld); |
| 421 | emulated_controllers[player_index]->addItem(tr("Handheld")); | 423 | emulated_controllers[player_index]->addItem(tr("Handheld")); |
| 422 | } | 424 | } |
| 425 | |||
| 426 | pairs.emplace_back(emulated_controllers[player_index]->count(), | ||
| 427 | Settings::ControllerType::GameCube); | ||
| 428 | emulated_controllers[player_index]->addItem(tr("GameCube Controller")); | ||
| 423 | } | 429 | } |
| 424 | 430 | ||
| 425 | Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( | 431 | Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( |
| @@ -461,6 +467,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) | |||
| 461 | switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), | 467 | switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), |
| 462 | player_index)) { | 468 | player_index)) { |
| 463 | case Settings::ControllerType::ProController: | 469 | case Settings::ControllerType::ProController: |
| 470 | case Settings::ControllerType::GameCube: | ||
| 464 | return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); | 471 | return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); |
| 465 | case Settings::ControllerType::DualJoyconDetached: | 472 | case Settings::ControllerType::DualJoyconDetached: |
| 466 | return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); | 473 | 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() { | |||
| 614 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) | 614 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) |
| 615 | .toString() | 615 | .toString() |
| 616 | .toStdString()); | 616 | .toStdString()); |
| 617 | FS::GetUserPath(FS::UserPath::CacheDir, | ||
| 618 | qt_config | ||
| 619 | ->value(QStringLiteral("cache_directory"), | ||
| 620 | QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir))) | ||
| 621 | .toString() | ||
| 622 | .toStdString()); | ||
| 623 | Settings::values.gamecard_inserted = | 617 | Settings::values.gamecard_inserted = |
| 624 | ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); | 618 | ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); |
| 625 | Settings::values.gamecard_current_game = | 619 | Settings::values.gamecard_current_game = |
| @@ -1218,9 +1212,6 @@ void Config::SaveDataStorageValues() { | |||
| 1218 | WriteSetting(QStringLiteral("dump_directory"), | 1212 | WriteSetting(QStringLiteral("dump_directory"), |
| 1219 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), | 1213 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), |
| 1220 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); | 1214 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); |
| 1221 | WriteSetting(QStringLiteral("cache_directory"), | ||
| 1222 | QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)), | ||
| 1223 | QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir))); | ||
| 1224 | WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); | 1215 | WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); |
| 1225 | WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, | 1216 | WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, |
| 1226 | false); | 1217 | 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) | |||
| 26 | [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); | 26 | [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); |
| 27 | connect(ui->load_path_button, &QToolButton::pressed, this, | 27 | connect(ui->load_path_button, &QToolButton::pressed, this, |
| 28 | [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); | 28 | [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); |
| 29 | connect(ui->cache_directory_button, &QToolButton::pressed, this, | ||
| 30 | [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); }); | ||
| 31 | 29 | ||
| 32 | connect(ui->reset_game_list_cache, &QPushButton::pressed, this, | 30 | connect(ui->reset_game_list_cache, &QPushButton::pressed, this, |
| 33 | &ConfigureFilesystem::ResetMetadata); | 31 | &ConfigureFilesystem::ResetMetadata); |
| @@ -50,8 +48,6 @@ void ConfigureFilesystem::setConfiguration() { | |||
| 50 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); | 48 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); |
| 51 | ui->load_path_edit->setText( | 49 | ui->load_path_edit->setText( |
| 52 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); | 50 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); |
| 53 | ui->cache_directory_edit->setText( | ||
| 54 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))); | ||
| 55 | 51 | ||
| 56 | ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); | 52 | ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); |
| 57 | ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); | 53 | ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); |
| @@ -72,9 +68,6 @@ void ConfigureFilesystem::applyConfiguration() { | |||
| 72 | ui->dump_path_edit->text().toStdString()); | 68 | ui->dump_path_edit->text().toStdString()); |
| 73 | Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, | 69 | Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, |
| 74 | ui->load_path_edit->text().toStdString()); | 70 | ui->load_path_edit->text().toStdString()); |
| 75 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir, | ||
| 76 | ui->cache_directory_edit->text().toStdString()); | ||
| 77 | Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString(); | ||
| 78 | 71 | ||
| 79 | Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); | 72 | Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); |
| 80 | Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); | 73 | Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); |
| @@ -103,9 +96,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) | |||
| 103 | case DirectoryTarget::Load: | 96 | case DirectoryTarget::Load: |
| 104 | caption = tr("Select Mod Load Directory..."); | 97 | caption = tr("Select Mod Load Directory..."); |
| 105 | break; | 98 | break; |
| 106 | case DirectoryTarget::Cache: | ||
| 107 | caption = tr("Select Cache Directory..."); | ||
| 108 | break; | ||
| 109 | } | 99 | } |
| 110 | 100 | ||
| 111 | QString str; | 101 | 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: | |||
| 32 | Gamecard, | 32 | Gamecard, |
| 33 | Dump, | 33 | Dump, |
| 34 | Load, | 34 | Load, |
| 35 | Cache, | ||
| 36 | }; | 35 | }; |
| 37 | 36 | ||
| 38 | void SetDirectory(DirectoryTarget target, QLineEdit* edit); | 37 | 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 @@ | |||
| 198 | <string>Caching</string> | 198 | <string>Caching</string> |
| 199 | </property> | 199 | </property> |
| 200 | <layout class="QGridLayout" name="gridLayout_5"> | 200 | <layout class="QGridLayout" name="gridLayout_5"> |
| 201 | <item row="0" column="0"> | 201 | <item row="0" column="0" colspan="2"> |
| 202 | <widget class="QLabel" name="label_10"> | ||
| 203 | <property name="text"> | ||
| 204 | <string>Cache Directory</string> | ||
| 205 | </property> | ||
| 206 | </widget> | ||
| 207 | </item> | ||
| 208 | <item row="0" column="1"> | ||
| 209 | <spacer name="horizontalSpacer_3"> | ||
| 210 | <property name="orientation"> | ||
| 211 | <enum>Qt::Horizontal</enum> | ||
| 212 | </property> | ||
| 213 | <property name="sizeType"> | ||
| 214 | <enum>QSizePolicy::Fixed</enum> | ||
| 215 | </property> | ||
| 216 | <property name="sizeHint" stdset="0"> | ||
| 217 | <size> | ||
| 218 | <width>40</width> | ||
| 219 | <height>20</height> | ||
| 220 | </size> | ||
| 221 | </property> | ||
| 222 | </spacer> | ||
| 223 | </item> | ||
| 224 | <item row="0" column="2"> | ||
| 225 | <widget class="QLineEdit" name="cache_directory_edit"/> | ||
| 226 | </item> | ||
| 227 | <item row="0" column="3"> | ||
| 228 | <widget class="QToolButton" name="cache_directory_button"> | ||
| 229 | <property name="text"> | ||
| 230 | <string>...</string> | ||
| 231 | </property> | ||
| 232 | </widget> | ||
| 233 | </item> | ||
| 234 | <item row="1" column="0" colspan="4"> | ||
| 235 | <layout class="QHBoxLayout" name="horizontalLayout_2"> | 202 | <layout class="QHBoxLayout" name="horizontalLayout_2"> |
| 236 | <item> | 203 | <item> |
| 237 | <widget class="QCheckBox" name="cache_game_list"> | 204 | <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 | |||
| 467 | 467 | ||
| 468 | UpdateControllerIcon(); | 468 | UpdateControllerIcon(); |
| 469 | UpdateControllerAvailableButtons(); | 469 | UpdateControllerAvailableButtons(); |
| 470 | UpdateControllerEnabledButtons(); | ||
| 471 | UpdateControllerButtonNames(); | ||
| 470 | UpdateMotionButtons(); | 472 | UpdateMotionButtons(); |
| 471 | connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { | 473 | connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { |
| 472 | UpdateControllerIcon(); | 474 | UpdateControllerIcon(); |
| 473 | UpdateControllerAvailableButtons(); | 475 | UpdateControllerAvailableButtons(); |
| 476 | UpdateControllerEnabledButtons(); | ||
| 477 | UpdateControllerButtonNames(); | ||
| 474 | UpdateMotionButtons(); | 478 | UpdateMotionButtons(); |
| 475 | }); | 479 | }); |
| 476 | 480 | ||
| @@ -558,9 +562,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 558 | &ConfigureInputPlayer::SaveProfile); | 562 | &ConfigureInputPlayer::SaveProfile); |
| 559 | 563 | ||
| 560 | LoadConfiguration(); | 564 | LoadConfiguration(); |
| 561 | |||
| 562 | // TODO(wwylele): enable this when we actually emulate it | ||
| 563 | ui->buttonHome->setEnabled(false); | ||
| 564 | ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); | 565 | ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); |
| 565 | ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); | 566 | ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); |
| 566 | } | 567 | } |
| @@ -924,6 +925,12 @@ void ConfigureInputPlayer::SetConnectableControllers() { | |||
| 924 | Settings::ControllerType::Handheld); | 925 | Settings::ControllerType::Handheld); |
| 925 | ui->comboControllerType->addItem(tr("Handheld")); | 926 | ui->comboControllerType->addItem(tr("Handheld")); |
| 926 | } | 927 | } |
| 928 | |||
| 929 | if (enable_all || npad_style_set.gamecube == 1) { | ||
| 930 | index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), | ||
| 931 | Settings::ControllerType::GameCube); | ||
| 932 | ui->comboControllerType->addItem(tr("GameCube Controller")); | ||
| 933 | } | ||
| 927 | }; | 934 | }; |
| 928 | 935 | ||
| 929 | Core::System& system{Core::System::GetInstance()}; | 936 | Core::System& system{Core::System::GetInstance()}; |
| @@ -1014,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { | |||
| 1014 | 1021 | ||
| 1015 | // List of all the widgets that will be hidden by any of the following layouts that need | 1022 | // List of all the widgets that will be hidden by any of the following layouts that need |
| 1016 | // "unhidden" after the controller type changes | 1023 | // "unhidden" after the controller type changes |
| 1017 | const std::array<QWidget*, 9> layout_show = { | 1024 | const std::array<QWidget*, 11> layout_show = { |
| 1018 | ui->buttonShoulderButtonsSLSR, | 1025 | ui->buttonShoulderButtonsSLSR, |
| 1019 | ui->horizontalSpacerShoulderButtonsWidget, | 1026 | ui->horizontalSpacerShoulderButtonsWidget, |
| 1020 | ui->horizontalSpacerShoulderButtonsWidget2, | 1027 | ui->horizontalSpacerShoulderButtonsWidget2, |
| @@ -1024,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { | |||
| 1024 | ui->buttonShoulderButtonsRight, | 1031 | ui->buttonShoulderButtonsRight, |
| 1025 | ui->buttonMiscButtonsPlusHome, | 1032 | ui->buttonMiscButtonsPlusHome, |
| 1026 | ui->bottomRight, | 1033 | ui->bottomRight, |
| 1034 | ui->buttonMiscButtonsMinusGroup, | ||
| 1035 | ui->buttonMiscButtonsScreenshotGroup, | ||
| 1027 | }; | 1036 | }; |
| 1028 | 1037 | ||
| 1029 | for (auto* widget : layout_show) { | 1038 | for (auto* widget : layout_show) { |
| @@ -1056,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { | |||
| 1056 | ui->bottomLeft, | 1065 | ui->bottomLeft, |
| 1057 | }; | 1066 | }; |
| 1058 | break; | 1067 | break; |
| 1068 | case Settings::ControllerType::GameCube: | ||
| 1069 | layout_hidden = { | ||
| 1070 | ui->buttonShoulderButtonsSLSR, | ||
| 1071 | ui->horizontalSpacerShoulderButtonsWidget2, | ||
| 1072 | ui->buttonMiscButtonsMinusGroup, | ||
| 1073 | ui->buttonMiscButtonsScreenshotGroup, | ||
| 1074 | }; | ||
| 1075 | break; | ||
| 1059 | } | 1076 | } |
| 1060 | 1077 | ||
| 1061 | for (auto* widget : layout_hidden) { | 1078 | for (auto* widget : layout_hidden) { |
| @@ -1063,6 +1080,52 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { | |||
| 1063 | } | 1080 | } |
| 1064 | } | 1081 | } |
| 1065 | 1082 | ||
| 1083 | void ConfigureInputPlayer::UpdateControllerEnabledButtons() { | ||
| 1084 | auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); | ||
| 1085 | if (debug) { | ||
| 1086 | layout = Settings::ControllerType::ProController; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | // List of all the widgets that will be disabled by any of the following layouts that need | ||
| 1090 | // "enabled" after the controller type changes | ||
| 1091 | const std::array<QWidget*, 4> layout_enable = { | ||
| 1092 | ui->buttonHome, | ||
| 1093 | ui->buttonLStickPressedGroup, | ||
| 1094 | ui->groupRStickPressed, | ||
| 1095 | ui->buttonShoulderButtonsButtonLGroup, | ||
| 1096 | }; | ||
| 1097 | |||
| 1098 | for (auto* widget : layout_enable) { | ||
| 1099 | widget->setEnabled(true); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | std::vector<QWidget*> layout_disable; | ||
| 1103 | switch (layout) { | ||
| 1104 | case Settings::ControllerType::ProController: | ||
| 1105 | case Settings::ControllerType::DualJoyconDetached: | ||
| 1106 | case Settings::ControllerType::Handheld: | ||
| 1107 | case Settings::ControllerType::LeftJoycon: | ||
| 1108 | case Settings::ControllerType::RightJoycon: | ||
| 1109 | // TODO(wwylele): enable this when we actually emulate it | ||
| 1110 | layout_disable = { | ||
| 1111 | ui->buttonHome, | ||
| 1112 | }; | ||
| 1113 | break; | ||
| 1114 | case Settings::ControllerType::GameCube: | ||
| 1115 | layout_disable = { | ||
| 1116 | ui->buttonHome, | ||
| 1117 | ui->buttonLStickPressedGroup, | ||
| 1118 | ui->groupRStickPressed, | ||
| 1119 | ui->buttonShoulderButtonsButtonLGroup, | ||
| 1120 | }; | ||
| 1121 | break; | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | for (auto* widget : layout_disable) { | ||
| 1125 | widget->setEnabled(false); | ||
| 1126 | } | ||
| 1127 | } | ||
| 1128 | |||
| 1066 | void ConfigureInputPlayer::UpdateMotionButtons() { | 1129 | void ConfigureInputPlayer::UpdateMotionButtons() { |
| 1067 | if (debug) { | 1130 | if (debug) { |
| 1068 | // Motion isn't used with the debug controller, hide both groupboxes. | 1131 | // Motion isn't used with the debug controller, hide both groupboxes. |
| @@ -1085,6 +1148,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() { | |||
| 1085 | ui->buttonMotionLeftGroup->hide(); | 1148 | ui->buttonMotionLeftGroup->hide(); |
| 1086 | ui->buttonMotionRightGroup->show(); | 1149 | ui->buttonMotionRightGroup->show(); |
| 1087 | break; | 1150 | break; |
| 1151 | case Settings::ControllerType::GameCube: | ||
| 1152 | // Hide both "Motion 1/2". | ||
| 1153 | ui->buttonMotionLeftGroup->hide(); | ||
| 1154 | ui->buttonMotionRightGroup->hide(); | ||
| 1155 | break; | ||
| 1088 | case Settings::ControllerType::DualJoyconDetached: | 1156 | case Settings::ControllerType::DualJoyconDetached: |
| 1089 | default: | 1157 | default: |
| 1090 | // Show both "Motion 1/2". | 1158 | // Show both "Motion 1/2". |
| @@ -1094,6 +1162,36 @@ void ConfigureInputPlayer::UpdateMotionButtons() { | |||
| 1094 | } | 1162 | } |
| 1095 | } | 1163 | } |
| 1096 | 1164 | ||
| 1165 | void ConfigureInputPlayer::UpdateControllerButtonNames() { | ||
| 1166 | auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); | ||
| 1167 | if (debug) { | ||
| 1168 | layout = Settings::ControllerType::ProController; | ||
| 1169 | } | ||
| 1170 | |||
| 1171 | switch (layout) { | ||
| 1172 | case Settings::ControllerType::ProController: | ||
| 1173 | case Settings::ControllerType::DualJoyconDetached: | ||
| 1174 | case Settings::ControllerType::Handheld: | ||
| 1175 | case Settings::ControllerType::LeftJoycon: | ||
| 1176 | case Settings::ControllerType::RightJoycon: | ||
| 1177 | ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus")); | ||
| 1178 | ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL")); | ||
| 1179 | ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR")); | ||
| 1180 | ui->buttonShoulderButtonsRGroup->setTitle(tr("R")); | ||
| 1181 | ui->LStick->setTitle(tr("Left Stick")); | ||
| 1182 | ui->RStick->setTitle(tr("Right Stick")); | ||
| 1183 | break; | ||
| 1184 | case Settings::ControllerType::GameCube: | ||
| 1185 | ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause")); | ||
| 1186 | ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L")); | ||
| 1187 | ui->buttonShoulderButtonsZRGroup->setTitle(tr("R")); | ||
| 1188 | ui->buttonShoulderButtonsRGroup->setTitle(tr("Z")); | ||
| 1189 | ui->LStick->setTitle(tr("Control Stick")); | ||
| 1190 | ui->RStick->setTitle(tr("C-Stick")); | ||
| 1191 | break; | ||
| 1192 | } | ||
| 1193 | } | ||
| 1194 | |||
| 1097 | void ConfigureInputPlayer::UpdateMappingWithDefaults() { | 1195 | void ConfigureInputPlayer::UpdateMappingWithDefaults() { |
| 1098 | if (ui->comboDevices->currentIndex() == 0) { | 1196 | if (ui->comboDevices->currentIndex() == 0) { |
| 1099 | return; | 1197 | 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: | |||
| 143 | /// Hides and disables controller settings based on the current controller type. | 143 | /// Hides and disables controller settings based on the current controller type. |
| 144 | void UpdateControllerAvailableButtons(); | 144 | void UpdateControllerAvailableButtons(); |
| 145 | 145 | ||
| 146 | /// Disables controller settings based on the current controller type. | ||
| 147 | void UpdateControllerEnabledButtons(); | ||
| 148 | |||
| 146 | /// Shows or hides motion groupboxes based on the current controller type. | 149 | /// Shows or hides motion groupboxes based on the current controller type. |
| 147 | void UpdateMotionButtons(); | 150 | void UpdateMotionButtons(); |
| 148 | 151 | ||
| 152 | /// Alters the button names based on the current controller type. | ||
| 153 | void UpdateControllerButtonNames(); | ||
| 154 | |||
| 149 | /// Gets the default controller mapping for this device and auto configures the input to match. | 155 | /// Gets the default controller mapping for this device and auto configures the input to match. |
| 150 | void UpdateMappingWithDefaults(); | 156 | void UpdateMappingWithDefaults(); |
| 151 | 157 | ||
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) { | |||
| 227 | case Settings::ControllerType::RightJoycon: | 227 | case Settings::ControllerType::RightJoycon: |
| 228 | DrawRightController(p, center); | 228 | DrawRightController(p, center); |
| 229 | break; | 229 | break; |
| 230 | case Settings::ControllerType::GameCube: | ||
| 231 | DrawGCController(p, center); | ||
| 232 | break; | ||
| 230 | case Settings::ControllerType::ProController: | 233 | case Settings::ControllerType::ProController: |
| 231 | default: | 234 | default: |
| 232 | DrawProController(p, center); | 235 | 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() { | |||
| 42 | 42 | ||
| 43 | QAction* ControllerDialog::toggleViewAction() { | 43 | QAction* ControllerDialog::toggleViewAction() { |
| 44 | if (toggle_view_action == nullptr) { | 44 | if (toggle_view_action == nullptr) { |
| 45 | toggle_view_action = new QAction(windowTitle(), this); | 45 | toggle_view_action = new QAction(tr("&Controller P1"), this); |
| 46 | toggle_view_action->setCheckable(true); | 46 | toggle_view_action->setCheckable(true); |
| 47 | toggle_view_action->setChecked(isVisible()); | 47 | toggle_view_action->setChecked(isVisible()); |
| 48 | connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible); | 48 | 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) { | |||
| 2770 | .arg(errors)); | 2770 | .arg(errors)); |
| 2771 | } | 2771 | } |
| 2772 | 2772 | ||
| 2773 | QProgressDialog prog; | 2773 | QProgressDialog prog(this); |
| 2774 | prog.setRange(0, 0); | 2774 | prog.setRange(0, 0); |
| 2775 | prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " | 2775 | prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " |
| 2776 | "system's performance.")); | 2776 | "system's performance.")); |
| @@ -2952,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) { | |||
| 2952 | } | 2952 | } |
| 2953 | 2953 | ||
| 2954 | void GMainWindow::UpdateUITheme() { | 2954 | void GMainWindow::UpdateUITheme() { |
| 2955 | const QString default_icons = QStringLiteral(":/icons/default"); | 2955 | const QString default_icons = QStringLiteral("default"); |
| 2956 | const QString& current_theme = UISettings::values.theme; | 2956 | const QString& current_theme = UISettings::values.theme; |
| 2957 | const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second); | 2957 | const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second); |
| 2958 | QStringList theme_paths(default_theme_paths); | 2958 | QStringList theme_paths(default_theme_paths); |
| @@ -2968,7 +2968,6 @@ void GMainWindow::UpdateUITheme() { | |||
| 2968 | qApp->setStyleSheet({}); | 2968 | qApp->setStyleSheet({}); |
| 2969 | setStyleSheet({}); | 2969 | setStyleSheet({}); |
| 2970 | } | 2970 | } |
| 2971 | theme_paths.append(default_icons); | ||
| 2972 | QIcon::setThemeName(default_icons); | 2971 | QIcon::setThemeName(default_icons); |
| 2973 | } else { | 2972 | } else { |
| 2974 | const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss")); | 2973 | const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss")); |
| @@ -2980,10 +2979,7 @@ void GMainWindow::UpdateUITheme() { | |||
| 2980 | } else { | 2979 | } else { |
| 2981 | LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); | 2980 | LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); |
| 2982 | } | 2981 | } |
| 2983 | 2982 | QIcon::setThemeName(current_theme); | |
| 2984 | const QString theme_name = QStringLiteral(":/icons/") + current_theme; | ||
| 2985 | theme_paths.append({default_icons, theme_name}); | ||
| 2986 | QIcon::setThemeName(theme_name); | ||
| 2987 | } | 2983 | } |
| 2988 | 2984 | ||
| 2989 | QIcon::setThemeSearchPaths(theme_paths); | 2985 | 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 @@ | |||
| 14 | <string>yuzu</string> | 14 | <string>yuzu</string> |
| 15 | </property> | 15 | </property> |
| 16 | <property name="windowIcon"> | 16 | <property name="windowIcon"> |
| 17 | <iconset> | 17 | <iconset resource="yuzu.qrc"> |
| 18 | <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset> | 18 | <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset> |
| 19 | </property> | 19 | </property> |
| 20 | <property name="tabShape"> | 20 | <property name="tabShape"> |
| 21 | <enum>QTabWidget::Rounded</enum> | 21 | <enum>QTabWidget::Rounded</enum> |
| @@ -303,6 +303,8 @@ | |||
| 303 | </property> | 303 | </property> |
| 304 | </action> | 304 | </action> |
| 305 | </widget> | 305 | </widget> |
| 306 | <resources/> | 306 | <resources> |
| 307 | <include location="yuzu.qrc"/> | ||
| 308 | </resources> | ||
| 307 | <connections/> | 309 | <connections/> |
| 308 | </ui> | 310 | </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 @@ | |||
| 1 | <RCC> | ||
| 2 | <qresource prefix="/img"> | ||
| 3 | <file alias="yuzu.ico">../../dist/yuzu.ico</file> | ||
| 4 | </qresource> | ||
| 5 | </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 @@ | |||
| 1 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) | 1 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) |
| 2 | 2 | ||
| 3 | function(create_resource file output filename) | ||
| 4 | # Read hex data from file | ||
| 5 | file(READ ${file} filedata HEX) | ||
| 6 | # Convert hex data for C compatibility | ||
| 7 | string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) | ||
| 8 | # Write data to output file | ||
| 9 | set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE) | ||
| 10 | file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n") | ||
| 11 | endfunction() | ||
| 12 | |||
| 3 | add_executable(yuzu-cmd | 13 | add_executable(yuzu-cmd |
| 4 | config.cpp | 14 | config.cpp |
| 5 | config.h | 15 | config.h |
| @@ -24,6 +34,9 @@ if (MSVC) | |||
| 24 | endif() | 34 | endif() |
| 25 | target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) | 35 | target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) |
| 26 | 36 | ||
| 37 | create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") | ||
| 38 | target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) | ||
| 39 | |||
| 27 | target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include) | 40 | target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include) |
| 28 | 41 | ||
| 29 | if(UNIX AND NOT APPLE) | 42 | 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() { | |||
| 329 | FS::GetUserPath( | 329 | FS::GetUserPath( |
| 330 | FS::UserPath::DumpDir, | 330 | FS::UserPath::DumpDir, |
| 331 | sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); | 331 | sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); |
| 332 | FS::GetUserPath(FS::UserPath::CacheDir, | ||
| 333 | sdl2_config->Get("Data Storage", "cache_directory", | ||
| 334 | FS::GetUserPath(FS::UserPath::CacheDir))); | ||
| 335 | Settings::values.gamecard_inserted = | 332 | Settings::values.gamecard_inserted = |
| 336 | sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); | 333 | sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); |
| 337 | Settings::values.gamecard_current_game = | 334 | 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 @@ | |||
| 12 | #include "input_common/mouse/mouse_input.h" | 12 | #include "input_common/mouse/mouse_input.h" |
| 13 | #include "input_common/sdl/sdl.h" | 13 | #include "input_common/sdl/sdl.h" |
| 14 | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | 14 | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" |
| 15 | #include "yuzu_cmd/yuzu_icon.h" | ||
| 15 | 16 | ||
| 16 | EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_) | 17 | EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_) |
| 17 | : input_subsystem{input_subsystem_} { | 18 | : input_subsystem{input_subsystem_} { |
| @@ -194,6 +195,22 @@ void EmuWindow_SDL2::WaitEvent() { | |||
| 194 | } | 195 | } |
| 195 | } | 196 | } |
| 196 | 197 | ||
| 198 | void EmuWindow_SDL2::SetWindowIcon() { | ||
| 199 | SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); | ||
| 200 | if (yuzu_icon_stream == nullptr) { | ||
| 201 | LOG_WARNING(Frontend, "Failed to create yuzu icon stream."); | ||
| 202 | return; | ||
| 203 | } | ||
| 204 | SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); | ||
| 205 | if (window_icon == nullptr) { | ||
| 206 | LOG_WARNING(Frontend, "Failed to read BMP from stream."); | ||
| 207 | return; | ||
| 208 | } | ||
| 209 | // The icon is attached to the window pointer | ||
| 210 | SDL_SetWindowIcon(render_window, window_icon); | ||
| 211 | SDL_FreeSurface(window_icon); | ||
| 212 | } | ||
| 213 | |||
| 197 | void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { | 214 | void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { |
| 198 | SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); | 215 | SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); |
| 199 | } | 216 | } |
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: | |||
| 32 | /// Wait for the next event on the main thread. | 32 | /// Wait for the next event on the main thread. |
| 33 | void WaitEvent(); | 33 | void WaitEvent(); |
| 34 | 34 | ||
| 35 | // Sets the window icon from yuzu.bmp | ||
| 36 | void SetWindowIcon(); | ||
| 37 | |||
| 35 | protected: | 38 | protected: |
| 36 | /// Called by WaitEvent when a key is pressed or released. | 39 | /// Called by WaitEvent when a key is pressed or released. |
| 37 | void OnKeyEvent(int key, u8 state); | 40 | 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 | |||
| 107 | dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, | 107 | dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, |
| 108 | SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); | 108 | SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); |
| 109 | 109 | ||
| 110 | SetWindowIcon(); | ||
| 111 | |||
| 110 | if (fullscreen) { | 112 | if (fullscreen) { |
| 111 | Fullscreen(); | 113 | Fullscreen(); |
| 112 | } | 114 | } |
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 | |||
| 35 | std::exit(EXIT_FAILURE); | 35 | std::exit(EXIT_FAILURE); |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | SetWindowIcon(); | ||
| 39 | |||
| 38 | switch (wm.subsystem) { | 40 | switch (wm.subsystem) { |
| 39 | #ifdef SDL_VIDEO_DRIVER_WINDOWS | 41 | #ifdef SDL_VIDEO_DRIVER_WINDOWS |
| 40 | case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: | 42 | case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: |