Compare commits

...

13 Commits

Author SHA1 Message Date
Billy Laws
530fe24768 audio_core: No longer stall when sink queue is full
Now the audout and audren update rates are tied to the sink status stalling is no longer necessary.
2023-03-27 22:01:41 +01:00
Billy Laws
237934b736 Run clang-format 2023-03-27 22:01:41 +01:00
Billy Laws
ea5dd02db9 audio: Wait for samples on the emulated DSP side to avoid desyncs
Waiting on the host side is inaccurate and leads to desyncs in the event of the sink missing a deadline that require stalls to fix. By waiting for the sink to have space before even starting rendering such desyncs can be avoided.
2023-03-27 21:34:28 +01:00
Billy Laws
d8fc3f403b audio: Interpolate system manager sample count using host sink sample info
This avoids the need to stall if the host sink sporadically misses the deadline, in such a case the previous implementation would report them samples as being played on-time, causing the guest to send more samples and leading to a gradual buildup.
2023-03-26 22:48:57 +01:00
liamwhite
8da1a4ea22 Merge pull request #9994 from liamwhite/integer-constant
tests: mark integer literals as unsigned
2023-03-26 17:22:00 -04:00
Liam
1476ffd865 tests: mark integer literals as unsigned 2023-03-26 15:39:07 -04:00
Morph
568d523746 Merge pull request #9992 from liamwhite/climits
container_hash: use climits
2023-03-26 14:46:21 -04:00
Liam
7017f04ee8 container_hash: use climits 2023-03-26 10:24:33 -04:00
Morph
4b508655a4 Merge pull request #9989 from Morph1984/boost-1.79-exact
CMakeLists: Use boost 1.79.0 instead
2023-03-26 03:46:43 -04:00
Morph
49f6deecb8 video_core/macro: Make use of Common::HashValue 2023-03-25 23:52:26 -04:00
Morph
f09d192aac tests: Implement tests for verifying HashValue
Values were randomly generated and the verification was done against boost 1.79.
2023-03-25 23:52:26 -04:00
Morph
9971cd1d55 common: Port boost's hash_value implementation
Ports a small subset of boost's hash_value implementation (<= 1.80.0).
2023-03-25 23:52:25 -04:00
Morph
c4f5615c6b CMakeLists: Require a minimum of boost 1.79.0 2023-03-25 23:03:28 -04:00
16 changed files with 198 additions and 84 deletions

View File

@@ -210,7 +210,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
# =======================================================================
# Enforce the search mode of non-required packages for better and shorter failure messages
find_package(Boost 1.81.0 REQUIRED context)
find_package(Boost 1.79.0 REQUIRED context)
find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED)
find_package(inih 52 MODULE COMPONENTS INIReader)

View File

@@ -121,8 +121,7 @@ u64 DeviceSession::GetPlayedSampleCount() const {
}
std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
// Add 5ms of samples at a 48K sample rate.
played_sample_count += 48'000 * INCREMENT_TIME / 1s;
played_sample_count = stream->GetExpectedPlayedSampleCount();
if (type == Sink::StreamType::Out) {
system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
} else {

View File

@@ -189,6 +189,8 @@ void AudioRenderer::ThreadFunc() {
max_time = std::min(command_buffer.time_limit, max_time);
command_list_processor.SetProcessTimeMax(max_time);
streams[index]->WaitFreeSpace();
// Process the command list
{
MICROPROFILE_SCOPE(Audio_Renderer);

View File

@@ -10,6 +10,7 @@
#include "audio_core/renderer/adsp/command_buffer.h"
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "common/reader_writer_queue.h"
#include "common/thread.h"

View File

@@ -15,14 +15,9 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
MP_RGB(60, 19, 97));
namespace AudioCore::AudioRenderer {
constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
SystemManager::SystemManager(Core::System& core_)
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
thread_event{Core::Timing::CreateEvent(
"AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
return ThreadFunc2(time);
})} {}
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()} {}
SystemManager::~SystemManager() {
Stop();
@@ -33,8 +28,6 @@ bool SystemManager::InitializeUnsafe() {
if (adsp.Start()) {
active = true;
thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
thread_event);
}
}
@@ -45,7 +38,6 @@ void SystemManager::Stop() {
if (!active) {
return;
}
core.CoreTiming().UnscheduleEvent(thread_event, {});
active = false;
update.store(true);
update.notify_all();
@@ -111,16 +103,7 @@ void SystemManager::ThreadFunc() {
adsp.Signal();
adsp.Wait();
update.wait(false);
update.store(false);
}
}
std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
update.store(true);
update.notify_all();
return std::nullopt;
}
} // namespace AudioCore::AudioRenderer

