summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--externals/CMakeLists.txt3
m---------externals/soundtouch0
-rw-r--r--src/audio_core/CMakeLists.txt3
-rw-r--r--src/audio_core/cubeb_sink.cpp114
-rw-r--r--src/audio_core/null_sink.h6
-rw-r--r--src/audio_core/sink_details.cpp2
-rw-r--r--src/audio_core/sink_details.h4
-rw-r--r--src/audio_core/sink_stream.h4
-rw-r--r--src/audio_core/stream.cpp3
-rw-r--r--src/audio_core/time_stretch.cpp68
-rw-r--r--src/audio_core/time_stretch.h36
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/ring_buffer.h111
-rw-r--r--src/core/hle/ipc.h2
-rw-r--r--src/core/hle/kernel/thread.h12
-rw-r--r--src/core/hle/service/acc/acc_su.h6
-rw-r--r--src/core/hle/service/ns/pl_u.cpp152
-rw-r--r--src/core/hle/service/ns/pl_u.h8
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp6
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h6
-rw-r--r--src/core/hle/service/vi/vi.cpp2
-rw-r--r--src/core/loader/nro.cpp2
-rw-r--r--src/core/loader/nso.cpp3
-rw-r--r--src/core/settings.h1
-rw-r--r--src/core/telemetry_session.cpp3
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/ring_buffer.cpp130
-rw-r--r--src/video_core/engines/shader_bytecode.h10
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp25
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h1
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp34
-rw-r--r--src/yuzu/configuration/config.cpp3
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_audio.ui10
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp103
-rw-r--r--src/yuzu/configuration/configure_gamelist.h7
-rw-r--r--src/yuzu/debugger/wait_tree.cpp58
-rw-r--r--src/yuzu/main.cpp2
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/default_ini.h6
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
43 files changed, 749 insertions, 211 deletions
diff --git a/.gitmodules b/.gitmodules
index 4f4e8690b..e73ca99e3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,3 +31,6 @@
31[submodule "opus"] 31[submodule "opus"]
32 path = externals/opus 32 path = externals/opus
33 url = https://github.com/ogniK5377/opus.git 33 url = https://github.com/ogniK5377/opus.git
34[submodule "soundtouch"]
35 path = externals/soundtouch
36 url = https://github.com/citra-emu/ext-soundtouch.git
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 3d8e10c2b..53dcf1f1a 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -50,6 +50,9 @@ add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
50add_library(unicorn-headers INTERFACE) 50add_library(unicorn-headers INTERFACE)
51target_include_directories(unicorn-headers INTERFACE ./unicorn/include) 51target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
52 52
53# SoundTouch
54add_subdirectory(soundtouch)
55
53# Xbyak 56# Xbyak
54if (ARCHITECTURE_x86_64) 57if (ARCHITECTURE_x86_64)
55 # Defined before "dynarmic" above 58 # Defined before "dynarmic" above
diff --git a/externals/soundtouch b/externals/soundtouch
new file mode 160000
Subproject 060181eaf273180d3a7e87349895bd0cb6ccbf4
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 82e4850f7..c381dbe1d 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -17,6 +17,8 @@ add_library(audio_core STATIC
17 sink_stream.h 17 sink_stream.h
18 stream.cpp 18 stream.cpp
19 stream.h 19 stream.h
20 time_stretch.cpp
21 time_stretch.h
20 22
21 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> 23 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
22) 24)
@@ -24,6 +26,7 @@ add_library(audio_core STATIC
24create_target_directory_groups(audio_core) 26create_target_directory_groups(audio_core)
25 27
26target_link_libraries(audio_core PUBLIC common core) 28target_link_libraries(audio_core PUBLIC common core)
29target_link_libraries(audio_core PRIVATE SoundTouch)
27 30
28if(ENABLE_CUBEB) 31if(ENABLE_CUBEB)
29 target_link_libraries(audio_core PRIVATE cubeb) 32 target_link_libraries(audio_core PRIVATE cubeb)
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 5a1177d0c..79155a7a0 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -3,27 +3,23 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <atomic>
6#include <cstring> 7#include <cstring>
7#include <mutex>
8
9#include "audio_core/cubeb_sink.h" 8#include "audio_core/cubeb_sink.h"
10#include "audio_core/stream.h" 9#include "audio_core/stream.h"
10#include "audio_core/time_stretch.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/ring_buffer.h"
13#include "core/settings.h"
12 14
13namespace AudioCore { 15namespace AudioCore {
14 16
15class SinkStreamImpl final : public SinkStream { 17class CubebSinkStream final : public SinkStream {
16public: 18public:
17 SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, 19 CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
18 const std::string& name) 20 const std::string& name)
19 : ctx{ctx}, num_channels{num_channels_} { 21 : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
20 22 num_channels} {
21 if (num_channels == 6) {
22 // 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2
23 // channel for now
24 is_6_channel = true;
25 num_channels = 2;
26 }
27 23
28 cubeb_stream_params params{}; 24 cubeb_stream_params params{};
29 params.rate = sample_rate; 25 params.rate = sample_rate;
@@ -38,7 +34,7 @@ public:
38 34
39 if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, 35 if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
40 &params, std::max(512u, minimum_latency), 36 &params, std::max(512u, minimum_latency),
41 &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, 37 &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback,
42 this) != CUBEB_OK) { 38 this) != CUBEB_OK) {
43 LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); 39 LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
44 return; 40 return;
@@ -50,7 +46,7 @@ public:
50 } 46 }
51 } 47 }
52 48
53 ~SinkStreamImpl() { 49 ~CubebSinkStream() {
54 if (!ctx) { 50 if (!ctx) {
55 return; 51 return;
56 } 52 }
@@ -62,27 +58,32 @@ public:
62 cubeb_stream_destroy(stream_backend); 58 cubeb_stream_destroy(stream_backend);
63 } 59 }
64 60
65 void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) override { 61 void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
66 if (!ctx) { 62 if (source_num_channels > num_channels) {
63 // Downsample 6 channels to 2
64 std::vector<s16> buf;
65 buf.reserve(samples.size() * num_channels / source_num_channels);
66 for (size_t i = 0; i < samples.size(); i += source_num_channels) {
67 for (size_t ch = 0; ch < num_channels; ch++) {
68 buf.push_back(samples[i + ch]);
69 }
70 }
71 queue.Push(buf);
67 return; 72 return;
68 } 73 }
69 74
70 std::lock_guard lock{queue_mutex}; 75 queue.Push(samples);
76 }
71 77
72 queue.reserve(queue.size() + samples.size() * GetNumChannels()); 78 size_t SamplesInQueue(u32 num_channels) const override {
79 if (!ctx)
80 return 0;
73 81
74 if (is_6_channel) { 82 return queue.Size() / num_channels;
75 // Downsample 6 channels to 2 83 }
76 const size_t sample_count_copy_size = samples.size() * 2; 84
77 queue.reserve(sample_count_copy_size); 85 void Flush() override {
78 for (size_t i = 0; i < samples.size(); i += num_channels) { 86 should_flush = true;
79 queue.push_back(samples[i]);
80 queue.push_back(samples[i + 1]);
81 }
82 } else {
83 // Copy as-is
84 std::copy(samples.begin(), samples.end(), std::back_inserter(queue));
85 }
86 } 87 }
87 88
88 u32 GetNumChannels() const { 89 u32 GetNumChannels() const {
@@ -95,10 +96,11 @@ private:
95 cubeb* ctx{}; 96 cubeb* ctx{};
96 cubeb_stream* stream_backend{}; 97 cubeb_stream* stream_backend{};
97 u32 num_channels{}; 98 u32 num_channels{};
98 bool is_6_channel{};
99 99
100 std::mutex queue_mutex; 100 Common::RingBuffer<s16, 0x10000> queue;
101 std::vector<s16> queue; 101 std::array<s16, 2> last_frame;
102 std::atomic<bool> should_flush{};
103 TimeStretcher time_stretch;
102 104
103 static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, 105 static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
104 void* output_buffer, long num_frames); 106 void* output_buffer, long num_frames);
@@ -144,38 +146,52 @@ CubebSink::~CubebSink() {
144SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, 146SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
145 const std::string& name) { 147 const std::string& name) {
146 sink_streams.push_back( 148 sink_streams.push_back(
147 std::make_unique<SinkStreamImpl>(ctx, sample_rate, num_channels, output_device, name)); 149 std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name));
148 return *sink_streams.back(); 150 return *sink_streams.back();
149} 151}
150 152
151long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, 153long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
152 void* output_buffer, long num_frames) { 154 void* output_buffer, long num_frames) {
153 SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data); 155 CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data);
154 u8* buffer = reinterpret_cast<u8*>(output_buffer); 156 u8* buffer = reinterpret_cast<u8*>(output_buffer);
155 157
156 if (!impl) { 158 if (!impl) {
157 return {}; 159 return {};
158 } 160 }
159 161
160 std::lock_guard lock{impl->queue_mutex}; 162 const size_t num_channels = impl->GetNumChannels();
163 const size_t samples_to_write = num_channels * num_frames;
164 size_t samples_written;
165
166 if (Settings::values.enable_audio_stretching) {
167 const std::vector<s16> in{impl->queue.Pop()};
168 const size_t num_in{in.size() / num_channels};
169 s16* const out{reinterpret_cast<s16*>(buffer)};
170 const size_t out_frames = impl->time_stretch.Process(in.data(), num_in, out, num_frames);
171 samples_written = out_frames * num_channels;
161 172
162 const size_t frames_to_write{ 173 if (impl->should_flush) {
163 std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))}; 174 impl->time_stretch.Flush();
175 impl->should_flush = false;
176 }
177 } else {
178 samples_written = impl->queue.Pop(buffer, samples_to_write);
179 }
164 180
165 memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels()); 181 if (samples_written >= num_channels) {
166 impl->queue.erase(impl->queue.begin(), 182 std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
167 impl->queue.begin() + frames_to_write * impl->GetNumChannels()); 183 num_channels * sizeof(s16));
184 }
168 185
169 if (frames_to_write < num_frames) { 186 // Fill the rest of the frames with last_frame
170 // Fill the rest of the frames with silence 187 for (size_t i = samples_written; i < samples_to_write; i += num_channels) {
171 memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0, 188 std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16));
172 (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels());
173 } 189 }
174 190
175 return num_frames; 191 return num_frames;
176} 192}
177 193
178void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} 194void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
179 195
180std::vector<std::string> ListCubebSinkDevices() { 196std::vector<std::string> ListCubebSinkDevices() {
181 std::vector<std::string> device_list; 197 std::vector<std::string> device_list;
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index f235d93e5..2ed0c83b6 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -21,6 +21,12 @@ public:
21private: 21private:
22 struct NullSinkStreamImpl final : SinkStream { 22 struct NullSinkStreamImpl final : SinkStream {
23 void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} 23 void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
24
25 size_t SamplesInQueue(u32 /*num_channels*/) const override {
26 return 0;
27 }
28
29 void Flush() override {}
24 } null_sink_stream; 30 } null_sink_stream;
25}; 31};
26 32
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 955ba20fb..67cf1f3b2 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -24,7 +24,7 @@ const std::vector<SinkDetails> g_sink_details = {
24 [] { return std::vector<std::string>{"null"}; }}, 24 [] { return std::vector<std::string>{"null"}; }},
25}; 25};
26 26
27const SinkDetails& GetSinkDetails(std::string sink_id) { 27const SinkDetails& GetSinkDetails(std::string_view sink_id) {
28 auto iter = 28 auto iter =
29 std::find_if(g_sink_details.begin(), g_sink_details.end(), 29 std::find_if(g_sink_details.begin(), g_sink_details.end(),
30 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); 30 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
index ea666c554..03534b187 100644
--- a/src/audio_core/sink_details.h
+++ b/src/audio_core/sink_details.h
@@ -6,6 +6,8 @@
6 6
7#include <functional> 7#include <functional>
8#include <memory> 8#include <memory>
9#include <string>
10#include <string_view>
9#include <utility> 11#include <utility>
10#include <vector> 12#include <vector>
11 13
@@ -30,6 +32,6 @@ struct SinkDetails {
30 32
31extern const std::vector<SinkDetails> g_sink_details; 33extern const std::vector<SinkDetails> g_sink_details;
32 34
33const SinkDetails& GetSinkDetails(std::string sink_id); 35const SinkDetails& GetSinkDetails(std::string_view sink_id);
34 36
35} // namespace AudioCore 37} // namespace AudioCore
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h
index 41b6736d8..4309ad094 100644
--- a/src/audio_core/sink_stream.h
+++ b/src/audio_core/sink_stream.h
@@ -25,6 +25,10 @@ public:
25 * @param samples Samples in interleaved stereo PCM16 format. 25 * @param samples Samples in interleaved stereo PCM16 format.
26 */ 26 */
27 virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; 27 virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0;
28
29 virtual std::size_t SamplesInQueue(u32 num_channels) const = 0;
30
31 virtual void Flush() = 0;
28}; 32};
29 33
30using SinkStreamPtr = std::unique_ptr<SinkStream>; 34using SinkStreamPtr = std::unique_ptr<SinkStream>;
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index dbae75d8c..84dcdd98d 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -73,6 +73,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples) {
73void Stream::PlayNextBuffer() { 73void Stream::PlayNextBuffer() {
74 if (!IsPlaying()) { 74 if (!IsPlaying()) {
75 // Ensure we are in playing state before playing the next buffer 75 // Ensure we are in playing state before playing the next buffer
76 sink_stream.Flush();
76 return; 77 return;
77 } 78 }
78 79
@@ -83,6 +84,7 @@ void Stream::PlayNextBuffer() {
83 84
84 if (queued_buffers.empty()) { 85 if (queued_buffers.empty()) {
85 // No queued buffers - we are effectively paused 86 // No queued buffers - we are effectively paused
87 sink_stream.Flush();
86 return; 88 return;
87 } 89 }
88 90
@@ -90,6 +92,7 @@ void Stream::PlayNextBuffer() {
90 queued_buffers.pop(); 92 queued_buffers.pop();
91 93
92 VolumeAdjustSamples(active_buffer->Samples()); 94 VolumeAdjustSamples(active_buffer->Samples());
95
93 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); 96 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
94 97
95 CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); 98 CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
new file mode 100644
index 000000000..da094c46b
--- /dev/null
+++ b/src/audio_core/time_stretch.cpp
@@ -0,0 +1,68 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <cmath>
7#include <cstddef>
8#include "audio_core/time_stretch.h"
9#include "common/logging/log.h"
10
11namespace AudioCore {
12
13TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
14 : m_sample_rate(sample_rate), m_channel_count(channel_count) {
15 m_sound_touch.setChannels(channel_count);
16 m_sound_touch.setSampleRate(sample_rate);
17 m_sound_touch.setPitch(1.0);
18 m_sound_touch.setTempo(1.0);
19}
20
21void TimeStretcher::Clear() {
22 m_sound_touch.clear();
23}
24
25void TimeStretcher::Flush() {
26 m_sound_touch.flush();
27}
28
29size_t TimeStretcher::Process(const s16* in, size_t num_in, s16* out, size_t num_out) {
30 const double time_delta = static_cast<double>(num_out) / m_sample_rate; // seconds
31
32 // We were given actual_samples number of samples, and num_samples were requested from us.
33 double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
34
35 const double max_latency = 1.0; // seconds
36 const double max_backlog = m_sample_rate * max_latency;
37 const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
38 if (backlog_fullness > 5.0) {
39 // Too many samples in backlog: Don't push anymore on
40 num_in = 0;
41 }
42
43 // We ideally want the backlog to be about 50% full.
44 // This gives some headroom both ways to prevent underflow and overflow.
45 // We tweak current_ratio to encourage this.
46 constexpr double tweak_time_scale = 0.05; // seconds
47 const double tweak_correction = (backlog_fullness - 0.5) * (time_delta / tweak_time_scale);
48 current_ratio *= std::pow(1.0 + 2.0 * tweak_correction, tweak_correction < 0 ? 3.0 : 1.0);
49
50 // This low-pass filter smoothes out variance in the calculated stretch ratio.
51 // The time-scale determines how responsive this filter is.
52 constexpr double lpf_time_scale = 2.0; // seconds
53 const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
54 m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
55
56 // Place a lower limit of 5% speed. When a game boots up, there will be
57 // many silence samples. These do not need to be timestretched.
58 m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
59 m_sound_touch.setTempo(m_stretch_ratio);
60
61 LOG_DEBUG(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
62 backlog_fullness);
63
64 m_sound_touch.putSamples(in, num_in);
65 return m_sound_touch.receiveSamples(out, num_out);
66}
67
68} // namespace AudioCore
diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h
new file mode 100644
index 000000000..7e39e695e
--- /dev/null
+++ b/src/audio_core/time_stretch.h
@@ -0,0 +1,36 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <cstddef>
9#include <SoundTouch.h>
10#include "common/common_types.h"
11
12namespace AudioCore {
13
14class TimeStretcher {
15public:
16 TimeStretcher(u32 sample_rate, u32 channel_count);
17
18 /// @param in Input sample buffer
19 /// @param num_in Number of input frames in `in`
20 /// @param out Output sample buffer
21 /// @param num_out Desired number of output frames in `out`
22 /// @returns Actual number of frames written to `out`
23 size_t Process(const s16* in, size_t num_in, s16* out, size_t num_out);
24
25 void Clear();
26
27 void Flush();
28
29private:
30 u32 m_sample_rate;
31 u32 m_channel_count;
32 soundtouch::SoundTouch m_sound_touch;
33 double m_stretch_ratio = 1.0;
34};
35
36} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index f41946cc6..6a3f1fe08 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -71,6 +71,7 @@ add_library(common STATIC
71 param_package.cpp 71 param_package.cpp
72 param_package.h 72 param_package.h
73 quaternion.h 73 quaternion.h
74 ring_buffer.h
74 scm_rev.cpp 75 scm_rev.cpp
75 scm_rev.h 76 scm_rev.h
76 scope_exit.h 77 scope_exit.h
diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h
new file mode 100644
index 000000000..30d934a38
--- /dev/null
+++ b/src/common/ring_buffer.h
@@ -0,0 +1,111 @@
1// Copyright 2018 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 <algorithm>
8#include <array>
9#include <atomic>
10#include <cstddef>
11#include <cstring>
12#include <type_traits>
13#include <vector>
14#include "common/common_types.h"
15
16namespace Common {
17
18/// SPSC ring buffer
19/// @tparam T Element type
20/// @tparam capacity Number of slots in ring buffer
21/// @tparam granularity Slot size in terms of number of elements
22template <typename T, size_t capacity, size_t granularity = 1>
23class RingBuffer {
24 /// A "slot" is made of `granularity` elements of `T`.
25 static constexpr size_t slot_size = granularity * sizeof(T);
26 // T must be safely memcpy-able and have a trivial default constructor.
27 static_assert(std::is_trivial_v<T>);
28 // Ensure capacity is sensible.
29 static_assert(capacity < std::numeric_limits<size_t>::max() / 2 / granularity);
30 static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two");
31 // Ensure lock-free.
32 static_assert(std::atomic<size_t>::is_always_lock_free);
33
34public:
35 /// Pushes slots into the ring buffer
36 /// @param new_slots Pointer to the slots to push
37 /// @param slot_count Number of slots to push
38 /// @returns The number of slots actually pushed
39 size_t Push(const void* new_slots, size_t slot_count) {
40 const size_t write_index = m_write_index.load();
41 const size_t slots_free = capacity + m_read_index.load() - write_index;
42 const size_t push_count = std::min(slot_count, slots_free);
43
44 const size_t pos = write_index % capacity;
45 const size_t first_copy = std::min(capacity - pos, push_count);
46 const size_t second_copy = push_count - first_copy;
47
48 const char* in = static_cast<const char*>(new_slots);
49 std::memcpy(m_data.data() + pos * granularity, in, first_copy * slot_size);
50 in += first_copy * slot_size;
51 std::memcpy(m_data.data(), in, second_copy * slot_size);
52
53 m_write_index.store(write_index + push_count);
54
55 return push_count;
56 }
57
58 size_t Push(const std::vector<T>& input) {
59 return Push(input.data(), input.size());
60 }
61
62 /// Pops slots from the ring buffer
63 /// @param output Where to store the popped slots
64 /// @param max_slots Maximum number of slots to pop
65 /// @returns The number of slots actually popped
66 size_t Pop(void* output, size_t max_slots = ~size_t(0)) {
67 const size_t read_index = m_read_index.load();
68 const size_t slots_filled = m_write_index.load() - read_index;
69 const size_t pop_count = std::min(slots_filled, max_slots);
70
71 const size_t pos = read_index % capacity;
72 const size_t first_copy = std::min(capacity - pos, pop_count);
73 const size_t second_copy = pop_count - first_copy;
74
75 char* out = static_cast<char*>(output);
76 std::memcpy(out, m_data.data() + pos * granularity, first_copy * slot_size);
77 out += first_copy * slot_size;
78 std::memcpy(out, m_data.data(), second_copy * slot_size);
79
80 m_read_index.store(read_index + pop_count);
81
82 return pop_count;
83 }
84
85 std::vector<T> Pop(size_t max_slots = ~size_t(0)) {
86 std::vector<T> out(std::min(max_slots, capacity) * granularity);
87 const size_t count = Pop(out.data(), out.size() / granularity);
88 out.resize(count * granularity);
89 return out;
90 }
91
92 /// @returns Number of slots used
93 size_t Size() const {
94 return m_write_index.load() - m_read_index.load();
95 }
96
97 /// @returns Maximum size of ring buffer
98 constexpr size_t Capacity() const {
99 return capacity;
100 }
101
102private:
103 // It is important to align the below variables for performance reasons:
104 // Having them on the same cache-line would result in false-sharing between them.
105 alignas(128) std::atomic<size_t> m_read_index{0};
106 alignas(128) std::atomic<size_t> m_write_index{0};
107
108 std::array<T, granularity * capacity> m_data;
109};
110
111} // namespace Common
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index eaa5395ac..545cd884a 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -153,7 +153,7 @@ struct DataPayloadHeader {
153 u32_le magic; 153 u32_le magic;
154 INSERT_PADDING_WORDS(1); 154 INSERT_PADDING_WORDS(1);
155}; 155};
156static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect"); 156static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadHeader size is incorrect");
157 157
158struct DomainMessageHeader { 158struct DomainMessageHeader {
159 enum class CommandType : u32_le { 159 enum class CommandType : u32_le {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 20f50458b..cb57ee78a 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -15,6 +15,12 @@
15#include "core/hle/kernel/wait_object.h" 15#include "core/hle/kernel/wait_object.h"
16#include "core/hle/result.h" 16#include "core/hle/result.h"
17 17
18namespace Kernel {
19
20class KernelCore;
21class Process;
22class Scheduler;
23
18enum ThreadPriority : u32 { 24enum ThreadPriority : u32 {
19 THREADPRIO_HIGHEST = 0, ///< Highest thread priority 25 THREADPRIO_HIGHEST = 0, ///< Highest thread priority
20 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps 26 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
@@ -54,12 +60,6 @@ enum class ThreadWakeupReason {
54 Timeout // The thread was woken up due to a wait timeout. 60 Timeout // The thread was woken up due to a wait timeout.
55}; 61};
56 62
57namespace Kernel {
58
59class KernelCore;
60class Process;
61class Scheduler;
62
63class Thread final : public WaitObject { 63class Thread final : public WaitObject {
64public: 64public:
65 /** 65 /**
diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h
index a3eb885bf..fcced063a 100644
--- a/src/core/hle/service/acc/acc_su.h
+++ b/src/core/hle/service/acc/acc_su.h
@@ -6,8 +6,7 @@
6 6
7#include "core/hle/service/acc/acc.h" 7#include "core/hle/service/acc/acc.h"
8 8
9namespace Service { 9namespace Service::Account {
10namespace Account {
11 10
12class ACC_SU final : public Module::Interface { 11class ACC_SU final : public Module::Interface {
13public: 12public:
@@ -16,5 +15,4 @@ public:
16 ~ACC_SU() override; 15 ~ACC_SU() override;
17}; 16};
18 17
19} // namespace Account 18} // namespace Service::Account
20} // namespace Service
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index da1c46d59..447689a1a 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -2,6 +2,10 @@
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 <algorithm>
6#include <cstring>
7#include <vector>
8
5#include <FontChineseSimplified.h> 9#include <FontChineseSimplified.h>
6#include <FontChineseTraditional.h> 10#include <FontChineseTraditional.h>
7#include <FontExtendedChineseSimplified.h> 11#include <FontExtendedChineseSimplified.h>
@@ -9,14 +13,19 @@
9#include <FontNintendoExtended.h> 13#include <FontNintendoExtended.h>
10#include <FontStandard.h> 14#include <FontStandard.h>
11 15
16#include "common/assert.h"
12#include "common/common_paths.h" 17#include "common/common_paths.h"
18#include "common/common_types.h"
13#include "common/file_util.h" 19#include "common/file_util.h"
20#include "common/logging/log.h"
21#include "common/swap.h"
14#include "core/core.h" 22#include "core/core.h"
15#include "core/file_sys/content_archive.h" 23#include "core/file_sys/content_archive.h"
16#include "core/file_sys/nca_metadata.h" 24#include "core/file_sys/nca_metadata.h"
17#include "core/file_sys/registered_cache.h" 25#include "core/file_sys/registered_cache.h"
18#include "core/file_sys/romfs.h" 26#include "core/file_sys/romfs.h"
19#include "core/hle/ipc_helpers.h" 27#include "core/hle/ipc_helpers.h"
28#include "core/hle/kernel/shared_memory.h"
20#include "core/hle/service/filesystem/filesystem.h" 29#include "core/hle/service/filesystem/filesystem.h"
21#include "core/hle/service/ns/pl_u.h" 30#include "core/hle/service/ns/pl_u.h"
22 31
@@ -35,49 +44,41 @@ struct FontRegion {
35 u32 size; 44 u32 size;
36}; 45};
37 46
38static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ 47constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
39 std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), 48 std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
40 std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), 49 std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
41 std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), 50 std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
42 std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), 51 std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
43 std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), 52 std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
44 std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), 53 std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
45 std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")}; 54 std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
55};
46 56
47static constexpr std::array<const char*, 7> SHARED_FONTS_TTF{"FontStandard.ttf", 57constexpr std::array<const char*, 7> SHARED_FONTS_TTF{
48 "FontChineseSimplified.ttf", 58 "FontStandard.ttf",
49 "FontExtendedChineseSimplified.ttf", 59 "FontChineseSimplified.ttf",
50 "FontChineseTraditional.ttf", 60 "FontExtendedChineseSimplified.ttf",
51 "FontKorean.ttf", 61 "FontChineseTraditional.ttf",
52 "FontNintendoExtended.ttf", 62 "FontKorean.ttf",
53 "FontNintendoExtended2.ttf"}; 63 "FontNintendoExtended.ttf",
64 "FontNintendoExtended2.ttf",
65};
54 66
55// The below data is specific to shared font data dumped from Switch on f/w 2.2 67// The below data is specific to shared font data dumped from Switch on f/w 2.2
56// Virtual address and offsets/sizes likely will vary by dump 68// Virtual address and offsets/sizes likely will vary by dump
57static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; 69constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
58static constexpr u32 EXPECTED_RESULT{ 70constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
59 0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be 71constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
60static constexpr u32 EXPECTED_MAGIC{ 72constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
61 0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be 73constexpr FontRegion EMPTY_REGION{0, 0};
62static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
63static constexpr FontRegion EMPTY_REGION{0, 0};
64std::vector<FontRegion>
65 SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives
66
67const FontRegion& GetSharedFontRegion(size_t index) {
68 if (index >= SHARED_FONT_REGIONS.size() || SHARED_FONT_REGIONS.empty()) {
69 // No font fallback
70 return EMPTY_REGION;
71 }
72 return SHARED_FONT_REGIONS.at(index);
73}
74 74
75enum class LoadState : u32 { 75enum class LoadState : u32 {
76 Loading = 0, 76 Loading = 0,
77 Done = 1, 77 Done = 1,
78}; 78};
79 79
80void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, size_t& offset) { 80static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
81 size_t& offset) {
81 ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, 82 ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
82 "Shared fonts exceeds 17mb!"); 83 "Shared fonts exceeds 17mb!");
83 ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); 84 ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
@@ -104,28 +105,52 @@ static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& out
104 offset += input.size() + (sizeof(u32) * 2); 105 offset += input.size() + (sizeof(u32) * 2);
105} 106}
106 107
108// Helper function to make BuildSharedFontsRawRegions a bit nicer
107static u32 GetU32Swapped(const u8* data) { 109static u32 GetU32Swapped(const u8* data) {
108 u32 value; 110 u32 value;
109 std::memcpy(&value, data, sizeof(value)); 111 std::memcpy(&value, data, sizeof(value));
110 return Common::swap32(value); // Helper function to make BuildSharedFontsRawRegions a bit nicer 112 return Common::swap32(value);
111} 113}
112 114
113void BuildSharedFontsRawRegions(const std::vector<u8>& input) { 115struct PL_U::Impl {
114 unsigned cur_offset = 0; // As we can derive the xor key we can just populate the offsets based 116 const FontRegion& GetSharedFontRegion(size_t index) const {
115 // on the shared memory dump 117 if (index >= shared_font_regions.size() || shared_font_regions.empty()) {
116 for (size_t i = 0; i < SHARED_FONTS.size(); i++) { 118 // No font fallback
117 // Out of shared fonts/Invalid font 119 return EMPTY_REGION;
118 if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) 120 }
119 break; 121 return shared_font_regions.at(index);
120 const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^
121 EXPECTED_MAGIC; // Derive key withing inverse xor
122 const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
123 SHARED_FONT_REGIONS.push_back(FontRegion{cur_offset + 8, SIZE});
124 cur_offset += SIZE + 8;
125 } 122 }
126}
127 123
128PL_U::PL_U() : ServiceFramework("pl:u") { 124 void BuildSharedFontsRawRegions(const std::vector<u8>& input) {
125 // As we can derive the xor key we can just populate the offsets
126 // based on the shared memory dump
127 unsigned cur_offset = 0;
128
129 for (size_t i = 0; i < SHARED_FONTS.size(); i++) {
130 // Out of shared fonts/invalid font
131 if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) {
132 break;
133 }
134
135 // Derive key withing inverse xor
136 const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC;
137 const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
138 shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE});
139 cur_offset += SIZE + 8;
140 }
141 }
142
143 /// Handle to shared memory region designated for a shared font
144 Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
145
146 /// Backing memory for the shared font data
147 std::shared_ptr<std::vector<u8>> shared_font;
148
149 // Automatically populated based on shared_fonts dump or system archives.
150 std::vector<FontRegion> shared_font_regions;
151};
152
153PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
129 static const FunctionInfo functions[] = { 154 static const FunctionInfo functions[] = {
130 {0, &PL_U::RequestLoad, "RequestLoad"}, 155 {0, &PL_U::RequestLoad, "RequestLoad"},
131 {1, &PL_U::GetLoadState, "GetLoadState"}, 156 {1, &PL_U::GetLoadState, "GetLoadState"},
@@ -141,7 +166,7 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
141 // Rebuild shared fonts from data ncas 166 // Rebuild shared fonts from data ncas
142 if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), 167 if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
143 FileSys::ContentRecordType::Data)) { 168 FileSys::ContentRecordType::Data)) {
144 shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); 169 impl->shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
145 for (auto font : SHARED_FONTS) { 170 for (auto font : SHARED_FONTS) {
146 const auto nca = 171 const auto nca =
147 nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data); 172 nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
@@ -177,12 +202,12 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
177 static_cast<u32>(offset + 8), 202 static_cast<u32>(offset + 8),
178 static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 203 static_cast<u32>((font_data_u32.size() * sizeof(u32)) -
179 8)}; // Font offset and size do not account for the header 204 8)}; // Font offset and size do not account for the header
180 DecryptSharedFont(font_data_u32, *shared_font, offset); 205 DecryptSharedFont(font_data_u32, *impl->shared_font, offset);
181 SHARED_FONT_REGIONS.push_back(region); 206 impl->shared_font_regions.push_back(region);
182 } 207 }
183 208
184 } else { 209 } else {
185 shared_font = std::make_shared<std::vector<u8>>( 210 impl->shared_font = std::make_shared<std::vector<u8>>(
186 SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size 211 SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
187 212
188 const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); 213 const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
@@ -206,8 +231,8 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
206 static_cast<u32>(offset + 8), 231 static_cast<u32>(offset + 8),
207 static_cast<u32>(ttf_bytes.size())}; // Font offset and size do not account 232 static_cast<u32>(ttf_bytes.size())}; // Font offset and size do not account
208 // for the header 233 // for the header
209 EncryptSharedFont(ttf_bytes, *shared_font, offset); 234 EncryptSharedFont(ttf_bytes, *impl->shared_font, offset);
210 SHARED_FONT_REGIONS.push_back(region); 235 impl->shared_font_regions.push_back(region);
211 } else { 236 } else {
212 LOG_WARNING(Service_NS, "Unable to load font: {}", font_ttf); 237 LOG_WARNING(Service_NS, "Unable to load font: {}", font_ttf);
213 } 238 }
@@ -222,26 +247,28 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
222 if (file.IsOpen()) { 247 if (file.IsOpen()) {
223 // Read shared font data 248 // Read shared font data
224 ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE); 249 ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
225 file.ReadBytes(shared_font->data(), shared_font->size()); 250 file.ReadBytes(impl->shared_font->data(), impl->shared_font->size());
226 BuildSharedFontsRawRegions(*shared_font); 251 impl->BuildSharedFontsRawRegions(*impl->shared_font);
227 } else { 252 } else {
228 LOG_WARNING(Service_NS, 253 LOG_WARNING(Service_NS,
229 "Shared Font file missing. Loading open source replacement from memory"); 254 "Shared Font file missing. Loading open source replacement from memory");
230 255
256 // clang-format off
231 const std::vector<std::vector<u8>> open_source_shared_fonts_ttf = { 257 const std::vector<std::vector<u8>> open_source_shared_fonts_ttf = {
232 {std::begin(FontChineseSimplified), std::end(FontChineseSimplified)}, 258 {std::begin(FontChineseSimplified), std::end(FontChineseSimplified)},
233 {std::begin(FontChineseTraditional), std::end(FontChineseTraditional)}, 259 {std::begin(FontChineseTraditional), std::end(FontChineseTraditional)},
234 {std::begin(FontExtendedChineseSimplified), 260 {std::begin(FontExtendedChineseSimplified), std::end(FontExtendedChineseSimplified)},
235 std::end(FontExtendedChineseSimplified)}, 261 {std::begin(FontKorean), std::end(FontKorean)},
236 {std::begin(FontNintendoExtended), std::end(FontNintendoExtended)}, 262 {std::begin(FontNintendoExtended), std::end(FontNintendoExtended)},
237 {std::begin(FontStandard), std::end(FontStandard)}, 263 {std::begin(FontStandard), std::end(FontStandard)},
238 }; 264 };
265 // clang-format on
239 266
240 for (const std::vector<u8>& font_ttf : open_source_shared_fonts_ttf) { 267 for (const std::vector<u8>& font_ttf : open_source_shared_fonts_ttf) {
241 const FontRegion region{static_cast<u32>(offset + 8), 268 const FontRegion region{static_cast<u32>(offset + 8),
242 static_cast<u32>(font_ttf.size())}; 269 static_cast<u32>(font_ttf.size())};
243 EncryptSharedFont(font_ttf, *shared_font, offset); 270 EncryptSharedFont(font_ttf, *impl->shared_font, offset);
244 SHARED_FONT_REGIONS.push_back(region); 271 impl->shared_font_regions.push_back(region);
245 } 272 }
246 } 273 }
247 } 274 }
@@ -275,7 +302,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
275 LOG_DEBUG(Service_NS, "called, font_id={}", font_id); 302 LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
276 IPC::ResponseBuilder rb{ctx, 3}; 303 IPC::ResponseBuilder rb{ctx, 3};
277 rb.Push(RESULT_SUCCESS); 304 rb.Push(RESULT_SUCCESS);
278 rb.Push<u32>(GetSharedFontRegion(font_id).size); 305 rb.Push<u32>(impl->GetSharedFontRegion(font_id).size);
279} 306}
280 307
281void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { 308void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
@@ -285,17 +312,18 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
285 LOG_DEBUG(Service_NS, "called, font_id={}", font_id); 312 LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
286 IPC::ResponseBuilder rb{ctx, 3}; 313 IPC::ResponseBuilder rb{ctx, 3};
287 rb.Push(RESULT_SUCCESS); 314 rb.Push(RESULT_SUCCESS);
288 rb.Push<u32>(GetSharedFontRegion(font_id).offset); 315 rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset);
289} 316}
290 317
291void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { 318void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
292 // Map backing memory for the font data 319 // Map backing memory for the font data
293 Core::CurrentProcess()->vm_manager.MapMemoryBlock( 320 Core::CurrentProcess()->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0,
294 SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared); 321 SHARED_FONT_MEM_SIZE,
322 Kernel::MemoryState::Shared);
295 323
296 // Create shared font memory object 324 // Create shared font memory object
297 auto& kernel = Core::System::GetInstance().Kernel(); 325 auto& kernel = Core::System::GetInstance().Kernel();
298 shared_font_mem = Kernel::SharedMemory::Create( 326 impl->shared_font_mem = Kernel::SharedMemory::Create(
299 kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, 327 kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
300 Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, 328 Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
301 "PL_U:shared_font_mem"); 329 "PL_U:shared_font_mem");
@@ -303,7 +331,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
303 LOG_DEBUG(Service_NS, "called"); 331 LOG_DEBUG(Service_NS, "called");
304 IPC::ResponseBuilder rb{ctx, 2, 1}; 332 IPC::ResponseBuilder rb{ctx, 2, 1};
305 rb.Push(RESULT_SUCCESS); 333 rb.Push(RESULT_SUCCESS);
306 rb.PushCopyObjects(shared_font_mem); 334 rb.PushCopyObjects(impl->shared_font_mem);
307} 335}
308 336
309void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { 337void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
@@ -316,9 +344,9 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
316 std::vector<u32> font_sizes; 344 std::vector<u32> font_sizes;
317 345
318 // TODO(ogniK): Have actual priority order 346 // TODO(ogniK): Have actual priority order
319 for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) { 347 for (size_t i = 0; i < impl->shared_font_regions.size(); i++) {
320 font_codes.push_back(static_cast<u32>(i)); 348 font_codes.push_back(static_cast<u32>(i));
321 auto region = GetSharedFontRegion(i); 349 auto region = impl->GetSharedFontRegion(i);
322 font_offsets.push_back(region.offset); 350 font_offsets.push_back(region.offset);
323 font_sizes.push_back(region.size); 351 font_sizes.push_back(region.size);
324 } 352 }
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
index 296c3db05..253f26a2a 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/pl_u.h
@@ -5,7 +5,6 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include "core/hle/kernel/shared_memory.h"
9#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
10 9
11namespace Service::NS { 10namespace Service::NS {
@@ -23,11 +22,8 @@ private:
23 void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); 22 void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
24 void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); 23 void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx);
25 24
26 /// Handle to shared memory region designated for a shared font 25 struct Impl;
27 Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; 26 std::unique_ptr<Impl> impl;
28
29 /// Backing memory for the shared font data
30 std::shared_ptr<std::vector<u8>> shared_font;
31}; 27};
32 28
33} // namespace Service::NS 29} // namespace Service::NS
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index 34f98fe5a..fd98d541d 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -9,8 +9,7 @@
9#include "core/core.h" 9#include "core/core.h"
10#include "core/hle/service/nvflinger/buffer_queue.h" 10#include "core/hle/service/nvflinger/buffer_queue.h"
11 11
12namespace Service { 12namespace Service::NVFlinger {
13namespace NVFlinger {
14 13
15BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { 14BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
16 auto& kernel = Core::System::GetInstance().Kernel(); 15 auto& kernel = Core::System::GetInstance().Kernel();
@@ -104,5 +103,4 @@ u32 BufferQueue::Query(QueryType type) {
104 return 0; 103 return 0;
105} 104}
106 105
107} // namespace NVFlinger 106} // namespace Service::NVFlinger
108} // namespace Service
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index 17c81928a..50b767732 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -15,8 +15,7 @@ namespace CoreTiming {
15struct EventType; 15struct EventType;
16} 16}
17 17
18namespace Service { 18namespace Service::NVFlinger {
19namespace NVFlinger {
20 19
21struct IGBPBuffer { 20struct IGBPBuffer {
22 u32_le magic; 21 u32_le magic;
@@ -98,5 +97,4 @@ private:
98 Kernel::SharedPtr<Kernel::Event> buffer_wait_event; 97 Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
99}; 98};
100 99
101} // namespace NVFlinger 100} // namespace Service::NVFlinger
102} // namespace Service
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 85244ac3b..cf94b00e6 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -514,7 +514,7 @@ private:
514 ctx.SleepClientThread( 514 ctx.SleepClientThread(
515 Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1, 515 Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1,
516 [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, 516 [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
517 ThreadWakeupReason reason) { 517 Kernel::ThreadWakeupReason reason) {
518 // Repeat TransactParcel DequeueBuffer when a buffer is available 518 // Repeat TransactParcel DequeueBuffer when a buffer is available
519 auto buffer_queue = nv_flinger->GetBufferQueue(id); 519 auto buffer_queue = nv_flinger->GetBufferQueue(id);
520 boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); 520 boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index bb89a9da3..c49ec34ab 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -191,7 +191,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
191 process->svc_access_mask.set(); 191 process->svc_access_mask.set();
192 process->resource_limit = 192 process->resource_limit =
193 kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION); 193 kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
194 process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); 194 process->Run(base_addr, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
195 195
196 is_loaded = true; 196 is_loaded = true;
197 return ResultStatus::Success; 197 return ResultStatus::Success;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 082a95d40..3c6306818 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -157,7 +157,8 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
157 process->svc_access_mask.set(); 157 process->svc_access_mask.set();
158 process->resource_limit = 158 process->resource_limit =
159 kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION); 159 kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
160 process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); 160 process->Run(Memory::PROCESS_IMAGE_VADDR, Kernel::THREADPRIO_DEFAULT,
161 Memory::DEFAULT_STACK_SIZE);
161 162
162 is_loaded = true; 163 is_loaded = true;
163 return ResultStatus::Success; 164 return ResultStatus::Success;
diff --git a/src/core/settings.h b/src/core/settings.h
index 08a16ef2c..0318d019c 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -148,6 +148,7 @@ struct Values {
148 148
149 // Audio 149 // Audio
150 std::string sink_id; 150 std::string sink_id;
151 bool enable_audio_stretching;
151 std::string audio_device_id; 152 std::string audio_device_id;
152 float volume; 153 float volume;
153 154
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 3730e85b8..b0df154ca 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -120,6 +120,9 @@ TelemetrySession::TelemetrySession() {
120 Telemetry::AppendOSInfo(field_collection); 120 Telemetry::AppendOSInfo(field_collection);
121 121
122 // Log user configuration information 122 // Log user configuration information
123 AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id);
124 AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
125 Settings::values.enable_audio_stretching);
123 AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); 126 AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
124 AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore", 127 AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
125 Settings::values.use_multi_core); 128 Settings::values.use_multi_core);
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 4d74bb395..4e75a72ec 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,5 +1,6 @@
1add_executable(tests 1add_executable(tests
2 common/param_package.cpp 2 common/param_package.cpp
3 common/ring_buffer.cpp
3 core/arm/arm_test_common.cpp 4 core/arm/arm_test_common.cpp
4 core/arm/arm_test_common.h 5 core/arm/arm_test_common.h
5 core/core_timing.cpp 6 core/core_timing.cpp
diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp
new file mode 100644
index 000000000..f3fe57839
--- /dev/null
+++ b/src/tests/common/ring_buffer.cpp
@@ -0,0 +1,130 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <cstddef>
8#include <numeric>
9#include <thread>
10#include <vector>
11#include <catch2/catch.hpp>
12#include "common/ring_buffer.h"
13
14namespace Common {
15
16TEST_CASE("RingBuffer: Basic Tests", "[common]") {
17 RingBuffer<char, 4, 1> buf;
18
19 // Pushing values into a ring buffer with space should succeed.
20 for (size_t i = 0; i < 4; i++) {
21 const char elem = static_cast<char>(i);
22 const size_t count = buf.Push(&elem, 1);
23 REQUIRE(count == 1);
24 }
25
26 REQUIRE(buf.Size() == 4);
27
28 // Pushing values into a full ring buffer should fail.
29 {
30 const char elem = static_cast<char>(42);
31 const size_t count = buf.Push(&elem, 1);
32 REQUIRE(count == 0);
33 }
34
35 REQUIRE(buf.Size() == 4);
36
37 // Popping multiple values from a ring buffer with values should succeed.
38 {
39 const std::vector<char> popped = buf.Pop(2);
40 REQUIRE(popped.size() == 2);
41 REQUIRE(popped[0] == 0);
42 REQUIRE(popped[1] == 1);
43 }
44
45 REQUIRE(buf.Size() == 2);
46
47 // Popping a single value from a ring buffer with values should succeed.
48 {
49 const std::vector<char> popped = buf.Pop(1);
50 REQUIRE(popped.size() == 1);
51 REQUIRE(popped[0] == 2);
52 }
53
54 REQUIRE(buf.Size() == 1);
55
56 // Pushing more values than space available should partially suceed.
57 {
58 std::vector<char> to_push(6);
59 std::iota(to_push.begin(), to_push.end(), 88);
60 const size_t count = buf.Push(to_push);
61 REQUIRE(count == 3);
62 }
63
64 REQUIRE(buf.Size() == 4);
65
66 // Doing an unlimited pop should pop all values.
67 {
68 const std::vector<char> popped = buf.Pop();
69 REQUIRE(popped.size() == 4);
70 REQUIRE(popped[0] == 3);
71 REQUIRE(popped[1] == 88);
72 REQUIRE(popped[2] == 89);
73 REQUIRE(popped[3] == 90);
74 }
75
76 REQUIRE(buf.Size() == 0);
77}
78
79TEST_CASE("RingBuffer: Threaded Test", "[common]") {
80 RingBuffer<char, 4, 2> buf;
81 const char seed = 42;
82 const size_t count = 1000000;
83 size_t full = 0;
84 size_t empty = 0;
85
86 const auto next_value = [](std::array<char, 2>& value) {
87 value[0] += 1;
88 value[1] += 2;
89 };
90
91 std::thread producer{[&] {
92 std::array<char, 2> value = {seed, seed};
93 size_t i = 0;
94 while (i < count) {
95 if (const size_t c = buf.Push(&value[0], 1); c > 0) {
96 REQUIRE(c == 1);
97 i++;
98 next_value(value);
99 } else {
100 full++;
101 std::this_thread::yield();
102 }
103 }
104 }};
105
106 std::thread consumer{[&] {
107 std::array<char, 2> value = {seed, seed};
108 size_t i = 0;
109 while (i < count) {
110 if (const std::vector<char> v = buf.Pop(1); v.size() > 0) {
111 REQUIRE(v.size() == 2);
112 REQUIRE(v[0] == value[0]);
113 REQUIRE(v[1] == value[1]);
114 i++;
115 next_value(value);
116 } else {
117 empty++;
118 std::this_thread::yield();
119 }
120 }
121 }};
122
123 producer.join();
124 consumer.join();
125
126 REQUIRE(buf.Size() == 0);
127 printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
128}
129
130} // namespace Common
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 2db906ea5..58f2904ce 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -254,6 +254,15 @@ enum class TextureQueryType : u64 {
254 BorderColor = 22, 254 BorderColor = 22,
255}; 255};
256 256
257enum class TextureProcessMode : u64 {
258 None = 0,
259 LZ = 1, // Unknown, appears to be the same as none.
260 LB = 2, // Load Bias.
261 LL = 3, // Load LOD (LevelOfDetail)
262 LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB
263 LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL
264};
265
257enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 }; 266enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 };
258enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 }; 267enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 };
259 268
@@ -573,6 +582,7 @@ union Instruction {
573 BitField<28, 1, u64> array; 582 BitField<28, 1, u64> array;
574 BitField<29, 2, TextureType> texture_type; 583 BitField<29, 2, TextureType> texture_type;
575 BitField<31, 4, u64> component_mask; 584 BitField<31, 4, u64> component_mask;
585 BitField<55, 3, TextureProcessMode> process_mode;
576 586
577 bool IsComponentEnabled(size_t component) const { 587 bool IsComponentEnabled(size_t component) const {
578 return ((1ull << component) & component_mask) != 0; 588 return ((1ull << component) & component_mask) != 0;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index c59f3af1b..7e1bba67d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <array>
6#include <memory> 7#include <memory>
7#include <string> 8#include <string>
8#include <string_view> 9#include <string_view>
@@ -58,6 +59,8 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
58 59
59 if (extension == "GL_ARB_direct_state_access") { 60 if (extension == "GL_ARB_direct_state_access") {
60 has_ARB_direct_state_access = true; 61 has_ARB_direct_state_access = true;
62 } else if (extension == "GL_ARB_multi_bind") {
63 has_ARB_multi_bind = true;
61 } else if (extension == "GL_ARB_separate_shader_objects") { 64 } else if (extension == "GL_ARB_separate_shader_objects") {
62 has_ARB_separate_shader_objects = true; 65 has_ARB_separate_shader_objects = true;
63 } else if (extension == "GL_ARB_vertex_attrib_binding") { 66 } else if (extension == "GL_ARB_vertex_attrib_binding") {
@@ -644,12 +647,23 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
644 const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)]; 647 const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)];
645 const auto& entries = shader->GetShaderEntries().const_buffer_entries; 648 const auto& entries = shader->GetShaderEntries().const_buffer_entries;
646 649
650 constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers;
651 std::array<GLuint, max_binds> bind_buffers;
652 std::array<GLintptr, max_binds> bind_offsets;
653 std::array<GLsizeiptr, max_binds> bind_sizes;
654
655 ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points.");
656
647 // Upload only the enabled buffers from the 16 constbuffers of each shader stage 657 // Upload only the enabled buffers from the 16 constbuffers of each shader stage
648 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { 658 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
649 const auto& used_buffer = entries[bindpoint]; 659 const auto& used_buffer = entries[bindpoint];
650 const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; 660 const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()];
651 661
652 if (!buffer.enabled) { 662 if (!buffer.enabled) {
663 // With disabled buffers set values as zero to unbind them
664 bind_buffers[bindpoint] = 0;
665 bind_offsets[bindpoint] = 0;
666 bind_sizes[bindpoint] = 0;
653 continue; 667 continue;
654 } 668 }
655 669
@@ -677,15 +691,20 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
677 GLintptr const_buffer_offset = buffer_cache.UploadMemory( 691 GLintptr const_buffer_offset = buffer_cache.UploadMemory(
678 buffer.address, size, static_cast<size_t>(uniform_buffer_alignment)); 692 buffer.address, size, static_cast<size_t>(uniform_buffer_alignment));
679 693
680 glBindBufferRange(GL_UNIFORM_BUFFER, current_bindpoint + bindpoint,
681 buffer_cache.GetHandle(), const_buffer_offset, size);
682
683 // Now configure the bindpoint of the buffer inside the shader 694 // Now configure the bindpoint of the buffer inside the shader
684 glUniformBlockBinding(shader->GetProgramHandle(), 695 glUniformBlockBinding(shader->GetProgramHandle(),
685 shader->GetProgramResourceIndex(used_buffer), 696 shader->GetProgramResourceIndex(used_buffer),
686 current_bindpoint + bindpoint); 697 current_bindpoint + bindpoint);
698
699 // Prepare values for multibind
700 bind_buffers[bindpoint] = buffer_cache.GetHandle();
701 bind_offsets[bindpoint] = const_buffer_offset;
702 bind_sizes[bindpoint] = size;
687 } 703 }
688 704
705 glBindBuffersRange(GL_UNIFORM_BUFFER, current_bindpoint, static_cast<GLsizei>(entries.size()),
706 bind_buffers.data(), bind_offsets.data(), bind_sizes.data());
707
689 return current_bindpoint + static_cast<u32>(entries.size()); 708 return current_bindpoint + static_cast<u32>(entries.size());
690} 709}
691 710
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 745c3dc0c..163412882 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -159,6 +159,7 @@ private:
159 void SyncLogicOpState(); 159 void SyncLogicOpState();
160 160
161 bool has_ARB_direct_state_access = false; 161 bool has_ARB_direct_state_access = false;
162 bool has_ARB_multi_bind = false;
162 bool has_ARB_separate_shader_objects = false; 163 bool has_ARB_separate_shader_objects = false;
163 bool has_ARB_vertex_attrib_binding = false; 164 bool has_ARB_vertex_attrib_binding = false;
164 165
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index fb56decc0..32001e44b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -116,7 +116,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
116 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U 116 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
117 {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S 117 {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
118 {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI 118 {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI
119 {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U 119 {GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U
120 {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, 120 {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm,
121 false}, // A2B10G10R10U 121 false}, // A2B10G10R10U
122 {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U 122 {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 582c811e0..2d56370c7 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1853,15 +1853,47 @@ private:
1853 coord = "vec2 coords = vec2(" + x + ", " + y + ");"; 1853 coord = "vec2 coords = vec2(" + x + ", " + y + ");";
1854 texture_type = Tegra::Shader::TextureType::Texture2D; 1854 texture_type = Tegra::Shader::TextureType::Texture2D;
1855 } 1855 }
1856 // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias
1857 // or lod.
1858 const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
1856 1859
1857 const std::string sampler = GetSampler(instr.sampler, texture_type, false); 1860 const std::string sampler = GetSampler(instr.sampler, texture_type, false);
1858 // Add an extra scope and declare the texture coords inside to prevent 1861 // Add an extra scope and declare the texture coords inside to prevent
1859 // overwriting them in case they are used as outputs of the texs instruction. 1862 // overwriting them in case they are used as outputs of the texs instruction.
1863
1860 shader.AddLine("{"); 1864 shader.AddLine("{");
1861 ++shader.scope; 1865 ++shader.scope;
1862 shader.AddLine(coord); 1866 shader.AddLine(coord);
1863 const std::string texture = "texture(" + sampler + ", coords)"; 1867 std::string texture;
1864 1868
1869 switch (instr.tex.process_mode) {
1870 case Tegra::Shader::TextureProcessMode::None: {
1871 texture = "texture(" + sampler + ", coords)";
1872 break;
1873 }
1874 case Tegra::Shader::TextureProcessMode::LZ: {
1875 texture = "textureLod(" + sampler + ", coords, 0.0)";
1876 break;
1877 }
1878 case Tegra::Shader::TextureProcessMode::LB:
1879 case Tegra::Shader::TextureProcessMode::LBA: {
1880 // TODO: Figure if A suffix changes the equation at all.
1881 texture = "texture(" + sampler + ", coords, " + op_c + ')';
1882 break;
1883 }
1884 case Tegra::Shader::TextureProcessMode::LL:
1885 case Tegra::Shader::TextureProcessMode::LLA: {
1886 // TODO: Figure if A suffix changes the equation at all.
1887 texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
1888 break;
1889 }
1890 default: {
1891 texture = "texture(" + sampler + ", coords)";
1892 LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
1893 static_cast<u32>(instr.tex.process_mode.Value()));
1894 UNREACHABLE();
1895 }
1896 }
1865 size_t dest_elem{}; 1897 size_t dest_elem{};
1866 for (size_t elem = 0; elem < 4; ++elem) { 1898 for (size_t elem = 0; elem < 4; ++elem) {
1867 if (!instr.tex.IsComponentEnabled(elem)) { 1899 if (!instr.tex.IsComponentEnabled(elem)) {
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index c43e79e78..d229225b4 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -95,6 +95,8 @@ void Config::ReadValues() {
95 95
96 qt_config->beginGroup("Audio"); 96 qt_config->beginGroup("Audio");
97 Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); 97 Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
98 Settings::values.enable_audio_stretching =
99 qt_config->value("enable_audio_stretching", true).toBool();
98 Settings::values.audio_device_id = 100 Settings::values.audio_device_id =
99 qt_config->value("output_device", "auto").toString().toStdString(); 101 qt_config->value("output_device", "auto").toString().toStdString();
100 Settings::values.volume = qt_config->value("volume", 1).toFloat(); 102 Settings::values.volume = qt_config->value("volume", 1).toFloat();
@@ -230,6 +232,7 @@ void Config::SaveValues() {
230 232
231 qt_config->beginGroup("Audio"); 233 qt_config->beginGroup("Audio");
232 qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); 234 qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
235 qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
233 qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); 236 qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
234 qt_config->setValue("volume", Settings::values.volume); 237 qt_config->setValue("volume", Settings::values.volume);
235 qt_config->endGroup(); 238 qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fbb813f6c..6ea59f2a3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -46,6 +46,8 @@ void ConfigureAudio::setConfiguration() {
46 } 46 }
47 ui->output_sink_combo_box->setCurrentIndex(new_sink_index); 47 ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
48 48
49 ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
50
49 // The device list cannot be pre-populated (nor listed) until the output sink is known. 51 // The device list cannot be pre-populated (nor listed) until the output sink is known.
50 updateAudioDevices(new_sink_index); 52 updateAudioDevices(new_sink_index);
51 53
@@ -67,6 +69,7 @@ void ConfigureAudio::applyConfiguration() {
67 Settings::values.sink_id = 69 Settings::values.sink_id =
68 ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) 70 ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
69 .toStdString(); 71 .toStdString();
72 Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
70 Settings::values.audio_device_id = 73 Settings::values.audio_device_id =
71 ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) 74 ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
72 .toStdString(); 75 .toStdString();
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index ef67890dc..a29a0e265 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -31,6 +31,16 @@
31 </item> 31 </item>
32 </layout> 32 </layout>
33 </item> 33 </item>
34 <item>
35 <widget class="QCheckBox" name="toggle_audio_stretching">
36 <property name="toolTip">
37 <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
38 </property>
39 <property name="text">
40 <string>Enable audio stretching</string>
41 </property>
42 </widget>
43 </item>
34 <item> 44 <item>
35 <layout class="QHBoxLayout"> 45 <layout class="QHBoxLayout">
36 <item> 46 <item>
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index 1ae3423cf..0be030434 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -2,47 +2,51 @@
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 "core/core.h" 5#include <array>
6#include <utility>
7
8#include "common/common_types.h"
6#include "core/settings.h" 9#include "core/settings.h"
7#include "ui_configure_gamelist.h" 10#include "ui_configure_gamelist.h"
8#include "ui_settings.h"
9#include "yuzu/configuration/configure_gamelist.h" 11#include "yuzu/configuration/configure_gamelist.h"
12#include "yuzu/ui_settings.h"
13
14namespace {
15constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{
16 std::make_pair(0, QT_TR_NOOP("None")),
17 std::make_pair(32, QT_TR_NOOP("Small (32x32)")),
18 std::make_pair(64, QT_TR_NOOP("Standard (64x64)")),
19 std::make_pair(128, QT_TR_NOOP("Large (128x128)")),
20 std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")),
21}};
22
23constexpr std::array<const char*, 4> row_text_names{{
24 QT_TR_NOOP("Filename"),
25 QT_TR_NOOP("Filetype"),
26 QT_TR_NOOP("Title ID"),
27 QT_TR_NOOP("Title Name"),
28}};
29} // Anonymous namespace
10 30
11ConfigureGameList::ConfigureGameList(QWidget* parent) 31ConfigureGameList::ConfigureGameList(QWidget* parent)
12 : QWidget(parent), ui(new Ui::ConfigureGameList) { 32 : QWidget(parent), ui(new Ui::ConfigureGameList) {
13 ui->setupUi(this); 33 ui->setupUi(this);
14 34
15 static const std::vector<std::pair<u32, std::string>> default_icon_sizes{ 35 InitializeIconSizeComboBox();
16 std::make_pair(0, "None"), std::make_pair(32, "Small"), 36 InitializeRowComboBoxes();
17 std::make_pair(64, "Standard"), std::make_pair(128, "Large"),
18 std::make_pair(256, "Full Size"),
19 };
20
21 for (const auto& size : default_icon_sizes) {
22 ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" +
23 std::to_string(size.first) + "x" +
24 std::to_string(size.first) + ")"),
25 size.first);
26 }
27
28 static const std::vector<std::string> row_text_names{
29 "Filename",
30 "Filetype",
31 "Title ID",
32 "Title Name",
33 };
34
35 for (size_t i = 0; i < row_text_names.size(); ++i) {
36 ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]),
37 QVariant::fromValue(i));
38 ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]),
39 QVariant::fromValue(i));
40 }
41 37
42 this->setConfiguration(); 38 this->setConfiguration();
43} 39}
44 40
45ConfigureGameList::~ConfigureGameList() {} 41ConfigureGameList::~ConfigureGameList() = default;
42
43void ConfigureGameList::applyConfiguration() {
44 UISettings::values.show_unknown = ui->show_unknown->isChecked();
45 UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
46 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
47 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
48 Settings::Apply();
49}
46 50
47void ConfigureGameList::setConfiguration() { 51void ConfigureGameList::setConfiguration() {
48 ui->show_unknown->setChecked(UISettings::values.show_unknown); 52 ui->show_unknown->setChecked(UISettings::values.show_unknown);
@@ -54,10 +58,39 @@ void ConfigureGameList::setConfiguration() {
54 ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); 58 ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id));
55} 59}
56 60
57void ConfigureGameList::applyConfiguration() { 61void ConfigureGameList::changeEvent(QEvent* event) {
58 UISettings::values.show_unknown = ui->show_unknown->isChecked(); 62 if (event->type() == QEvent::LanguageChange) {
59 UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); 63 RetranslateUI();
60 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 64 return;
61 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); 65 }
62 Settings::Apply(); 66
67 QWidget::changeEvent(event);
68}
69
70void ConfigureGameList::RetranslateUI() {
71 ui->retranslateUi(this);
72
73 for (int i = 0; i < ui->icon_size_combobox->count(); i++) {
74 ui->icon_size_combobox->setItemText(i, tr(default_icon_sizes[i].second));
75 }
76
77 for (int i = 0; i < ui->row_1_text_combobox->count(); i++) {
78 const QString name = tr(row_text_names[i]);
79
80 ui->row_1_text_combobox->setItemText(i, name);
81 ui->row_2_text_combobox->setItemText(i, name);
82 }
83}
84
85void ConfigureGameList::InitializeIconSizeComboBox() {
86 for (const auto& size : default_icon_sizes) {
87 ui->icon_size_combobox->addItem(size.second, size.first);
88 }
89}
90
91void ConfigureGameList::InitializeRowComboBoxes() {
92 for (size_t i = 0; i < row_text_names.size(); ++i) {
93 ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i));
94 ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i));
95 }
63} 96}
diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h
index 94fba6373..ff7406c60 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_gamelist.h
@@ -23,6 +23,11 @@ public:
23private: 23private:
24 void setConfiguration(); 24 void setConfiguration();
25 25
26private: 26 void changeEvent(QEvent*) override;
27 void RetranslateUI();
28
29 void InitializeIconSizeComboBox();
30 void InitializeRowComboBoxes();
31
27 std::unique_ptr<Ui::ConfigureGameList> ui; 32 std::unique_ptr<Ui::ConfigureGameList> ui;
28}; 33};
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 6c2cd967e..dc1023113 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -213,35 +213,35 @@ QString WaitTreeThread::GetText() const {
213 const auto& thread = static_cast<const Kernel::Thread&>(object); 213 const auto& thread = static_cast<const Kernel::Thread&>(object);
214 QString status; 214 QString status;
215 switch (thread.status) { 215 switch (thread.status) {
216 case ThreadStatus::Running: 216 case Kernel::ThreadStatus::Running:
217 status = tr("running"); 217 status = tr("running");
218 break; 218 break;
219 case ThreadStatus::Ready: 219 case Kernel::ThreadStatus::Ready:
220 status = tr("ready"); 220 status = tr("ready");
221 break; 221 break;
222 case ThreadStatus::WaitHLEEvent: 222 case Kernel::ThreadStatus::WaitHLEEvent:
223 status = tr("waiting for HLE return"); 223 status = tr("waiting for HLE return");
224 break; 224 break;
225 case ThreadStatus::WaitSleep: 225 case Kernel::ThreadStatus::WaitSleep:
226 status = tr("sleeping"); 226 status = tr("sleeping");
227 break; 227 break;
228 case ThreadStatus::WaitIPC: 228 case Kernel::ThreadStatus::WaitIPC:
229 status = tr("waiting for IPC reply"); 229 status = tr("waiting for IPC reply");
230 break; 230 break;
231 case ThreadStatus::WaitSynchAll: 231 case Kernel::ThreadStatus::WaitSynchAll:
232 case ThreadStatus::WaitSynchAny: 232 case Kernel::ThreadStatus::WaitSynchAny:
233 status = tr("waiting for objects"); 233 status = tr("waiting for objects");
234 break; 234 break;
235 case ThreadStatus::WaitMutex: 235 case Kernel::ThreadStatus::WaitMutex:
236 status = tr("waiting for mutex"); 236 status = tr("waiting for mutex");
237 break; 237 break;
238 case ThreadStatus::WaitArb: 238 case Kernel::ThreadStatus::WaitArb:
239 status = tr("waiting for address arbiter"); 239 status = tr("waiting for address arbiter");
240 break; 240 break;
241 case ThreadStatus::Dormant: 241 case Kernel::ThreadStatus::Dormant:
242 status = tr("dormant"); 242 status = tr("dormant");
243 break; 243 break;
244 case ThreadStatus::Dead: 244 case Kernel::ThreadStatus::Dead:
245 status = tr("dead"); 245 status = tr("dead");
246 break; 246 break;
247 } 247 }
@@ -254,23 +254,23 @@ QString WaitTreeThread::GetText() const {
254QColor WaitTreeThread::GetColor() const { 254QColor WaitTreeThread::GetColor() const {
255 const auto& thread = static_cast<const Kernel::Thread&>(object); 255 const auto& thread = static_cast<const Kernel::Thread&>(object);
256 switch (thread.status) { 256 switch (thread.status) {
257 case ThreadStatus::Running: 257 case Kernel::ThreadStatus::Running:
258 return QColor(Qt::GlobalColor::darkGreen); 258 return QColor(Qt::GlobalColor::darkGreen);
259 case ThreadStatus::Ready: 259 case Kernel::ThreadStatus::Ready:
260 return QColor(Qt::GlobalColor::darkBlue); 260 return QColor(Qt::GlobalColor::darkBlue);
261 case ThreadStatus::WaitHLEEvent: 261 case Kernel::ThreadStatus::WaitHLEEvent:
262 case ThreadStatus::WaitIPC: 262 case Kernel::ThreadStatus::WaitIPC:
263 return QColor(Qt::GlobalColor::darkRed); 263 return QColor(Qt::GlobalColor::darkRed);
264 case ThreadStatus::WaitSleep: 264 case Kernel::ThreadStatus::WaitSleep:
265 return QColor(Qt::GlobalColor::darkYellow); 265 return QColor(Qt::GlobalColor::darkYellow);
266 case ThreadStatus::WaitSynchAll: 266 case Kernel::ThreadStatus::WaitSynchAll:
267 case ThreadStatus::WaitSynchAny: 267 case Kernel::ThreadStatus::WaitSynchAny:
268 case ThreadStatus::WaitMutex: 268 case Kernel::ThreadStatus::WaitMutex:
269 case ThreadStatus::WaitArb: 269 case Kernel::ThreadStatus::WaitArb:
270 return QColor(Qt::GlobalColor::red); 270 return QColor(Qt::GlobalColor::red);
271 case ThreadStatus::Dormant: 271 case Kernel::ThreadStatus::Dormant:
272 return QColor(Qt::GlobalColor::darkCyan); 272 return QColor(Qt::GlobalColor::darkCyan);
273 case ThreadStatus::Dead: 273 case Kernel::ThreadStatus::Dead:
274 return QColor(Qt::GlobalColor::gray); 274 return QColor(Qt::GlobalColor::gray);
275 default: 275 default:
276 return WaitTreeItem::GetColor(); 276 return WaitTreeItem::GetColor();
@@ -284,13 +284,13 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
284 284
285 QString processor; 285 QString processor;
286 switch (thread.processor_id) { 286 switch (thread.processor_id) {
287 case ThreadProcessorId::THREADPROCESSORID_DEFAULT: 287 case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:
288 processor = tr("default"); 288 processor = tr("default");
289 break; 289 break;
290 case ThreadProcessorId::THREADPROCESSORID_0: 290 case Kernel::ThreadProcessorId::THREADPROCESSORID_0:
291 case ThreadProcessorId::THREADPROCESSORID_1: 291 case Kernel::ThreadProcessorId::THREADPROCESSORID_1:
292 case ThreadProcessorId::THREADPROCESSORID_2: 292 case Kernel::ThreadProcessorId::THREADPROCESSORID_2:
293 case ThreadProcessorId::THREADPROCESSORID_3: 293 case Kernel::ThreadProcessorId::THREADPROCESSORID_3:
294 processor = tr("core %1").arg(thread.processor_id); 294 processor = tr("core %1").arg(thread.processor_id);
295 break; 295 break;
296 default: 296 default:
@@ -314,8 +314,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
314 else 314 else
315 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); 315 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
316 316
317 if (thread.status == ThreadStatus::WaitSynchAny || 317 if (thread.status == Kernel::ThreadStatus::WaitSynchAny ||
318 thread.status == ThreadStatus::WaitSynchAll) { 318 thread.status == Kernel::ThreadStatus::WaitSynchAll) {
319 list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, 319 list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
320 thread.IsSleepingOnWaitAll())); 320 thread.IsSleepingOnWaitAll()));
321 } 321 }
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 05a4a55e8..22a317737 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -449,6 +449,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
449 unsupported_ext.append("ARB_base_instance"); 449 unsupported_ext.append("ARB_base_instance");
450 if (!GLAD_GL_ARB_texture_storage) 450 if (!GLAD_GL_ARB_texture_storage)
451 unsupported_ext.append("ARB_texture_storage"); 451 unsupported_ext.append("ARB_texture_storage");
452 if (!GLAD_GL_ARB_multi_bind)
453 unsupported_ext.append("ARB_multi_bind");
452 454
453 // Extensions required to support some texture formats. 455 // Extensions required to support some texture formats.
454 if (!GLAD_GL_EXT_texture_compression_s3tc) 456 if (!GLAD_GL_EXT_texture_compression_s3tc)
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index f00b5a66b..991abda2e 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -108,6 +108,8 @@ void Config::ReadValues() {
108 108
109 // Audio 109 // Audio
110 Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); 110 Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
111 Settings::values.enable_audio_stretching =
112 sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
111 Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); 113 Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
112 Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); 114 Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
113 115
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 6ed9e7962..002a4ec15 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -150,6 +150,12 @@ swap_screen =
150# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) 150# auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available)
151output_engine = 151output_engine =
152 152
153# Whether or not to enable the audio-stretching post-processing effect.
154# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
155# at the cost of increasing audio latency.
156# 0: No, 1 (default): Yes
157enable_audio_stretching =
158
153# Which audio device to use. 159# Which audio device to use.
154# auto (default): Auto-select 160# auto (default): Auto-select
155output_device = 161output_device =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index d213929bd..0733301b2 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -96,6 +96,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
96 unsupported_ext.push_back("ARB_base_instance"); 96 unsupported_ext.push_back("ARB_base_instance");
97 if (!GLAD_GL_ARB_texture_storage) 97 if (!GLAD_GL_ARB_texture_storage)
98 unsupported_ext.push_back("ARB_texture_storage"); 98 unsupported_ext.push_back("ARB_texture_storage");
99 if (!GLAD_GL_ARB_multi_bind)
100 unsupported_ext.push_back("ARB_multi_bind");
99 101
100 // Extensions required to support some texture formats. 102 // Extensions required to support some texture formats.
101 if (!GLAD_GL_EXT_texture_compression_s3tc) 103 if (!GLAD_GL_EXT_texture_compression_s3tc)