summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/android/app/src/main/res/values/arrays.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
-rw-r--r--src/audio_core/CMakeLists.txt11
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp3
-rw-r--r--src/audio_core/sink/oboe_sink.cpp223
-rw-r--r--src/audio_core/sink/oboe_sink.h75
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp3
-rw-r--r--src/audio_core/sink/sink.h12
-rw-r--r--src/audio_core/sink/sink_details.cpp13
-rw-r--r--src/audio_core/sink/sink_stream.cpp41
-rw-r--r--src/common/fs/path_util.cpp6
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/settings_enums.h7
-rw-r--r--src/core/hle/service/audio/audren_u.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.h7
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp18
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp35
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_core.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/buffer_slot.h1
-rw-r--r--src/tests/video_core/memory_tracker.cpp6
-rw-r--r--src/video_core/buffer_cache/word_manager.h2
-rw-r--r--src/video_core/rasterizer_accelerated.cpp99
-rw-r--r--src/video_core/rasterizer_accelerated.h29
-rw-r--r--src/video_core/rasterizer_interface.h2
-rw-r--r--src/video_core/shader_cache.cpp4
-rw-r--r--src/video_core/texture_cache/texture_cache.h10
-rw-r--r--src/yuzu/configuration/configure_ui.cpp4
-rw-r--r--src/yuzu/configuration/qt_config.cpp5
-rw-r--r--src/yuzu/main.cpp6
-rw-r--r--src/yuzu/uisettings.h3
-rw-r--r--vcpkg.json9
32 files changed, 524 insertions, 124 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 18b8f7967..9dfc06ac3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
142 if (ENABLE_WEB_SERVICE) 142 if (ENABLE_WEB_SERVICE)
143 list(APPEND VCPKG_MANIFEST_FEATURES "web-service") 143 list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
144 endif() 144 endif()
145 if (ANDROID)
146 list(APPEND VCPKG_MANIFEST_FEATURES "android")
147 endif()
145 148
146 include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) 149 include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
147elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") 150elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index ab435dce9..e3915ef4f 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -256,11 +256,13 @@
256 256
257 <string-array name="outputEngineEntries"> 257 <string-array name="outputEngineEntries">
258 <item>@string/auto</item> 258 <item>@string/auto</item>
259 <item>@string/oboe</item>
259 <item>@string/cubeb</item> 260 <item>@string/cubeb</item>
260 <item>@string/string_null</item> 261 <item>@string/string_null</item>
261 </string-array> 262 </string-array>
262 <integer-array name="outputEngineValues"> 263 <integer-array name="outputEngineValues">
263 <item>0</item> 264 <item>0</item>
265 <item>4</item>
264 <item>1</item> 266 <item>1</item>
265 <item>3</item> 267 <item>3</item>
266 </integer-array> 268 </integer-array>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c86c43df2..0b80b04a4 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -503,6 +503,7 @@
503 <string name="theme_mode_dark">Dark</string> 503 <string name="theme_mode_dark">Dark</string>
504 504
505 <!-- Audio output engines --> 505 <!-- Audio output engines -->
506 <string name="oboe">oboe</string>
506 <string name="cubeb">cubeb</string> 507 <string name="cubeb">cubeb</string>
507 508
508 <!-- Black backgrounds theme --> 509 <!-- Black backgrounds theme -->
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 400988c5f..e982d03be 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -253,6 +253,17 @@ if (ENABLE_SDL2)
253 target_compile_definitions(audio_core PRIVATE HAVE_SDL2) 253 target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
254endif() 254endif()
255 255
256if (ANDROID)
257 target_sources(audio_core PRIVATE
258 sink/oboe_sink.cpp
259 sink/oboe_sink.h
260 )
261
262 # FIXME: this port seems broken, it cannot be imported with find_package(oboe REQUIRED)
263 target_link_libraries(audio_core PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/liboboe.a")
264 target_compile_definitions(audio_core PRIVATE HAVE_OBOE)
265endif()
266
256if (YUZU_USE_PRECOMPILED_HEADERS) 267if (YUZU_USE_PRECOMPILED_HEADERS)
257 target_precompile_headers(audio_core PRIVATE precompiled_headers.h) 268 target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
258endif() 269endif()
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 51a23fe15..d97ca2a40 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -253,8 +253,9 @@ CubebSink::~CubebSink() {
253#endif 253#endif
254} 254}
255 255
256SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels, 256SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
257 const std::string& name, StreamType type) { 257 const std::string& name, StreamType type) {
258 system_channels = system_channels_;
258 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( 259 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
259 ctx, device_channels, system_channels, output_device, input_device, name, type, system)); 260 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
260 261
diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp
new file mode 100644
index 000000000..e61841172
--- /dev/null
+++ b/src/audio_core/sink/oboe_sink.cpp
@@ -0,0 +1,223 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <span>
5#include <vector>
6
7#include <oboe/Oboe.h>
8
9#include "audio_core/common/common.h"
10#include "audio_core/sink/oboe_sink.h"
11#include "audio_core/sink/sink_stream.h"
12#include "common/logging/log.h"
13#include "common/scope_exit.h"
14#include "core/core.h"
15
16namespace AudioCore::Sink {
17
18class OboeSinkStream final : public SinkStream,
19 public oboe::AudioStreamDataCallback,
20 public oboe::AudioStreamErrorCallback {
21public:
22 explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
23 u32 system_channels_)
24 : SinkStream(system_, type_) {
25 name = name_;
26 system_channels = system_channels_;
27
28 this->OpenStream();
29 }
30
31 ~OboeSinkStream() override {
32 LOG_INFO(Audio_Sink, "Destroyed Oboe stream");
33 }
34
35 void Finalize() override {
36 this->Stop();
37 m_stream.reset();
38 }
39
40 void Start(bool resume = false) override {
41 if (!m_stream || !paused) {
42 return;
43 }
44
45 paused = false;
46
47 if (m_stream->start() != oboe::Result::OK) {
48 LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream");
49 }
50 }
51
52 void Stop() override {
53 if (!m_stream || paused) {
54 return;
55 }
56
57 this->SignalPause();
58
59 if (m_stream->stop() != oboe::Result::OK) {
60 LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream");
61 }
62 }
63
64public:
65 static s32 QueryChannelCount(oboe::Direction direction) {
66 std::shared_ptr<oboe::AudioStream> temp_stream;
67 oboe::AudioStreamBuilder builder;
68
69 const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream);
70 ASSERT(result == oboe::Result::OK);
71
72 return temp_stream->getChannelCount() >= 6 ? 6 : 2;
73 }
74
75protected:
76 oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
77 s32 num_buffer_frames) override {
78 const size_t num_channels = this->GetDeviceChannels();
79 const size_t frame_size = num_channels;
80 const size_t num_frames = static_cast<size_t>(num_buffer_frames);
81
82 if (type == StreamType::In) {
83 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data),
84 num_frames * frame_size};
85 this->ProcessAudioIn(input_buffer, num_frames);
86 } else {
87 std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data),
88 num_frames * frame_size};
89 this->ProcessAudioOutAndRender(output_buffer, num_frames);
90 }
91
92 return oboe::DataCallbackResult::Continue;
93 }
94
95 void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override {
96 LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing");
97
98 if (this->OpenStream()) {
99 m_stream->start();
100 }
101 }
102
103private:
104 static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder,
105 oboe::Direction direction) {
106 // TODO: investigate callback delay issues when using AAudio
107 return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency)
108 ->setAudioApi(oboe::AudioApi::OpenSLES)
109 ->setDirection(direction)
110 ->setSampleRate(TargetSampleRate)
111 ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
112 ->setFormat(oboe::AudioFormat::I16)
113 ->setFormatConversionAllowed(true)
114 ->setUsage(oboe::Usage::Game)
115 ->setBufferCapacityInFrames(TargetSampleCount * 2);
116 }
117
118 bool OpenStream() {
119 const auto direction = [&]() {
120 switch (type) {
121 case StreamType::In:
122 return oboe::Direction::Input;
123 case StreamType::Out:
124 case StreamType::Render:
125 return oboe::Direction::Output;
126 default:
127 ASSERT(false);
128 return oboe::Direction::Output;
129 }
130 }();
131
132 const auto expected_channels = QueryChannelCount(direction);
133 const auto expected_mask = [&]() {
134 switch (expected_channels) {
135 case 1:
136 return oboe::ChannelMask::Mono;
137 case 2:
138 return oboe::ChannelMask::Stereo;
139 case 6:
140 return oboe::ChannelMask::CM5Point1;
141 default:
142 ASSERT(false);
143 return oboe::ChannelMask::Unspecified;
144 }
145 }();
146
147 oboe::AudioStreamBuilder builder;
148 const auto result = ConfigureBuilder(builder, direction)
149 ->setChannelCount(expected_channels)
150 ->setChannelMask(expected_mask)
151 ->setChannelConversionAllowed(true)
152 ->setDataCallback(this)
153 ->setErrorCallback(this)
154 ->openStream(m_stream);
155 ASSERT(result == oboe::Result::OK);
156 return result == oboe::Result::OK && this->SetStreamProperties();
157 }
158
159 bool SetStreamProperties() {
160 ASSERT(m_stream);
161
162 m_stream->setBufferSizeInFrames(TargetSampleCount * 2);
163 device_channels = m_stream->getChannelCount();
164
165 const auto sample_rate = m_stream->getSampleRate();
166 const auto buffer_capacity = m_stream->getBufferCapacityInFrames();
167 const auto stream_backend =
168 m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES";
169
170 LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}",
171 stream_backend, device_channels, sample_rate, buffer_capacity);
172
173 return true;
174 }
175
176 std::shared_ptr<oboe::AudioStream> m_stream{};
177};
178
179OboeSink::OboeSink() {
180 // TODO: This is not generally knowable
181 // The channel count is distinct based on direction and can change
182 device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output);
183}
184
185OboeSink::~OboeSink() = default;
186
187SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
188 const std::string& name, StreamType type) {
189 SinkStreamPtr& stream = sink_streams.emplace_back(
190 std::make_unique<OboeSinkStream>(system, type, name, system_channels));
191
192 return stream.get();
193}
194
195void OboeSink::CloseStream(SinkStream* to_remove) {
196 sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; });
197}
198
199void OboeSink::CloseStreams() {
200 sink_streams.clear();
201}
202
203f32 OboeSink::GetDeviceVolume() const {
204 if (sink_streams.empty()) {
205 return 1.0f;
206 }
207
208 return sink_streams.front()->GetDeviceVolume();
209}
210
211void OboeSink::SetDeviceVolume(f32 volume) {
212 for (auto& stream : sink_streams) {
213 stream->SetDeviceVolume(volume);
214 }
215}
216
217void OboeSink::SetSystemVolume(f32 volume) {
218 for (auto& stream : sink_streams) {
219 stream->SetSystemVolume(volume);
220 }
221}
222
223} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/oboe_sink.h b/src/audio_core/sink/oboe_sink.h
new file mode 100644
index 000000000..8f6f54ab5
--- /dev/null
+++ b/src/audio_core/sink/oboe_sink.h
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <list>
7#include <string>
8
9#include "audio_core/sink/sink.h"
10
11namespace Core {
12class System;
13}
14
15namespace AudioCore::Sink {
16class SinkStream;
17
18class OboeSink final : public Sink {
19public:
20 explicit OboeSink();
21 ~OboeSink() override;
22
23 /**
24 * Create a new sink stream.
25 *
26 * @param system - Core system.
27 * @param system_channels - Number of channels the audio system expects.
28 * May differ from the device's channel count.
29 * @param name - Name of this stream.
30 * @param type - Type of this stream, render/in/out.
31 *
32 * @return A pointer to the created SinkStream
33 */
34 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
35 const std::string& name, StreamType type) override;
36
37 /**
38 * Close a given stream.
39 *
40 * @param stream - The stream to close.
41 */
42 void CloseStream(SinkStream* stream) override;
43
44 /**
45 * Close all streams.
46 */
47 void CloseStreams() override;
48
49 /**
50 * Get the device volume. Set from calls to the IAudioDevice service.
51 *
52 * @return Volume of the device.
53 */
54 f32 GetDeviceVolume() const override;
55
56 /**
57 * Set the device volume. Set from calls to the IAudioDevice service.
58 *
59 * @param volume - New volume of the device.
60 */
61 void SetDeviceVolume(f32 volume) override;
62
63 /**
64 * Set the system volume. Comes from the audio system using this stream.
65 *
66 * @param volume - New volume of the system.
67 */
68 void SetSystemVolume(f32 volume) override;
69
70private:
71 /// List of streams managed by this sink
72 std::list<SinkStreamPtr> sink_streams{};
73};
74
75} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index 96e0efce2..7dd155ff0 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -168,8 +168,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
168 168
169SDLSink::~SDLSink() = default; 169SDLSink::~SDLSink() = default;
170 170
171SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels, 171SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
172 const std::string&, StreamType type) { 172 const std::string&, StreamType type) {
173 system_channels = system_channels_;
173 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( 174 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
174 device_channels, system_channels, output_device, input_device, type, system)); 175 device_channels, system_channels, output_device, input_device, type, system));
175 return stream.get(); 176 return stream.get();
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
index f28c6d126..e22e8c3e5 100644
--- a/src/audio_core/sink/sink.h
+++ b/src/audio_core/sink/sink.h
@@ -85,9 +85,21 @@ public:
85 */ 85 */
86 virtual void SetSystemVolume(f32 volume) = 0; 86 virtual void SetSystemVolume(f32 volume) = 0;
87 87
88 /**
89 * Get the number of channels the game has set, can be different to the host hardware's support.
90 * Either 2 or 6.
91 *
92 * @return Number of device channels.
93 */
94 u32 GetSystemChannels() const {
95 return system_channels;
96 }
97
88protected: 98protected:
89 /// Number of device channels supported by the hardware 99 /// Number of device channels supported by the hardware
90 u32 device_channels{2}; 100 u32 device_channels{2};
101 /// Number of channels the game is sending
102 u32 system_channels{2};
91}; 103};
92 104
93using SinkPtr = std::unique_ptr<Sink>; 105using SinkPtr = std::unique_ptr<Sink>;
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 7c9a4e3ac..449af659d 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -7,6 +7,9 @@
7#include <vector> 7#include <vector>
8 8
9#include "audio_core/sink/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_OBOE
11#include "audio_core/sink/oboe_sink.h"
12#endif
10#ifdef HAVE_CUBEB 13#ifdef HAVE_CUBEB
11#include "audio_core/sink/cubeb_sink.h" 14#include "audio_core/sink/cubeb_sink.h"
12#endif 15#endif
@@ -36,6 +39,16 @@ struct SinkDetails {
36 39
37// sink_details is ordered in terms of desirability, with the best choice at the top. 40// sink_details is ordered in terms of desirability, with the best choice at the top.
38constexpr SinkDetails sink_details[] = { 41constexpr SinkDetails sink_details[] = {
42#ifdef HAVE_OBOE
43 SinkDetails{
44 Settings::AudioEngine::Oboe,
45 [](std::string_view device_id) -> std::unique_ptr<Sink> {
46 return std::make_unique<OboeSink>();
47 },
48 [](bool capture) { return std::vector<std::string>{"Default"}; },
49 []() { return true; },
50 },
51#endif
39#ifdef HAVE_CUBEB 52#ifdef HAVE_CUBEB
40 SinkDetails{ 53 SinkDetails{
41 Settings::AudioEngine::Cubeb, 54 Settings::AudioEngine::Cubeb,
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 2a09db599..c047b0668 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -40,29 +40,36 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
40 40
41 if (system_channels == 6 && device_channels == 2) { 41 if (system_channels == 6 && device_channels == 2) {
42 // We're given 6 channels, but our device only outputs 2, so downmix. 42 // We're given 6 channels, but our device only outputs 2, so downmix.
43 static constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; 43 // Front = 1.0
44 // Center = 0.596
45 // LFE = 0.354
46 // Back = 0.707
47 static constexpr std::array<f32, 4> down_mix_coeff{1.0, 0.596f, 0.354f, 0.707f};
44 48
45 for (u32 read_index = 0, write_index = 0; read_index < samples.size(); 49 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
46 read_index += system_channels, write_index += device_channels) { 50 read_index += system_channels, write_index += device_channels) {
51 const auto fl =
52 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]);
53 const auto fr =
54 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]);
55 const auto c =
56 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::Center)]);
57 const auto lfe =
58 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::LFE)]);
59 const auto bl =
60 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackLeft)]);
61 const auto br =
62 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackRight)]);
63
47 const auto left_sample{ 64 const auto left_sample{
48 ((Common::FixedPoint<49, 15>( 65 static_cast<s32>((fl * down_mix_coeff[0] + c * down_mix_coeff[1] +
49 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * 66 lfe * down_mix_coeff[2] + bl * down_mix_coeff[3]) *
50 down_mix_coeff[0] + 67 volume)};
51 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
52 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
53 samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
54 volume)
55 .to_int()};
56 68
57 const auto right_sample{ 69 const auto right_sample{
58 ((Common::FixedPoint<49, 15>( 70 static_cast<s32>((fr * down_mix_coeff[0] + c * down_mix_coeff[1] +
59 samples[read_index + static_cast<u32>(Channels::FrontRight)]) * 71 lfe * down_mix_coeff[2] + br * down_mix_coeff[3]) *
60 down_mix_coeff[0] + 72 volume)};
61 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
62 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
63 samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
64 volume)
65 .to_int()};
66 73
67 samples[write_index + static_cast<u32>(Channels::FrontLeft)] = 74 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
68 static_cast<s16>(std::clamp(left_sample, min, max)); 75 static_cast<s16>(std::clamp(left_sample, min, max));
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index d2f50432a..4f69db6f5 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -418,9 +418,9 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
418 return std::string(RemoveTrailingSlash(path)); 418 return std::string(RemoveTrailingSlash(path));
419} 419}
420 420
421std::string_view GetParentPath(std::string_view path) { 421std::string GetParentPath(std::string_view path) {
422 if (path.empty()) { 422 if (path.empty()) {
423 return path; 423 return std::string(path);
424 } 424 }
425 425
426#ifdef ANDROID 426#ifdef ANDROID
@@ -439,7 +439,7 @@ std::string_view GetParentPath(std::string_view path) {
439 name_index = std::max(name_bck_index, name_fwd_index); 439 name_index = std::max(name_bck_index, name_fwd_index);
440 } 440 }
441 441
442 return path.substr(0, name_index); 442 return std::string(path.substr(0, name_index));
443} 443}
444 444
445std::string_view GetPathWithoutTop(std::string_view path) { 445std::string_view GetPathWithoutTop(std::string_view path) {
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 23c8b1359..59301e7ed 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -302,7 +302,7 @@ enum class DirectorySeparator {
302 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); 302 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
303 303
304// Gets all of the text up to the last '/' or '\' in the path. 304// Gets all of the text up to the last '/' or '\' in the path.
305[[nodiscard]] std::string_view GetParentPath(std::string_view path); 305[[nodiscard]] std::string GetParentPath(std::string_view path);
306 306
307// Gets all of the text after the first '/' or '\' in the path. 307// Gets all of the text after the first '/' or '\' in the path.
308[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); 308[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index d6351e57e..617036588 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -82,16 +82,15 @@ enum class AudioEngine : u32 {
82 Cubeb, 82 Cubeb,
83 Sdl2, 83 Sdl2,
84 Null, 84 Null,
85 Oboe,
85}; 86};
86 87
87template <> 88template <>
88inline std::vector<std::pair<std::string, AudioEngine>> 89inline std::vector<std::pair<std::string, AudioEngine>>
89EnumMetadata<AudioEngine>::Canonicalizations() { 90EnumMetadata<AudioEngine>::Canonicalizations() {
90 return { 91 return {
91 {"auto", AudioEngine::Auto}, 92 {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
92 {"cubeb", AudioEngine::Cubeb}, 93 {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
93 {"sdl2", AudioEngine::Sdl2},
94 {"null", AudioEngine::Null},
95 }; 94 };
96} 95}
97 96
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 2f09cade5..23e56c77a 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -359,7 +359,7 @@ private:
359 359
360 void GetActiveChannelCount(HLERequestContext& ctx) { 360 void GetActiveChannelCount(HLERequestContext& ctx) {
361 const auto& sink{system.AudioCore().GetOutputSink()}; 361 const auto& sink{system.AudioCore().GetOutputSink()};
362 u32 channel_count{sink.GetDeviceChannels()}; 362 u32 channel_count{sink.GetSystemChannels()};
363 363
364 LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); 364 LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count);
365 365
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index e7e7c4c28..276d264e1 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -54,6 +54,13 @@ enum class ImageDirectoryId : u32 {
54 SdCard, 54 SdCard,
55}; 55};
56 56
57enum class OpenDirectoryMode : u64 {
58 Directory = (1 << 0),
59 File = (1 << 1),
60 All = Directory | File
61};
62DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
63
57class FileSystemController { 64class FileSystemController {
58public: 65public:
59 explicit FileSystemController(Core::System& system_); 66 explicit FileSystemController(Core::System& system_);
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index b1310d6e4..82ecc1b90 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -259,7 +259,7 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec
259 259
260class IDirectory final : public ServiceFramework<IDirectory> { 260class IDirectory final : public ServiceFramework<IDirectory> {
261public: 261public:
262 explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_) 262 explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_, OpenDirectoryMode mode)
263 : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) { 263 : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) {
264 static const FunctionInfo functions[] = { 264 static const FunctionInfo functions[] = {
265 {0, &IDirectory::Read, "Read"}, 265 {0, &IDirectory::Read, "Read"},
@@ -269,8 +269,12 @@ public:
269 269
270 // TODO(DarkLordZach): Verify that this is the correct behavior. 270 // TODO(DarkLordZach): Verify that this is the correct behavior.
271 // Build entry index now to save time later. 271 // Build entry index now to save time later.
272 BuildEntryIndex(entries, backend->GetFiles(), FileSys::EntryType::File); 272 if (True(mode & OpenDirectoryMode::Directory)) {
273 BuildEntryIndex(entries, backend->GetSubdirectories(), FileSys::EntryType::Directory); 273 BuildEntryIndex(entries, backend->GetSubdirectories(), FileSys::EntryType::Directory);
274 }
275 if (True(mode & OpenDirectoryMode::File)) {
276 BuildEntryIndex(entries, backend->GetFiles(), FileSys::EntryType::File);
277 }
274 } 278 }
275 279
276private: 280private:
@@ -446,11 +450,9 @@ public:
446 450
447 const auto file_buffer = ctx.ReadBuffer(); 451 const auto file_buffer = ctx.ReadBuffer();
448 const std::string name = Common::StringFromBuffer(file_buffer); 452 const std::string name = Common::StringFromBuffer(file_buffer);
453 const auto mode = rp.PopRaw<OpenDirectoryMode>();
449 454
450 // TODO(Subv): Implement this filter. 455 LOG_DEBUG(Service_FS, "called. directory={}, mode={}", name, mode);
451 const u32 filter_flags = rp.Pop<u32>();
452
453 LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags);
454 456
455 FileSys::VirtualDir vfs_dir{}; 457 FileSys::VirtualDir vfs_dir{};
456 auto result = backend.OpenDirectory(&vfs_dir, name); 458 auto result = backend.OpenDirectory(&vfs_dir, name);
@@ -460,7 +462,7 @@ public:
460 return; 462 return;
461 } 463 }
462 464
463 auto directory = std::make_shared<IDirectory>(system, vfs_dir); 465 auto directory = std::make_shared<IDirectory>(system, vfs_dir, mode);
464 466
465 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 467 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
466 rb.Push(ResultSuccess); 468 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
index d91886bed..bbe8e06d4 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
@@ -90,6 +90,18 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
90 90
91 LOG_DEBUG(Service_Nvnflinger, "acquiring slot={}", slot); 91 LOG_DEBUG(Service_Nvnflinger, "acquiring slot={}", slot);
92 92
93 // If the front buffer is still being tracked, update its slot state
94 if (core->StillTracking(*front)) {
95 slots[slot].acquire_called = true;
96 slots[slot].needs_cleanup_on_release = false;
97 slots[slot].buffer_state = BufferState::Acquired;
98
99 // TODO: for now, avoid resetting the fence, so that when we next return this
100 // slot to the producer, it will wait for the fence to pass. We should fix this
101 // by properly waiting for the fence in the BufferItemConsumer.
102 // slots[slot].fence = Fence::NoFence();
103 }
104
93 // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to 105 // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
94 // avoid unnecessarily remapping this buffer on the consumer side. 106 // avoid unnecessarily remapping this buffer on the consumer side.
95 if (out_buffer->acquire_called) { 107 if (out_buffer->acquire_called) {
@@ -132,11 +144,28 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc
132 ++current; 144 ++current;
133 } 145 }
134 146
135 slots[slot].buffer_state = BufferState::Free; 147 if (slots[slot].buffer_state == BufferState::Acquired) {
148 // TODO: for now, avoid resetting the fence, so that when we next return this
149 // slot to the producer, it can wait for its own fence to pass. We should fix this
150 // by properly waiting for the fence in the BufferItemConsumer.
151 // slots[slot].fence = release_fence;
152 slots[slot].buffer_state = BufferState::Free;
136 153
137 listener = core->connected_producer_listener; 154 listener = core->connected_producer_listener;
138 155
139 LOG_DEBUG(Service_Nvnflinger, "releasing slot {}", slot); 156 LOG_DEBUG(Service_Nvnflinger, "releasing slot {}", slot);
157 } else if (slots[slot].needs_cleanup_on_release) {
158 LOG_DEBUG(Service_Nvnflinger, "releasing a stale buffer slot {} (state = {})", slot,
159 slots[slot].buffer_state);
160 slots[slot].needs_cleanup_on_release = false;
161 return Status::StaleBufferSlot;
162 } else {
163 LOG_ERROR(Service_Nvnflinger,
164 "attempted to release buffer slot {} but its state was {}", slot,
165 slots[slot].buffer_state);
166
167 return Status::BadValue;
168 }
140 169
141 core->SignalDequeueCondition(); 170 core->SignalDequeueCondition();
142 } 171 }
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
index 4ed5e5978..5d8c861fa 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
@@ -74,6 +74,10 @@ void BufferQueueCore::FreeBufferLocked(s32 slot) {
74 74
75 slots[slot].graphic_buffer.reset(); 75 slots[slot].graphic_buffer.reset();
76 76
77 if (slots[slot].buffer_state == BufferState::Acquired) {
78 slots[slot].needs_cleanup_on_release = true;
79 }
80
77 slots[slot].buffer_state = BufferState::Free; 81 slots[slot].buffer_state = BufferState::Free;
78 slots[slot].frame_number = UINT32_MAX; 82 slots[slot].frame_number = UINT32_MAX;
79 slots[slot].acquire_called = false; 83 slots[slot].acquire_called = false;
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d25bca049..37daca78b 100644
--- a/src/core/hle/service/nvnflinger/buffer_slot.h
+++ b/src/core/hle/service/nvnflinger/buffer_slot.h
@@ -31,6 +31,7 @@ struct BufferSlot final {
31 u64 frame_number{}; 31 u64 frame_number{};
32 Fence fence; 32 Fence fence;
33 bool acquire_called{}; 33 bool acquire_called{};
34 bool needs_cleanup_on_release{};
34 bool attached_by_consumer{}; 35 bool attached_by_consumer{};
35 bool is_preallocated{}; 36 bool is_preallocated{};
36}; 37};
diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp
index 2dbff21af..618793668 100644
--- a/src/tests/video_core/memory_tracker.cpp
+++ b/src/tests/video_core/memory_tracker.cpp
@@ -23,13 +23,13 @@ constexpr VAddr c = 16 * HIGH_PAGE_SIZE;
23 23
24class RasterizerInterface { 24class RasterizerInterface {
25public: 25public:
26 void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) { 26 void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
27 const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS}; 27 const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
28 const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >> 28 const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
29 Core::Memory::YUZU_PAGEBITS}; 29 Core::Memory::YUZU_PAGEBITS};
30 for (u64 page = page_start; page < page_end; ++page) { 30 for (u64 page = page_start; page < page_end; ++page) {
31 int& value = page_table[page]; 31 int& value = page_table[page];
32 value += (cache ? 1 : -1); 32 value += delta;
33 if (value < 0) { 33 if (value < 0) {
34 throw std::logic_error{"negative page"}; 34 throw std::logic_error{"negative page"};
35 } 35 }
@@ -546,4 +546,4 @@ TEST_CASE("MemoryTracker: Cached write downloads") {
546 REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE)); 546 REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
547 memory_track->MarkRegionAsCpuModified(c, WORD); 547 memory_track->MarkRegionAsCpuModified(c, WORD);
548 REQUIRE(rasterizer.Count() == 0); 548 REQUIRE(rasterizer.Count() == 0);
549} 549} \ No newline at end of file
diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h
index 95b752055..a336bde41 100644
--- a/src/video_core/buffer_cache/word_manager.h
+++ b/src/video_core/buffer_cache/word_manager.h
@@ -473,7 +473,7 @@ private:
473 VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; 473 VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
474 IteratePages(changed_bits, [&](size_t offset, size_t size) { 474 IteratePages(changed_bits, [&](size_t offset, size_t size) {
475 rasterizer->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, 475 rasterizer->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE,
476 size * BYTES_PER_PAGE, add_to_rasterizer); 476 size * BYTES_PER_PAGE, add_to_rasterizer ? 1 : -1);
477 }); 477 });
478 } 478 }
479 479
diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp
index 3c9477f6e..f200a650f 100644
--- a/src/video_core/rasterizer_accelerated.cpp
+++ b/src/video_core/rasterizer_accelerated.cpp
@@ -3,7 +3,6 @@
3 3
4#include <atomic> 4#include <atomic>
5 5
6#include "common/alignment.h"
7#include "common/assert.h" 6#include "common/assert.h"
8#include "common/common_types.h" 7#include "common/common_types.h"
9#include "common/div_ceil.h" 8#include "common/div_ceil.h"
@@ -12,65 +11,61 @@
12 11
13namespace VideoCore { 12namespace VideoCore {
14 13
15static constexpr u16 IdentityValue = 1;
16
17using namespace Core::Memory; 14using namespace Core::Memory;
18 15
19RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : map{}, cpu_memory{cpu_memory_} { 16RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_)
20 // We are tracking CPU memory, which cannot map more than 39 bits. 17 : cached_pages(std::make_unique<CachedPages>()), cpu_memory{cpu_memory_} {}
21 const VAddr start_address = 0;
22 const VAddr end_address = (1ULL << 39);
23 const IntervalType address_space_interval(start_address, end_address);
24 const auto value = std::make_pair(address_space_interval, IdentityValue);
25
26 map.add(value);
27}
28 18
29RasterizerAccelerated::~RasterizerAccelerated() = default; 19RasterizerAccelerated::~RasterizerAccelerated() = default;
30 20
31void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) { 21void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
32 std::scoped_lock lk{map_lock}; 22 u64 uncache_begin = 0;
33 23 u64 cache_begin = 0;
34 // Align sizes. 24 u64 uncache_bytes = 0;
35 addr = Common::AlignDown(addr, YUZU_PAGESIZE); 25 u64 cache_bytes = 0;
36 size = Common::AlignUp(size, YUZU_PAGESIZE); 26
37 27 std::atomic_thread_fence(std::memory_order_acquire);
38 // Declare the overall interval we are going to operate on. 28 const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
39 const VAddr start_address = addr; 29 for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) {
40 const VAddr end_address = addr + size; 30 std::atomic_uint16_t& count = cached_pages->at(page >> 2).Count(page);
41 const IntervalType modification_range(start_address, end_address); 31
42 32 if (delta > 0) {
43 // Find the boundaries of where to iterate. 33 ASSERT_MSG(count.load(std::memory_order::relaxed) < UINT16_MAX, "Count may overflow!");
44 const auto lower = map.lower_bound(modification_range); 34 } else if (delta < 0) {
45 const auto upper = map.upper_bound(modification_range); 35 ASSERT_MSG(count.load(std::memory_order::relaxed) > 0, "Count may underflow!");
46 36 } else {
47 // Iterate over the contained intervals. 37 ASSERT_MSG(false, "Delta must be non-zero!");
48 for (auto it = lower; it != upper; it++) { 38 }
49 // Intersect interval range with modification range.
50 const auto current_range = modification_range & it->first;
51
52 // Calculate the address and size to operate over.
53 const auto current_addr = current_range.lower();
54 const auto current_size = current_range.upper() - current_addr;
55
56 // Get the current value of the range.
57 const auto value = it->second;
58 39
59 if (cache && value == IdentityValue) { 40 // Adds or subtracts 1, as count is a unsigned 8-bit value
60 // If we are going to cache, and the value is not yet referenced, then cache this range. 41 count.fetch_add(static_cast<u16>(delta), std::memory_order_release);
61 cpu_memory.RasterizerMarkRegionCached(current_addr, current_size, true); 42
62 } else if (!cache && value == IdentityValue + 1) { 43 // Assume delta is either -1 or 1
63 // If we are going to uncache, and this is the last reference, then uncache this range. 44 if (count.load(std::memory_order::relaxed) == 0) {
64 cpu_memory.RasterizerMarkRegionCached(current_addr, current_size, false); 45 if (uncache_bytes == 0) {
46 uncache_begin = page;
47 }
48 uncache_bytes += YUZU_PAGESIZE;
49 } else if (uncache_bytes > 0) {
50 cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes,
51 false);
52 uncache_bytes = 0;
53 }
54 if (count.load(std::memory_order::relaxed) == 1 && delta > 0) {
55 if (cache_bytes == 0) {
56 cache_begin = page;
57 }
58 cache_bytes += YUZU_PAGESIZE;
59 } else if (cache_bytes > 0) {
60 cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
61 cache_bytes = 0;
65 } 62 }
66 } 63 }
67 64 if (uncache_bytes > 0) {
68 // Update the set. 65 cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, false);
69 const auto value = std::make_pair(modification_range, IdentityValue); 66 }
70 if (cache) { 67 if (cache_bytes > 0) {
71 map.add(value); 68 cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
72 } else {
73 map.subtract(value);
74 } 69 }
75} 70}
76 71
diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h
index f1968f186..e6c0ea87a 100644
--- a/src/video_core/rasterizer_accelerated.h
+++ b/src/video_core/rasterizer_accelerated.h
@@ -3,8 +3,8 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <mutex> 6#include <array>
7#include <boost/icl/interval_map.hpp> 7#include <atomic>
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/rasterizer_interface.h" 10#include "video_core/rasterizer_interface.h"
@@ -21,17 +21,28 @@ public:
21 explicit RasterizerAccelerated(Core::Memory::Memory& cpu_memory_); 21 explicit RasterizerAccelerated(Core::Memory::Memory& cpu_memory_);
22 ~RasterizerAccelerated() override; 22 ~RasterizerAccelerated() override;
23 23
24 void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) override; 24 void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
25 25
26private: 26private:
27 using PageIndex = VAddr; 27 class CacheEntry final {
28 using PageReferenceCount = u16; 28 public:
29 CacheEntry() = default;
29 30
30 using IntervalMap = boost::icl::interval_map<PageIndex, PageReferenceCount>; 31 std::atomic_uint16_t& Count(std::size_t page) {
31 using IntervalType = IntervalMap::interval_type; 32 return values[page & 3];
33 }
32 34
33 IntervalMap map; 35 const std::atomic_uint16_t& Count(std::size_t page) const {
34 std::mutex map_lock; 36 return values[page & 3];
37 }
38
39 private:
40 std::array<std::atomic_uint16_t, 4> values{};
41 };
42 static_assert(sizeof(CacheEntry) == 8, "CacheEntry should be 8 bytes!");
43
44 using CachedPages = std::array<CacheEntry, 0x2000000>;
45 std::unique_ptr<CachedPages> cached_pages;
35 Core::Memory::Memory& cpu_memory; 46 Core::Memory::Memory& cpu_memory;
36}; 47};
37 48
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index fd42d26b5..af1469147 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -162,7 +162,7 @@ public:
162 } 162 }
163 163
164 /// Increase/decrease the number of object in pages touching the specified region 164 /// Increase/decrease the number of object in pages touching the specified region
165 virtual void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) {} 165 virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
166 166
167 /// Initialize disk cached resources for the game being emulated 167 /// Initialize disk cached resources for the game being emulated
168 virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading, 168 virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index a109f9cbe..e81cd031b 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -132,7 +132,7 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
132 132
133 storage.push_back(std::move(data)); 133 storage.push_back(std::move(data));
134 134
135 rasterizer.UpdatePagesCachedCount(addr, size, true); 135 rasterizer.UpdatePagesCachedCount(addr, size, 1);
136} 136}
137 137
138void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { 138void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
@@ -209,7 +209,7 @@ void ShaderCache::UnmarkMemory(Entry* entry) {
209 209
210 const VAddr addr = entry->addr_start; 210 const VAddr addr = entry->addr_start;
211 const size_t size = entry->addr_end - addr; 211 const size_t size = entry->addr_end - addr;
212 rasterizer.UpdatePagesCachedCount(addr, size, false); 212 rasterizer.UpdatePagesCachedCount(addr, size, -1);
213} 213}
214 214
215void ShaderCache::RemoveShadersFromStorage(std::span<ShaderInfo*> removed_shaders) { 215void ShaderCache::RemoveShadersFromStorage(std::span<ShaderInfo*> removed_shaders) {
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index d7941f6a4..0d5a1709f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -2080,7 +2080,7 @@ void TextureCache<P>::TrackImage(ImageBase& image, ImageId image_id) {
2080 ASSERT(False(image.flags & ImageFlagBits::Tracked)); 2080 ASSERT(False(image.flags & ImageFlagBits::Tracked));
2081 image.flags |= ImageFlagBits::Tracked; 2081 image.flags |= ImageFlagBits::Tracked;
2082 if (False(image.flags & ImageFlagBits::Sparse)) { 2082 if (False(image.flags & ImageFlagBits::Sparse)) {
2083 rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, true); 2083 rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1);
2084 return; 2084 return;
2085 } 2085 }
2086 if (True(image.flags & ImageFlagBits::Registered)) { 2086 if (True(image.flags & ImageFlagBits::Registered)) {
@@ -2091,13 +2091,13 @@ void TextureCache<P>::TrackImage(ImageBase& image, ImageId image_id) {
2091 const auto& map = slot_map_views[map_view_id]; 2091 const auto& map = slot_map_views[map_view_id];
2092 const VAddr cpu_addr = map.cpu_addr; 2092 const VAddr cpu_addr = map.cpu_addr;
2093 const std::size_t size = map.size; 2093 const std::size_t size = map.size;
2094 rasterizer.UpdatePagesCachedCount(cpu_addr, size, true); 2094 rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
2095 } 2095 }
2096 return; 2096 return;
2097 } 2097 }
2098 ForEachSparseSegment(image, 2098 ForEachSparseSegment(image,
2099 [this]([[maybe_unused]] GPUVAddr gpu_addr, VAddr cpu_addr, size_t size) { 2099 [this]([[maybe_unused]] GPUVAddr gpu_addr, VAddr cpu_addr, size_t size) {
2100 rasterizer.UpdatePagesCachedCount(cpu_addr, size, true); 2100 rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
2101 }); 2101 });
2102} 2102}
2103 2103
@@ -2106,7 +2106,7 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
2106 ASSERT(True(image.flags & ImageFlagBits::Tracked)); 2106 ASSERT(True(image.flags & ImageFlagBits::Tracked));
2107 image.flags &= ~ImageFlagBits::Tracked; 2107 image.flags &= ~ImageFlagBits::Tracked;
2108 if (False(image.flags & ImageFlagBits::Sparse)) { 2108 if (False(image.flags & ImageFlagBits::Sparse)) {
2109 rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, false); 2109 rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1);
2110 return; 2110 return;
2111 } 2111 }
2112 ASSERT(True(image.flags & ImageFlagBits::Registered)); 2112 ASSERT(True(image.flags & ImageFlagBits::Registered));
@@ -2117,7 +2117,7 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
2117 const auto& map = slot_map_views[map_view_id]; 2117 const auto& map = slot_map_views[map_view_id];
2118 const VAddr cpu_addr = map.cpu_addr; 2118 const VAddr cpu_addr = map.cpu_addr;
2119 const std::size_t size = map.size; 2119 const std::size_t size = map.size;
2120 rasterizer.UpdatePagesCachedCount(cpu_addr, size, false); 2120 rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
2121 } 2121 }
2122} 2122}
2123 2123
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index dd43f0a0e..c8e871151 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -193,8 +193,8 @@ void ConfigureUi::RequestGameListUpdate() {
193void ConfigureUi::SetConfiguration() { 193void ConfigureUi::SetConfiguration() {
194 ui->theme_combobox->setCurrentIndex( 194 ui->theme_combobox->setCurrentIndex(
195 ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme))); 195 ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme)));
196 ui->language_combobox->setCurrentIndex( 196 ui->language_combobox->setCurrentIndex(ui->language_combobox->findData(
197 ui->language_combobox->findData(QString::fromStdString(UISettings::values.language))); 197 QString::fromStdString(UISettings::values.language.GetValue())));
198 ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); 198 ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
199 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); 199 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
200 ui->show_size->setChecked(UISettings::values.show_size.GetValue()); 200 ui->show_size->setChecked(UISettings::values.show_size.GetValue());
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
index 636c5e640..417a43ec5 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -187,7 +187,6 @@ void QtConfig::ReadPathValues() {
187 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); 187 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
188 188
189 UISettings::values.roms_path = ReadStringSetting(std::string("romsPath")); 189 UISettings::values.roms_path = ReadStringSetting(std::string("romsPath"));
190 UISettings::values.symbols_path = ReadStringSetting(std::string("symbolsPath"));
191 UISettings::values.game_dir_deprecated = 190 UISettings::values.game_dir_deprecated =
192 ReadStringSetting(std::string("gameListRootDir"), std::string(".")); 191 ReadStringSetting(std::string("gameListRootDir"), std::string("."));
193 UISettings::values.game_dir_deprecated_deepscan = 192 UISettings::values.game_dir_deprecated_deepscan =
@@ -225,8 +224,6 @@ void QtConfig::ReadPathValues() {
225 UISettings::values.recent_files = 224 UISettings::values.recent_files =
226 QString::fromStdString(ReadStringSetting(std::string("recentFiles"))) 225 QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
227 .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive); 226 .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
228 UISettings::values.language =
229 ReadStringSetting(std::string("language"), std::make_optional(std::string("")));
230 227
231 EndGroup(); 228 EndGroup();
232} 229}
@@ -409,7 +406,6 @@ void QtConfig::SavePathValues() {
409 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); 406 BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
410 407
411 WriteSetting(std::string("romsPath"), UISettings::values.roms_path); 408 WriteSetting(std::string("romsPath"), UISettings::values.roms_path);
412 WriteSetting(std::string("symbolsPath"), UISettings::values.symbols_path);
413 BeginArray(std::string("gamedirs")); 409 BeginArray(std::string("gamedirs"));
414 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { 410 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
415 SetArrayIndex(i); 411 SetArrayIndex(i);
@@ -422,7 +418,6 @@ void QtConfig::SavePathValues() {
422 418
423 WriteSetting(std::string("recentFiles"), 419 WriteSetting(std::string("recentFiles"),
424 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); 420 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
425 WriteSetting(std::string("language"), UISettings::values.language);
426 421
427 EndGroup(); 422 EndGroup();
428} 423}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f31ed7ebb..059fcf041 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -5147,12 +5147,12 @@ void GMainWindow::UpdateUITheme() {
5147void GMainWindow::LoadTranslation() { 5147void GMainWindow::LoadTranslation() {
5148 bool loaded; 5148 bool loaded;
5149 5149
5150 if (UISettings::values.language.empty()) { 5150 if (UISettings::values.language.GetValue().empty()) {
5151 // If the selected language is empty, use system locale 5151 // If the selected language is empty, use system locale
5152 loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/")); 5152 loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
5153 } else { 5153 } else {
5154 // Otherwise load from the specified file 5154 // Otherwise load from the specified file
5155 loaded = translator.load(QString::fromStdString(UISettings::values.language), 5155 loaded = translator.load(QString::fromStdString(UISettings::values.language.GetValue()),
5156 QStringLiteral(":/languages/")); 5156 QStringLiteral(":/languages/"));
5157 } 5157 }
5158 5158
@@ -5164,7 +5164,7 @@ void GMainWindow::LoadTranslation() {
5164} 5164}
5165 5165
5166void GMainWindow::OnLanguageChanged(const QString& locale) { 5166void GMainWindow::OnLanguageChanged(const QString& locale) {
5167 if (UISettings::values.language != std::string("en")) { 5167 if (UISettings::values.language.GetValue() != std::string("en")) {
5168 qApp->removeTranslator(&translator); 5168 qApp->removeTranslator(&translator);
5169 } 5169 }
5170 5170
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 549a39e1b..f9906be33 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -154,12 +154,11 @@ struct Values {
154 Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots}; 154 Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
155 155
156 std::string roms_path; 156 std::string roms_path;
157 std::string symbols_path;
158 std::string game_dir_deprecated; 157 std::string game_dir_deprecated;
159 bool game_dir_deprecated_deepscan; 158 bool game_dir_deprecated_deepscan;
160 QVector<GameDir> game_dirs; 159 QVector<GameDir> game_dirs;
161 QStringList recent_files; 160 QStringList recent_files;
162 std::string language; 161 Setting<std::string> language{linkage, {}, "language", Category::Paths};
163 162
164 std::string theme; 163 std::string theme;
165 164
diff --git a/vcpkg.json b/vcpkg.json
index 01a4657d4..a9e895153 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -41,6 +41,15 @@
41 "platform": "windows" 41 "platform": "windows"
42 } 42 }
43 ] 43 ]
44 },
45 "android": {
46 "description": "Enable Android dependencies",
47 "dependencies": [
48 {
49 "name": "oboe",
50 "platform": "android"
51 }
52 ]
44 } 53 }
45 }, 54 },
46 "overrides": [ 55 "overrides": [