View File

@@ -68,11 +68,6 @@ private:
*/
void ThreadFunc();
/**
* Signalling core timing thread to run ThreadFunc.
*/
std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
enum class StreamState {
Filling,
Steady,
@@ -95,8 +90,6 @@ private:
ADSP::ADSP& adsp;
/// AudioRenderer mailbox for communication
ADSP::AudioRenderer_Mailbox* mailbox{};
/// Core timing event to signal main thread
std::shared_ptr<Core::Timing::EventType> thread_event;
/// Atomic for main thread to wait on
std::atomic<bool> update{};
};

View File

@@ -101,8 +101,6 @@ public:
~CubebSinkStream() override {
LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
Unstall();
if (!ctx) {
return;
}
@@ -143,8 +141,6 @@ public:
* Stop the sink stream.
*/
void Stop() override {
Unstall();
if (!ctx || paused) {
return;
}

View File

@@ -88,7 +88,6 @@ public:
* Finalize the sink stream.
*/
void Finalize() override {
Unstall();
if (device == 0) {
return;
}
@@ -116,7 +115,6 @@ public:
* Stop the sink stream.
*/
void Stop() override {
Unstall();
if (device == 0 || paused) {
return;
}

View File

@@ -14,6 +14,8 @@
#include "common/fixed_point.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
namespace AudioCore::Sink {
@@ -149,10 +151,6 @@ void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t n
return;
}
if (queued_buffers > max_queue_size) {
Stall();
}
while (frames_written < num_frames) {
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
@@ -187,10 +185,6 @@ void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t n
}
std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
if (queued_buffers <= max_queue_size) {
Unstall();
}
}
void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
@@ -198,10 +192,15 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
const std::size_t frame_size = num_channels;
const std::size_t frame_size_bytes = frame_size * sizeof(s16);
size_t frames_written{0};
size_t actual_frames_written{0};
// If we're paused or going to shut down, we don't want to consume buffers as coretiming is
// paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) {
if (system.IsShuttingDown()) {
release_cv.notify_one();
}
static constexpr std::array<s16, 6> silence{};
for (size_t i = frames_written; i < num_frames; i++) {
std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
@@ -209,20 +208,6 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
return;
}
// Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
// queued up (30+) but not all at once, which causes constant stalling here, so just let the
// video play out without attempting to stall.
// Can hopefully remove this later with a more complete NVDEC implementation.
const auto nvdec_active{system.AudioCore().IsNVDECActive()};
// Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
// and never recovers to a normal state, so just skip attempting to sync things on single-core.
if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
Stall();
} else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
while (frames_written < num_frames) {
// If the playing buffer has been consumed or has no frames, we need a new one
if (playing_buffer.consumed || playing_buffer.frames == 0) {
@@ -237,6 +222,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
}
// Successfully dequeued a new buffer.
queued_buffers--;
{ std::unique_lock lk{release_mutex}; }
release_cv.notify_one();
}
// Get the minimum frames available between the currently playing buffer, and the
@@ -248,6 +237,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
frames_available * frame_size);
frames_written += frames_available;
actual_frames_written += frames_available;
playing_buffer.frames_played += frames_available;
// If that's all the frames in the current buffer, add its samples and mark it as
@@ -260,26 +250,29 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
{
std::scoped_lock lk{sample_count_lock};
last_sample_count_update_time =
Core::Timing::CyclesToUs(system.CoreTiming().GetClockTicks());
min_played_sample_count = max_played_sample_count;
max_played_sample_count += actual_frames_written;
}
}
void SinkStream::Stall() {
std::scoped_lock lk{stall_guard};
if (stalled_lock) {
return;
}
stalled_lock = system.StallApplication();
u64 SinkStream::GetExpectedPlayedSampleCount() {
std::scoped_lock lk{sample_count_lock};
auto cur_time{Core::Timing::CyclesToUs(system.CoreTiming().GetClockTicks())};
auto time_delta{cur_time - last_sample_count_update_time};
auto exp_played_sample_count{min_played_sample_count +
(TargetSampleRate * time_delta) / std::chrono::seconds{1}};
return std::min<u64>(exp_played_sample_count, max_played_sample_count);
}
void SinkStream::Unstall() {
std::scoped_lock lk{stall_guard};
if (!stalled_lock) {
return;
}
system.UnstallApplication();
stalled_lock.unlock();
void SinkStream::WaitFreeSpace() {
std::unique_lock lk{release_mutex};
release_cv.wait(
lk, [this]() { return queued_buffers < max_queue_size || system.IsShuttingDown(); });
}
} // namespace AudioCore::Sink

View File

@@ -5,6 +5,7 @@
#include <array>
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <span>
@@ -14,6 +15,7 @@
#include "common/common_types.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
#include "common/thread.h"
namespace Core {
class System;
@@ -53,9 +55,7 @@ struct SinkBuffer {
class SinkStream {
public:
explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
virtual ~SinkStream() {
Unstall();
}
virtual ~SinkStream() {}
/**
* Finalize the sink stream.
@@ -201,14 +201,16 @@ public:
void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
/**
* Stall core processes if the audio thread falls too far behind.
* Get the total number of samples expected to have been played by this stream.
*
* @return The number of samples.
*/
void Stall();
u64 GetExpectedPlayedSampleCount();
/**
* Unstall core processes.
* Waits for free space in the sample ring buffer
*/
void Unstall();
void WaitFreeSpace();
protected:
/// Core system
@@ -237,12 +239,21 @@ private:
std::atomic<u32> queued_buffers{};
/// The ring size for audio out buffers (usually 4, rarely 2 or 8)
u32 max_queue_size{};
/// Locks access to sample count tracking info
std::mutex sample_count_lock;
/// Minimum number of total samples that have been played since the last callback
u64 min_played_sample_count{};
/// Maximum number of total samples that can be played since the last callback
u64 max_played_sample_count{};
/// The time the two above tracking variables were last written to
std::chrono::microseconds last_sample_count_update_time{};
/// Set by the audio render/in/out system which uses this stream
f32 system_volume{1.0f};
/// Set via IAudioDevice service calls
f32 device_volume{1.0f};
std::mutex stall_guard;
std::unique_lock<std::mutex> stalled_lock;
/// Signalled when ring buffer entries are consumed
std::condition_variable release_cv;
std::mutex release_mutex;
};
using SinkStreamPtr = std::unique_ptr<SinkStream>;

View File

@@ -38,6 +38,7 @@ add_library(common STATIC
common_precompiled_headers.h
common_types.h
concepts.h
container_hash.h
demangle.cpp
demangle.h
div_ceil.h

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2005-2014 Daniel James
// SPDX-FileCopyrightText: 2016 Austin Appleby
// SPDX-License-Identifier: BSL-1.0
#include <array>
#include <climits>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <vector>
namespace Common {
namespace detail {
template <typename T>
requires std::is_unsigned_v<T>
inline std::size_t HashValue(T val) {
const unsigned int size_t_bits = std::numeric_limits<std::size_t>::digits;
const unsigned int length =
(std::numeric_limits<T>::digits - 1) / static_cast<unsigned int>(size_t_bits);
std::size_t seed = 0;
for (unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) {
seed ^= static_cast<size_t>(val >> i) + (seed << 6) + (seed >> 2);
}
seed ^= static_cast<size_t>(val) + (seed << 6) + (seed >> 2);
return seed;
}
template <size_t Bits>
struct HashCombineImpl {
template <typename T>
static inline T fn(T seed, T value) {
seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
template <>
struct HashCombineImpl<64> {
static inline std::uint64_t fn(std::uint64_t h, std::uint64_t k) {
const std::uint64_t m = (std::uint64_t(0xc6a4a793) << 32) + 0x5bd1e995;
const int r = 47;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
// Completely arbitrary number, to prevent 0's
// from hashing to 0.
h += 0xe6546b64;
return h;
}
};
} // namespace detail
template <typename T>
inline void HashCombine(std::size_t& seed, const T& v) {
seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v));
}
template <typename It>
inline std::size_t HashRange(It first, It last) {
std::size_t seed = 0;
for (; first != last; ++first) {
HashCombine<typename std::iterator_traits<It>::value_type>(seed, *first);
}
return seed;
}
template <typename T, size_t Size>
std::size_t HashValue(const std::array<T, Size>& v) {
return HashRange(v.cbegin(), v.cend());
}
template <typename T, typename Allocator>
std::size_t HashValue(const std::vector<T, Allocator>& v) {
return HashRange(v.cbegin(), v.cend());
}
} // namespace Common

View File

@@ -4,6 +4,7 @@
add_executable(tests
common/bit_field.cpp
common/cityhash.cpp
common/container_hash.cpp
common/fibers.cpp
common/host_memory.cpp
common/param_package.cpp

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch_test_macros.hpp>
#include "common/common_types.h"
#include "common/container_hash.h"
TEST_CASE("ContainerHash", "[common]") {
constexpr std::array<u8, 32> U8Values{
114, 10, 238, 189, 199, 242, 86, 96, 53, 193, 195, 247, 249, 56, 253, 61,
205, 3, 172, 4, 210, 197, 43, 72, 103, 8, 99, 89, 5, 97, 68, 196,
};
constexpr std::array<u16, 32> U16Values{
61586, 49151, 3313, 11641, 31695, 54795, 46764, 20965, 23287, 14039, 19265,
49093, 58932, 22518, 27139, 42825, 57417, 54237, 48057, 14586, 42813, 32994,
33970, 45501, 5619, 15895, 33227, 27509, 25391, 37275, 60218, 17599,
};
constexpr std::array<u32, 32> U32Values{
3838402410U, 2029146863U, 1730869921U, 985528872U, 186773874U, 2094639868U, 3324775932U,
1795512424U, 2571165571U, 3256934519U, 2358691590U, 2752682538U, 1484336451U, 378124520U,
3463015699U, 3395942161U, 1263211979U, 3473632889U, 3039822212U, 2068707357U, 2223837919U,
1823232191U, 1583884041U, 1264393380U, 4087566993U, 3188607101U, 3933680362U, 1464520765U,
1786838406U, 1311734848U, 2773642241U, 3993641692U,
};
constexpr std::array<u64, 32> U64Values{
5908025796157537817ULL, 10947547850358315100ULL, 844798943576724669ULL,
7999662937458523703ULL, 4006550374705895164ULL, 1832550525423503632ULL,
9323088254855830976ULL, 12028890075598379412ULL, 6021511300787826236ULL,
7864675007938747948ULL, 18099387408859708806ULL, 6438638299316820708ULL,
9029399285648501543ULL, 18195459433089960253ULL, 17214335092761966083ULL,
5549347964591337833ULL, 14899526073304962015ULL, 5058883181561464475ULL,
7436311795731206973ULL, 7535129567768649864ULL, 1287169596809258072ULL,
8237671246353565927ULL, 1715230541978016153ULL, 8443157615068813300ULL,
6098675262328527839ULL, 704652094100376853ULL, 1303411723202926503ULL,
7808312933946424854ULL, 6863726670433556594ULL, 9870361541383217495ULL,
9273671094091079488ULL, 17541434976160119010ULL,
};
REQUIRE(Common::HashValue(U8Values) == 5867183267093890552ULL);
REQUIRE(Common::HashValue(U16Values) == 9594135570564347135ULL);
REQUIRE(Common::HashValue(U32Values) == 13123757214696618460ULL);
REQUIRE(Common::HashValue(U64Values) == 7296500016546938380ULL);
}

View File

@@ -6,7 +6,7 @@
#include <optional>
#include <span>
#include <boost/container_hash/hash.hpp>
#include "common/container_hash.h"
#include <fstream>
#include "common/assert.h"
@@ -89,7 +89,7 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
if (!mid_method.has_value()) {
cache_info.lle_program = Compile(macro_code->second);
cache_info.hash = boost::hash_value(macro_code->second);
cache_info.hash = Common::HashValue(macro_code->second);
if (Settings::values.dump_macros) {
Dump(cache_info.hash, macro_code->second);
}
@@ -100,7 +100,7 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
code.resize(macro_cached.size() - rebased_method);
std::memcpy(code.data(), macro_cached.data() + rebased_method,
code.size() * sizeof(u32));
cache_info.hash = boost::hash_value(code);
cache_info.hash = Common::HashValue(code);
cache_info.lle_program = Compile(code);
if (Settings::values.dump_macros) {
Dump(cache_info.hash, code);

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "yuzu",
"builtin-baseline": "a7b6122f6b6504d16d96117336a0562693579933",
"builtin-baseline": "acc3bcf76b84ae5041c86ab55fe138ae7b8255c7",
"version": "1.0",
"dependencies": [
"boost-algorithm",