diff options
52 files changed, 992 insertions, 254 deletions
diff --git a/.gitmodules b/.gitmodules index 4f4e8690b..e73ca99e3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "opus"] path = externals/opus url = https://github.com/ogniK5377/opus.git +[submodule "soundtouch"] + path = externals/soundtouch + url = https://github.com/citra-emu/ext-soundtouch.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f32ecfba..500d099fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,6 +431,9 @@ enable_testing() add_subdirectory(externals) add_subdirectory(src) +# Set yuzu project as default StartUp Project in Visual Studio +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu) + # Installation instructions # ========================= 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) add_library(unicorn-headers INTERFACE) target_include_directories(unicorn-headers INTERFACE ./unicorn/include) +# SoundTouch +add_subdirectory(soundtouch) + # Xbyak if (ARCHITECTURE_x86_64) # 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 sink_stream.h stream.cpp stream.h + time_stretch.cpp + time_stretch.h $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> ) @@ -24,6 +26,7 @@ add_library(audio_core STATIC create_target_directory_groups(audio_core) target_link_libraries(audio_core PUBLIC common core) +target_link_libraries(audio_core PRIVATE SoundTouch) if(ENABLE_CUBEB) 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 @@ // Refer to the license.txt file included. #include <algorithm> +#include <atomic> #include <cstring> -#include <mutex> - #include "audio_core/cubeb_sink.h" #include "audio_core/stream.h" +#include "audio_core/time_stretch.h" #include "common/logging/log.h" +#include "common/ring_buffer.h" +#include "core/settings.h" namespace AudioCore { -class SinkStreamImpl final : public SinkStream { +class CubebSinkStream final : public SinkStream { public: - SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, - const std::string& name) - : ctx{ctx}, num_channels{num_channels_} { - - if (num_channels == 6) { - // 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2 - // channel for now - is_6_channel = true; - num_channels = 2; - } + CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, + const std::string& name) + : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, + num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; @@ -38,7 +34,7 @@ public: if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, ¶ms, std::max(512u, minimum_latency), - &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, + &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback, this) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); return; @@ -50,7 +46,7 @@ public: } } - ~SinkStreamImpl() { + ~CubebSinkStream() { if (!ctx) { return; } @@ -62,27 +58,32 @@ public: cubeb_stream_destroy(stream_backend); } - void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) override { - if (!ctx) { + void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { + if (source_num_channels > num_channels) { + // Downsample 6 channels to 2 + std::vector<s16> buf; + buf.reserve(samples.size() * num_channels / source_num_channels); + for (size_t i = 0; i < samples.size(); i += source_num_channels) { + for (size_t ch = 0; ch < num_channels; ch++) { + buf.push_back(samples[i + ch]); + } + } + queue.Push(buf); return; } - std::lock_guard lock{queue_mutex}; + queue.Push(samples); + } - queue.reserve(queue.size() + samples.size() * GetNumChannels()); + size_t SamplesInQueue(u32 num_channels) const override { + if (!ctx) + return 0; - if (is_6_channel) { - // Downsample 6 channels to 2 - const size_t sample_count_copy_size = samples.size() * 2; - queue.reserve(sample_count_copy_size); - for (size_t i = 0; i < samples.size(); i += num_channels) { - queue.push_back(samples[i]); - queue.push_back(samples[i + 1]); - } - } else { - // Copy as-is - std::copy(samples.begin(), samples.end(), std::back_inserter(queue)); - } + return queue.Size() / num_channels; + } + + void Flush() override { + should_flush = true; } u32 GetNumChannels() const { @@ -95,10 +96,11 @@ private: cubeb* ctx{}; cubeb_stream* stream_backend{}; u32 num_channels{}; - bool is_6_channel{}; - std::mutex queue_mutex; - std::vector<s16> queue; + Common::RingBuffer<s16, 0x10000> queue; + std::array<s16, 2> last_frame; + std::atomic<bool> should_flush{}; + TimeStretcher time_stretch; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); @@ -144,38 +146,52 @@ CubebSink::~CubebSink() { SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string& name) { sink_streams.push_back( - std::make_unique<SinkStreamImpl>(ctx, sample_rate, num_channels, output_device, name)); + std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name)); return *sink_streams.back(); } -long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, - void* output_buffer, long num_frames) { - SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data); +long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, + void* output_buffer, long num_frames) { + CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data); u8* buffer = reinterpret_cast<u8*>(output_buffer); if (!impl) { return {}; } - std::lock_guard lock{impl->queue_mutex}; + const size_t num_channels = impl->GetNumChannels(); + const size_t samples_to_write = num_channels * num_frames; + size_t samples_written; + + if (Settings::values.enable_audio_stretching) { + const std::vector<s16> in{impl->queue.Pop()}; + const size_t num_in{in.size() / num_channels}; + s16* const out{reinterpret_cast<s16*>(buffer)}; + const size_t out_frames = impl->time_stretch.Process(in.data(), num_in, out, num_frames); + samples_written = out_frames * num_channels; - const size_t frames_to_write{ - std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))}; + if (impl->should_flush) { + impl->time_stretch.Flush(); + impl->should_flush = false; + } + } else { + samples_written = impl->queue.Pop(buffer, samples_to_write); + } - memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels()); - impl->queue.erase(impl->queue.begin(), - impl->queue.begin() + frames_to_write * impl->GetNumChannels()); + if (samples_written >= num_channels) { + std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), + num_channels * sizeof(s16)); + } - if (frames_to_write < num_frames) { - // Fill the rest of the frames with silence - memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0, - (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels()); + // Fill the rest of the frames with last_frame + for (size_t i = samples_written; i < samples_to_write; i += num_channels) { + std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); } return num_frames; } -void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} +void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} std::vector<std::string> ListCubebSinkDevices() { 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: private: struct NullSinkStreamImpl final : SinkStream { void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} + + size_t SamplesInQueue(u32 /*num_channels*/) const override { + return 0; + } + + void Flush() override {} } null_sink_stream; }; 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: * @param samples Samples in interleaved stereo PCM16 format. */ virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0; + + virtual std::size_t SamplesInQueue(u32 num_channels) const = 0; + + virtual void Flush() = 0; }; using 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) { void Stream::PlayNextBuffer() { if (!IsPlaying()) { // Ensure we are in playing state before playing the next buffer + sink_stream.Flush(); return; } @@ -83,6 +84,7 @@ void Stream::PlayNextBuffer() { if (queued_buffers.empty()) { // No queued buffers - we are effectively paused + sink_stream.Flush(); return; } @@ -90,6 +92,7 @@ void Stream::PlayNextBuffer() { queued_buffers.pop(); VolumeAdjustSamples(active_buffer->Samples()); + sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); 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 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cmath> +#include <cstddef> +#include "audio_core/time_stretch.h" +#include "common/logging/log.h" + +namespace AudioCore { + +TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) + : m_sample_rate(sample_rate), m_channel_count(channel_count) { + m_sound_touch.setChannels(channel_count); + m_sound_touch.setSampleRate(sample_rate); + m_sound_touch.setPitch(1.0); + m_sound_touch.setTempo(1.0); +} + +void TimeStretcher::Clear() { + m_sound_touch.clear(); +} + +void TimeStretcher::Flush() { + m_sound_touch.flush(); +} + +size_t TimeStretcher::Process(const s16* in, size_t num_in, s16* out, size_t num_out) { + const double time_delta = static_cast<double>(num_out) / m_sample_rate; // seconds + + // We were given actual_samples number of samples, and num_samples were requested from us. + double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out); + + const double max_latency = 1.0; // seconds + const double max_backlog = m_sample_rate * max_latency; + const double backlog_fullness = m_sound_touch.numSamples() / max_backlog; + if (backlog_fullness > 5.0) { + // Too many samples in backlog: Don't push anymore on + num_in = 0; + } + + // We ideally want the backlog to be about 50% full. + // This gives some headroom both ways to prevent underflow and overflow. + // We tweak current_ratio to encourage this. + constexpr double tweak_time_scale = 0.05; // seconds + const double tweak_correction = (backlog_fullness - 0.5) * (time_delta / tweak_time_scale); + current_ratio *= std::pow(1.0 + 2.0 * tweak_correction, tweak_correction < 0 ? 3.0 : 1.0); + + // This low-pass filter smoothes out variance in the calculated stretch ratio. + // The time-scale determines how responsive this filter is. + constexpr double lpf_time_scale = 2.0; // seconds + const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale); + m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio); + + // Place a lower limit of 5% speed. When a game boots up, there will be + // many silence samples. These do not need to be timestretched. + m_stretch_ratio = std::max(m_stretch_ratio, 0.05); + m_sound_touch.setTempo(m_stretch_ratio); + + LOG_DEBUG(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio, + backlog_fullness); + + m_sound_touch.putSamples(in, num_in); + return m_sound_touch.receiveSamples(out, num_out); +} + +} // 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 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <SoundTouch.h> +#include "common/common_types.h" + +namespace AudioCore { + +class TimeStretcher { +public: + TimeStretcher(u32 sample_rate, u32 channel_count); + + /// @param in Input sample buffer + /// @param num_in Number of input frames in `in` + /// @param out Output sample buffer + /// @param num_out Desired number of output frames in `out` + /// @returns Actual number of frames written to `out` + size_t Process(const s16* in, size_t num_in, s16* out, size_t num_out); + + void Clear(); + + void Flush(); + +private: + u32 m_sample_rate; + u32 m_channel_count; + soundtouch::SoundTouch m_sound_touch; + double m_stretch_ratio = 1.0; +}; + +} // 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 param_package.cpp param_package.h quaternion.h + ring_buffer.h scm_rev.cpp scm_rev.h 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 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> +#include <atomic> +#include <cstddef> +#include <cstring> +#include <type_traits> +#include <vector> +#include "common/common_types.h" + +namespace Common { + +/// SPSC ring buffer +/// @tparam T Element type +/// @tparam capacity Number of slots in ring buffer +/// @tparam granularity Slot size in terms of number of elements +template <typename T, size_t capacity, size_t granularity = 1> +class RingBuffer { + /// A "slot" is made of `granularity` elements of `T`. + static constexpr size_t slot_size = granularity * sizeof(T); + // T must be safely memcpy-able and have a trivial default constructor. + static_assert(std::is_trivial_v<T>); + // Ensure capacity is sensible. + static_assert(capacity < std::numeric_limits<size_t>::max() / 2 / granularity); + static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two"); + // Ensure lock-free. + static_assert(std::atomic<size_t>::is_always_lock_free); + +public: + /// Pushes slots into the ring buffer + /// @param new_slots Pointer to the slots to push + /// @param slot_count Number of slots to push + /// @returns The number of slots actually pushed + size_t Push(const void* new_slots, size_t slot_count) { + const size_t write_index = m_write_index.load(); + const size_t slots_free = capacity + m_read_index.load() - write_index; + const size_t push_count = std::min(slot_count, slots_free); + + const size_t pos = write_index % capacity; + const size_t first_copy = std::min(capacity - pos, push_count); + const size_t second_copy = push_count - first_copy; + + const char* in = static_cast<const char*>(new_slots); + std::memcpy(m_data.data() + pos * granularity, in, first_copy * slot_size); + in += first_copy * slot_size; + std::memcpy(m_data.data(), in, second_copy * slot_size); + + m_write_index.store(write_index + push_count); + + return push_count; + } + + size_t Push(const std::vector<T>& input) { + return Push(input.data(), input.size()); + } + + /// Pops slots from the ring buffer + /// @param output Where to store the popped slots + /// @param max_slots Maximum number of slots to pop + /// @returns The number of slots actually popped + size_t Pop(void* output, size_t max_slots = ~size_t(0)) { + const size_t read_index = m_read_index.load(); + const size_t slots_filled = m_write_index.load() - read_index; + const size_t pop_count = std::min(slots_filled, max_slots); + + const size_t pos = read_index % capacity; + const size_t first_copy = std::min(capacity - pos, pop_count); + const size_t second_copy = pop_count - first_copy; + + char* out = static_cast<char*>(output); + std::memcpy(out, m_data.data() + pos * granularity, first_copy * slot_size); + out += first_copy * slot_size; + std::memcpy(out, m_data.data(), second_copy * slot_size); + + m_read_index.store(read_index + pop_count); + + return pop_count; + } + + std::vector<T> Pop(size_t max_slots = ~size_t(0)) { + std::vector<T> out(std::min(max_slots, capacity) * granularity); + const size_t count = Pop(out.data(), out.size() / granularity); + out.resize(count * granularity); + return out; + } + + /// @returns Number of slots used + size_t Size() const { + return m_write_index.load() - m_read_index.load(); + } + + /// @returns Maximum size of ring buffer + constexpr size_t Capacity() const { + return capacity; + } + +private: + // It is important to align the below variables for performance reasons: + // Having them on the same cache-line would result in false-sharing between them. + alignas(128) std::atomic<size_t> m_read_index{0}; + alignas(128) std::atomic<size_t> m_write_index{0}; + + std::array<T, granularity * capacity> m_data; +}; + +} // namespace Common diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index 4054d5db6..ad39c8271 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -21,6 +21,7 @@ enum { HandleTableFull = 105, InvalidMemoryState = 106, InvalidMemoryPermissions = 108, + InvalidThreadPriority = 112, InvalidProcessorId = 113, InvalidHandle = 114, InvalidCombination = 116, @@ -36,7 +37,7 @@ enum { // WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always // double check that the code matches before re-using the constant. -// TODO(bunnei): Replace these with correct errors for Switch OS +// TODO(bunnei): Replace -1 with correct errors for Switch OS constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull); constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1); constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge); @@ -53,15 +54,16 @@ constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::In constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel, ErrCodes::InvalidMemoryPermissions); constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle); +constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState); +constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel, + ErrCodes::InvalidThreadPriority); constexpr ResultCode ERR_INVALID_POINTER(-1); constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1); constexpr ResultCode ERR_NOT_AUTHORIZED(-1); /// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths. constexpr ResultCode ERR_INVALID_HANDLE_OS(-1); constexpr ResultCode ERR_NOT_FOUND(-1); -constexpr ResultCode ERR_OUT_OF_RANGE(-1); -constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(-1); constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout); /// Returned when Accept() is called on a port with no sessions to be accepted. constexpr ResultCode ERR_NO_PENDING_SESSIONS(-1); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 1c9373ed8..f500fd2e7 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -273,7 +273,11 @@ static void Break(u64 reason, u64 info1, u64 info2) { } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(VAddr address, s32 len) { +static void OutputDebugString(VAddr address, u64 len) { + if (len == 0) { + return; + } + std::string str(len, '\0'); Memory::ReadBlock(address, str.data(), str.size()); LOG_DEBUG(Debug_Emulated, "{}", str); @@ -378,7 +382,7 @@ static ResultCode GetThreadPriority(u32* priority, Handle handle) { /// Sets the priority for the specified thread static ResultCode SetThreadPriority(Handle handle, u32 priority) { if (priority > THREADPRIO_LOWEST) { - return ERR_OUT_OF_RANGE; + return ERR_INVALID_THREAD_PRIORITY; } auto& kernel = Core::System::GetInstance().Kernel(); @@ -523,7 +527,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V std::string name = fmt::format("unknown-{:X}", entry_point); if (priority > THREADPRIO_LOWEST) { - return ERR_OUT_OF_RANGE; + return ERR_INVALID_THREAD_PRIORITY; } SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit; @@ -544,8 +548,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V case THREADPROCESSORID_3: break; default: - ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); - break; + LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id); + return ERR_INVALID_PROCESSOR_ID; } auto& kernel = Core::System::GetInstance().Kernel(); diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 79c3fe31b..1eda5f879 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -222,9 +222,9 @@ void SvcWrap() { func((s64)PARAM(0)); } -template <void func(u64, s32 len)> +template <void func(u64, u64 len)> void SvcWrap() { - func(PARAM(0), (s32)(PARAM(1) & 0xFFFFFFFF)); + func(PARAM(0), PARAM(1)); } template <void func(u64, u64, u64)> diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 3d10d9af2..3f12a84dc 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -227,12 +227,12 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); - return ERR_OUT_OF_RANGE; + return ERR_INVALID_THREAD_PRIORITY; } if (processor_id > THREADPROCESSORID_MAX) { LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id); - return ERR_OUT_OF_RANGE_KERNEL; + return ERR_INVALID_PROCESSOR_ID; } // TODO(yuriks): Other checks, returning 0xD9001BEA diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 6b5e15633..128df7db5 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -15,6 +15,7 @@ #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/codecctl.h" #include "core/hle/service/audio/hwopus.h" +#include "core/hle/service/service.h" namespace Service::Audio { diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h index 95e5691f7..f5bd3bf5f 100644 --- a/src/core/hle/service/audio/audio.h +++ b/src/core/hle/service/audio/audio.h @@ -4,7 +4,9 @@ #pragma once -#include "core/hle/service/service.h" +namespace Service::SM { +class ServiceManager; +} namespace Service::Audio { diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 05100ca8f..80a002322 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -3,15 +3,20 @@ // Refer to the license.txt file included. #include <array> +#include <cstring> #include <vector> +#include "audio_core/audio_out.h" #include "audio_core/codec.h" +#include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audout_u.h" +#include "core/memory.h" namespace Service::Audio { @@ -25,6 +30,18 @@ enum { constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}}; constexpr int DefaultSampleRate{48000}; +struct AudoutParams { + s32_le sample_rate; + u16_le channel_count; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); + +enum class AudioState : u32 { + Started, + Stopped, +}; + class IAudioOut final : public ServiceFramework<IAudioOut> { public: IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index aa52d3855..dcaf64708 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -4,27 +4,18 @@ #pragma once -#include "audio_core/audio_out.h" #include "core/hle/service/service.h" +namespace AudioCore { +class AudioOut; +} + namespace Kernel { class HLERequestContext; } namespace Service::Audio { -struct AudoutParams { - s32_le sample_rate; - u16_le channel_count; - INSERT_PADDING_BYTES(2); -}; -static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); - -enum class AudioState : u32 { - Started, - Stopped, -}; - class IAudioOut; class AudOutU final : public ServiceFramework<AudOutU> { diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 3870bec65..e84c4fa2b 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> +#include <memory> +#include "audio_core/audio_renderer.h" #include "common/alignment.h" +#include "common/common_funcs.h" #include "common/logging/log.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 85a995a2f..c6bc3a90a 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -4,7 +4,6 @@ #pragma once -#include "audio_core/audio_renderer.h" #include "core/hle/service/service.h" namespace Kernel { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 341bfda42..668fef145 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -3,7 +3,12 @@ // Refer to the license.txt file included. #include <cstring> +#include <memory> +#include <vector> + #include <opus.h> + +#include "common/common_funcs.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index da1c46d59..ac0eaaa8f 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -2,6 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <cstring> +#include <vector> + #include <FontChineseSimplified.h> #include <FontChineseTraditional.h> #include <FontExtendedChineseSimplified.h> @@ -9,14 +13,19 @@ #include <FontNintendoExtended.h> #include <FontStandard.h> +#include "common/assert.h" #include "common/common_paths.h" +#include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" +#include "common/swap.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/pl_u.h" @@ -35,49 +44,41 @@ struct FontRegion { u32 size; }; -static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ +constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")}; + std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), +}; -static constexpr std::array<const char*, 7> SHARED_FONTS_TTF{"FontStandard.ttf", - "FontChineseSimplified.ttf", - "FontExtendedChineseSimplified.ttf", - "FontChineseTraditional.ttf", - "FontKorean.ttf", - "FontNintendoExtended.ttf", - "FontNintendoExtended2.ttf"}; +constexpr std::array<const char*, 7> SHARED_FONTS_TTF{ + "FontStandard.ttf", + "FontChineseSimplified.ttf", + "FontExtendedChineseSimplified.ttf", + "FontChineseTraditional.ttf", + "FontKorean.ttf", + "FontNintendoExtended.ttf", + "FontNintendoExtended2.ttf", +}; // The below data is specific to shared font data dumped from Switch on f/w 2.2 // Virtual address and offsets/sizes likely will vary by dump -static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; -static constexpr u32 EXPECTED_RESULT{ - 0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be -static constexpr u32 EXPECTED_MAGIC{ - 0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be -static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; -static constexpr FontRegion EMPTY_REGION{0, 0}; -std::vector<FontRegion> - SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives - -const FontRegion& GetSharedFontRegion(size_t index) { - if (index >= SHARED_FONT_REGIONS.size() || SHARED_FONT_REGIONS.empty()) { - // No font fallback - return EMPTY_REGION; - } - return SHARED_FONT_REGIONS.at(index); -} +constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be +constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be +constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; +constexpr FontRegion EMPTY_REGION{0, 0}; enum class LoadState : u32 { Loading = 0, Done = 1, }; -void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, size_t& offset) { +static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, + size_t& offset) { ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!"); 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 offset += input.size() + (sizeof(u32) * 2); } +// Helper function to make BuildSharedFontsRawRegions a bit nicer static u32 GetU32Swapped(const u8* data) { u32 value; std::memcpy(&value, data, sizeof(value)); - return Common::swap32(value); // Helper function to make BuildSharedFontsRawRegions a bit nicer + return Common::swap32(value); } -void BuildSharedFontsRawRegions(const std::vector<u8>& input) { - unsigned cur_offset = 0; // As we can derive the xor key we can just populate the offsets based - // on the shared memory dump - for (size_t i = 0; i < SHARED_FONTS.size(); i++) { - // Out of shared fonts/Invalid font - if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) - break; - const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ - EXPECTED_MAGIC; // Derive key withing inverse xor - const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; - SHARED_FONT_REGIONS.push_back(FontRegion{cur_offset + 8, SIZE}); - cur_offset += SIZE + 8; +struct PL_U::Impl { + const FontRegion& GetSharedFontRegion(size_t index) const { + if (index >= shared_font_regions.size() || shared_font_regions.empty()) { + // No font fallback + return EMPTY_REGION; + } + return shared_font_regions.at(index); } -} -PL_U::PL_U() : ServiceFramework("pl:u") { + void BuildSharedFontsRawRegions(const std::vector<u8>& input) { + // As we can derive the xor key we can just populate the offsets + // based on the shared memory dump + unsigned cur_offset = 0; + + for (size_t i = 0; i < SHARED_FONTS.size(); i++) { + // Out of shared fonts/invalid font + if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) { + break; + } + + // Derive key withing inverse xor + const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC; + const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; + shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE}); + cur_offset += SIZE + 8; + } + } + + /// Handle to shared memory region designated for a shared font + Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; + + /// Backing memory for the shared font data + std::shared_ptr<std::vector<u8>> shared_font; + + // Automatically populated based on shared_fonts dump or system archives. + std::vector<FontRegion> shared_font_regions; +}; + +PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { static const FunctionInfo functions[] = { {0, &PL_U::RequestLoad, "RequestLoad"}, {1, &PL_U::GetLoadState, "GetLoadState"}, @@ -141,7 +166,7 @@ PL_U::PL_U() : ServiceFramework("pl:u") { // Rebuild shared fonts from data ncas if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), FileSys::ContentRecordType::Data)) { - shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); + impl->shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); for (auto font : SHARED_FONTS) { const auto nca = nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data); @@ -177,12 +202,12 @@ PL_U::PL_U() : ServiceFramework("pl:u") { static_cast<u32>(offset + 8), static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 8)}; // Font offset and size do not account for the header - DecryptSharedFont(font_data_u32, *shared_font, offset); - SHARED_FONT_REGIONS.push_back(region); + DecryptSharedFont(font_data_u32, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); } } else { - shared_font = std::make_shared<std::vector<u8>>( + impl->shared_font = std::make_shared<std::vector<u8>>( SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); @@ -206,8 +231,8 @@ PL_U::PL_U() : ServiceFramework("pl:u") { static_cast<u32>(offset + 8), static_cast<u32>(ttf_bytes.size())}; // Font offset and size do not account // for the header - EncryptSharedFont(ttf_bytes, *shared_font, offset); - SHARED_FONT_REGIONS.push_back(region); + EncryptSharedFont(ttf_bytes, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); } else { LOG_WARNING(Service_NS, "Unable to load font: {}", font_ttf); } @@ -222,8 +247,8 @@ PL_U::PL_U() : ServiceFramework("pl:u") { if (file.IsOpen()) { // Read shared font data ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE); - file.ReadBytes(shared_font->data(), shared_font->size()); - BuildSharedFontsRawRegions(*shared_font); + file.ReadBytes(impl->shared_font->data(), impl->shared_font->size()); + impl->BuildSharedFontsRawRegions(*impl->shared_font); } else { LOG_WARNING(Service_NS, "Shared Font file missing. Loading open source replacement from memory"); @@ -240,8 +265,8 @@ PL_U::PL_U() : ServiceFramework("pl:u") { for (const std::vector<u8>& font_ttf : open_source_shared_fonts_ttf) { const FontRegion region{static_cast<u32>(offset + 8), static_cast<u32>(font_ttf.size())}; - EncryptSharedFont(font_ttf, *shared_font, offset); - SHARED_FONT_REGIONS.push_back(region); + EncryptSharedFont(font_ttf, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); } } } @@ -275,7 +300,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(GetSharedFontRegion(font_id).size); + rb.Push<u32>(impl->GetSharedFontRegion(font_id).size); } void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { @@ -285,17 +310,18 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(GetSharedFontRegion(font_id).offset); + rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset); } void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { // Map backing memory for the font data - Core::CurrentProcess()->vm_manager.MapMemoryBlock( - SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared); + Core::CurrentProcess()->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, + SHARED_FONT_MEM_SIZE, + Kernel::MemoryState::Shared); // Create shared font memory object auto& kernel = Core::System::GetInstance().Kernel(); - shared_font_mem = Kernel::SharedMemory::Create( + impl->shared_font_mem = Kernel::SharedMemory::Create( kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, "PL_U:shared_font_mem"); @@ -303,7 +329,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(shared_font_mem); + rb.PushCopyObjects(impl->shared_font_mem); } void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { @@ -316,9 +342,9 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { std::vector<u32> font_sizes; // TODO(ogniK): Have actual priority order - for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) { + for (size_t i = 0; i < impl->shared_font_regions.size(); i++) { font_codes.push_back(static_cast<u32>(i)); - auto region = GetSharedFontRegion(i); + auto region = impl->GetSharedFontRegion(i); font_offsets.push_back(region.offset); font_sizes.push_back(region.size); } 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 @@ #pragma once #include <memory> -#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/service.h" namespace Service::NS { @@ -23,11 +22,8 @@ private: void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); - /// Handle to shared memory region designated for a shared font - Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; - - /// Backing memory for the shared font data - std::shared_ptr<std::vector<u8>> shared_font; + struct Impl; + std::unique_ptr<Impl> impl; }; } // namespace Service::NS diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 3c43b8d8c..6a9eccfb5 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -1,36 +1,47 @@ -#include <cinttypes> +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/service/prepo/prepo.h" +#include "core/hle/service/service.h" namespace Service::PlayReport { -PlayReport::PlayReport(const char* name) : ServiceFramework(name) { - static const FunctionInfo functions[] = { - {10100, nullptr, "SaveReport"}, - {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, - {10200, nullptr, "RequestImmediateTransmission"}, - {10300, nullptr, "GetTransmissionStatus"}, - {20100, nullptr, "SaveSystemReport"}, - {20200, nullptr, "SetOperationMode"}, - {20101, nullptr, "SaveSystemReportWithUser"}, - {30100, nullptr, "ClearStorage"}, - {40100, nullptr, "IsUserAgreementCheckEnabled"}, - {40101, nullptr, "SetUserAgreementCheckEnabled"}, - {90100, nullptr, "GetStorageUsage"}, - {90200, nullptr, "GetStatistics"}, - {90201, nullptr, "GetThroughputHistory"}, - {90300, nullptr, "GetLastUploadError"}, - }; - RegisterHandlers(functions); -}; -void PlayReport::SaveReportWithUser(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Do we want to add play report? - LOG_WARNING(Service_PREPO, "(STUBBED) called"); +class PlayReport final : public ServiceFramework<PlayReport> { +public: + explicit PlayReport(const char* name) : ServiceFramework{name} { + // clang-format off + static const FunctionInfo functions[] = { + {10100, nullptr, "SaveReport"}, + {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10200, nullptr, "RequestImmediateTransmission"}, + {10300, nullptr, "GetTransmissionStatus"}, + {20100, nullptr, "SaveSystemReport"}, + {20200, nullptr, "SetOperationMode"}, + {20101, nullptr, "SaveSystemReportWithUser"}, + {30100, nullptr, "ClearStorage"}, + {40100, nullptr, "IsUserAgreementCheckEnabled"}, + {40101, nullptr, "SetUserAgreementCheckEnabled"}, + {90100, nullptr, "GetStorageUsage"}, + {90200, nullptr, "GetStatistics"}, + {90201, nullptr, "GetThroughputHistory"}, + {90300, nullptr, "GetLastUploadError"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void SaveReportWithUser(Kernel::HLERequestContext& ctx) { + // TODO(ogniK): Do we want to add play report? + LOG_WARNING(Service_PREPO, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index f5a6aba6d..0e7b01331 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -4,22 +4,12 @@ #pragma once -#include <memory> -#include <string> -#include "core/hle/kernel/event.h" -#include "core/hle/service/service.h" +namespace Service::SM { +class ServiceManager; +} namespace Service::PlayReport { -class PlayReport final : public ServiceFramework<PlayReport> { -public: - explicit PlayReport(const char* name); - ~PlayReport() = default; - -private: - void SaveReportWithUser(Kernel::HLERequestContext& ctx); -}; - void InstallInterfaces(SM::ServiceManager& service_manager); } // namespace Service::PlayReport 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 { // Audio std::string sink_id; + bool enable_audio_stretching; std::string audio_device_id; float volume; 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() { Telemetry::AppendOSInfo(field_collection); // Log user configuration information + AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id); + AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", + Settings::values.enable_audio_stretching); AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore", 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 @@ add_executable(tests common/param_package.cpp + common/ring_buffer.cpp core/arm/arm_test_common.cpp core/arm/arm_test_common.h 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 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstddef> +#include <numeric> +#include <thread> +#include <vector> +#include <catch2/catch.hpp> +#include "common/ring_buffer.h" + +namespace Common { + +TEST_CASE("RingBuffer: Basic Tests", "[common]") { + RingBuffer<char, 4, 1> buf; + + // Pushing values into a ring buffer with space should succeed. + for (size_t i = 0; i < 4; i++) { + const char elem = static_cast<char>(i); + const size_t count = buf.Push(&elem, 1); + REQUIRE(count == 1); + } + + REQUIRE(buf.Size() == 4); + + // Pushing values into a full ring buffer should fail. + { + const char elem = static_cast<char>(42); + const size_t count = buf.Push(&elem, 1); + REQUIRE(count == 0); + } + + REQUIRE(buf.Size() == 4); + + // Popping multiple values from a ring buffer with values should succeed. + { + const std::vector<char> popped = buf.Pop(2); + REQUIRE(popped.size() == 2); + REQUIRE(popped[0] == 0); + REQUIRE(popped[1] == 1); + } + + REQUIRE(buf.Size() == 2); + + // Popping a single value from a ring buffer with values should succeed. + { + const std::vector<char> popped = buf.Pop(1); + REQUIRE(popped.size() == 1); + REQUIRE(popped[0] == 2); + } + + REQUIRE(buf.Size() == 1); + + // Pushing more values than space available should partially suceed. + { + std::vector<char> to_push(6); + std::iota(to_push.begin(), to_push.end(), 88); + const size_t count = buf.Push(to_push); + REQUIRE(count == 3); + } + + REQUIRE(buf.Size() == 4); + + // Doing an unlimited pop should pop all values. + { + const std::vector<char> popped = buf.Pop(); + REQUIRE(popped.size() == 4); + REQUIRE(popped[0] == 3); + REQUIRE(popped[1] == 88); + REQUIRE(popped[2] == 89); + REQUIRE(popped[3] == 90); + } + + REQUIRE(buf.Size() == 0); +} + +TEST_CASE("RingBuffer: Threaded Test", "[common]") { + RingBuffer<char, 4, 2> buf; + const char seed = 42; + const size_t count = 1000000; + size_t full = 0; + size_t empty = 0; + + const auto next_value = [](std::array<char, 2>& value) { + value[0] += 1; + value[1] += 2; + }; + + std::thread producer{[&] { + std::array<char, 2> value = {seed, seed}; + size_t i = 0; + while (i < count) { + if (const size_t c = buf.Push(&value[0], 1); c > 0) { + REQUIRE(c == 1); + i++; + next_value(value); + } else { + full++; + std::this_thread::yield(); + } + } + }}; + + std::thread consumer{[&] { + std::array<char, 2> value = {seed, seed}; + size_t i = 0; + while (i < count) { + if (const std::vector<char> v = buf.Pop(1); v.size() > 0) { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == value[0]); + REQUIRE(v[1] == value[1]); + i++; + next_value(value); + } else { + empty++; + std::this_thread::yield(); + } + } + }}; + + producer.join(); + consumer.join(); + + REQUIRE(buf.Size() == 0); + printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty); +} + +} // namespace Common diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 9176a8dbc..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 { BorderColor = 22, }; +enum class TextureProcessMode : u64 { + None = 0, + LZ = 1, // Unknown, appears to be the same as none. + LB = 2, // Load Bias. + LL = 3, // Load LOD (LevelOfDetail) + LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB + LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL +}; + enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 }; enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 }; @@ -424,6 +433,45 @@ union Instruction { } bfe; union { + BitField<48, 3, u64> pred48; + + union { + BitField<20, 20, u64> entry_a; + BitField<39, 5, u64> entry_b; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } imm; + + union { + BitField<20, 14, u64> cb_index; + BitField<34, 5, u64> cb_offset; + BitField<56, 1, u64> neg; + BitField<57, 1, u64> uses_cc; + } hi; + + union { + BitField<20, 14, u64> cb_index; + BitField<34, 5, u64> cb_offset; + BitField<39, 5, u64> entry_a; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } rz; + + union { + BitField<39, 5, u64> entry_a; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } r1; + + union { + BitField<28, 8, u64> entry_a; + BitField<37, 1, u64> neg; + BitField<38, 1, u64> uses_cc; + } r2; + + } lea; + + union { BitField<0, 5, FlowCondition> cond; } flow; @@ -478,6 +526,18 @@ union Instruction { } psetp; union { + BitField<12, 3, u64> pred12; + BitField<15, 1, u64> neg_pred12; + BitField<24, 2, PredOperation> cond; + BitField<29, 3, u64> pred29; + BitField<32, 1, u64> neg_pred29; + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred39; + BitField<44, 1, u64> bf; + BitField<45, 2, PredOperation> op; + } pset; + + union { BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; BitField<43, 1, u64> neg_a; @@ -522,6 +582,7 @@ union Instruction { BitField<28, 1, u64> array; BitField<29, 2, TextureType> texture_type; BitField<31, 4, u64> component_mask; + BitField<55, 3, TextureProcessMode> process_mode; bool IsComponentEnabled(size_t component) const { return ((1ull << component) & component_mask) != 0; @@ -726,6 +787,11 @@ public: ISCADD_C, // Scale and Add ISCADD_R, ISCADD_IMM, + LEA_R1, + LEA_R2, + LEA_RZ, + LEA_IMM, + LEA_HI, POPC_C, POPC_R, POPC_IMM, @@ -784,6 +850,7 @@ public: ISET_C, ISET_IMM, PSETP, + PSET, XMAD_IMM, XMAD_CR, XMAD_RC, @@ -807,6 +874,7 @@ public: IntegerSet, IntegerSetPredicate, PredicateSetPredicate, + PredicateSetRegister, Conversion, Xmad, Unknown, @@ -958,6 +1026,11 @@ private: INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"), INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"), INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"), + INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"), + INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"), + INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"), + INST("010010111101----", Id::LEA_RZ, Type::ArithmeticInteger, "LEA_RZ"), + INST("00011000--------", Id::LEA_HI, Type::ArithmeticInteger, "LEA_HI"), INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), @@ -1012,6 +1085,7 @@ private: INST("010110110101----", Id::ISET_R, Type::IntegerSet, "ISET_R"), INST("010010110101----", Id::ISET_C, Type::IntegerSet, "ISET_C"), INST("0011011-0101----", Id::ISET_IMM, Type::IntegerSet, "ISET_IMM"), + INST("0101000010001---", Id::PSET, Type::PredicateSetRegister, "PSET"), INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"), INST("0011011-00------", Id::XMAD_IMM, Type::Xmad, "XMAD_IMM"), INST("0100111---------", Id::XMAD_CR, Type::Xmad, "XMAD_CR"), diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index be17a2b9c..0df3725c2 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -19,6 +19,7 @@ void RendererBase::RefreshBaseSettings() { UpdateCurrentFramebufferLayout(); renderer_settings.use_framelimiter = Settings::values.use_frame_limit; + renderer_settings.set_background_color = true; } void RendererBase::UpdateCurrentFramebufferLayout() { diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 2a357f9d0..2cd0738ff 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -19,6 +19,7 @@ namespace VideoCore { struct RendererSettings { std::atomic_bool use_framelimiter{false}; + std::atomic_bool set_background_color{false}; }; class RendererBase : NonCopyable { diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 29d61eccd..32001e44b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -53,8 +53,6 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); params.unaligned_height = config.tic.Height(); - params.cache_width = Common::AlignUp(params.width, 8); - params.cache_height = Common::AlignUp(params.height, 8); params.target = SurfaceTargetFromTextureType(config.tic.texture_type); switch (params.target) { @@ -89,8 +87,6 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.width = config.width; params.height = config.height; params.unaligned_height = config.height; - params.cache_width = Common::AlignUp(params.width, 8); - params.cache_height = Common::AlignUp(params.height, 8); params.target = SurfaceTarget::Texture2D; params.depth = 1; params.size_in_bytes = params.SizeInBytes(); @@ -110,8 +106,6 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.width = zeta_width; params.height = zeta_height; params.unaligned_height = zeta_height; - params.cache_width = Common::AlignUp(params.width, 8); - params.cache_height = Common::AlignUp(params.height, 8); params.target = SurfaceTarget::Texture2D; params.depth = 1; params.size_in_bytes = params.SizeInBytes(); @@ -122,7 +116,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI - {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U + {GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, false}, // A2B10G10R10U {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U @@ -477,30 +471,27 @@ CachedSurface::CachedSurface(const SurfaceParams& params) // Only pre-create the texture for non-compressed textures. switch (params.target) { case SurfaceParams::SurfaceTarget::Texture1D: - glTexImage1D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format, - rect.GetWidth(), 0, format_tuple.format, format_tuple.type, nullptr); + glTexStorage1D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, + rect.GetWidth()); break; case SurfaceParams::SurfaceTarget::Texture2D: - glTexImage2D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format, - rect.GetWidth(), rect.GetHeight(), 0, format_tuple.format, - format_tuple.type, nullptr); + glTexStorage2D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, + rect.GetWidth(), rect.GetHeight()); break; case SurfaceParams::SurfaceTarget::Texture3D: case SurfaceParams::SurfaceTarget::Texture2DArray: - glTexImage3D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format, - rect.GetWidth(), rect.GetHeight(), params.depth, 0, format_tuple.format, - format_tuple.type, nullptr); + glTexStorage3D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, + rect.GetWidth(), rect.GetHeight(), params.depth); break; default: LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", static_cast<u32>(params.target)); UNREACHABLE(); - glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, rect.GetWidth(), - rect.GetHeight(), 0, format_tuple.format, format_tuple.type, nullptr); + glTexStorage2D(GL_TEXTURE_2D, 1, format_tuple.internal_format, rect.GetWidth(), + rect.GetHeight()); } } - glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -817,16 +808,20 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, // Get a new surface with the new parameters, and blit the previous surface to it Surface new_surface{GetUncachedSurface(new_params)}; - // If format is unchanged, we can do a faster blit without reinterpreting pixel data - if (params.pixel_format == new_params.pixel_format) { + if (params.pixel_format == new_params.pixel_format || + !Settings::values.use_accurate_framebuffers) { + // If the format is the same, just do a framebuffer blit. This is significantly faster than + // using PBOs. The is also likely less accurate, as textures will be converted rather than + // reinterpreted. + BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle, params.GetRect(), params.type, read_framebuffer.handle, draw_framebuffer.handle); - return new_surface; - } + } else { + // When use_accurate_framebuffers setting is enabled, perform a more accurate surface copy, + // where pixels are reinterpreted as a new format (without conversion). This code path uses + // OpenGL PBOs and is quite slow. - // When using accurate framebuffers, always copy old data to new surface, regardless of format - if (Settings::values.use_accurate_framebuffers) { auto source_format = GetFormatTuple(params.pixel_format, params.component_type); auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index e660998d0..57ea8593b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -680,8 +680,8 @@ struct SurfaceParams { /// Checks if surfaces are compatible for caching bool IsCompatibleSurface(const SurfaceParams& other) const { - return std::tie(pixel_format, type, cache_width, cache_height) == - std::tie(other.pixel_format, other.type, other.cache_width, other.cache_height); + return std::tie(pixel_format, type, width, height) == + std::tie(other.pixel_format, other.type, other.width, other.height); } VAddr addr; @@ -696,10 +696,6 @@ struct SurfaceParams { u32 unaligned_height; size_t size_in_bytes; SurfaceTarget target; - - // Parameters used for caching only - u32 cache_width; - u32 cache_height; }; }; // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index e350113f1..2d56370c7 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -1505,6 +1505,73 @@ private: 1, 1); break; } + case OpCode::Id::LEA_R2: + case OpCode::Id::LEA_R1: + case OpCode::Id::LEA_IMM: + case OpCode::Id::LEA_RZ: + case OpCode::Id::LEA_HI: { + std::string op_a; + std::string op_b; + std::string op_c; + + switch (opcode->GetId()) { + case OpCode::Id::LEA_R2: { + op_a = regs.GetRegisterAsInteger(instr.gpr20); + op_b = regs.GetRegisterAsInteger(instr.gpr39); + op_c = std::to_string(instr.lea.r2.entry_a); + break; + } + + case OpCode::Id::LEA_R1: { + const bool neg = instr.lea.r1.neg != 0; + op_a = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_a = "-(" + op_a + ')'; + op_b = regs.GetRegisterAsInteger(instr.gpr20); + op_c = std::to_string(instr.lea.r1.entry_a); + break; + } + + case OpCode::Id::LEA_IMM: { + const bool neg = instr.lea.imm.neg != 0; + op_b = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_b = "-(" + op_b + ')'; + op_a = std::to_string(instr.lea.imm.entry_a); + op_c = std::to_string(instr.lea.imm.entry_b); + break; + } + + case OpCode::Id::LEA_RZ: { + const bool neg = instr.lea.rz.neg != 0; + op_b = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_b = "-(" + op_b + ')'; + op_a = regs.GetUniform(instr.lea.rz.cb_index, instr.lea.rz.cb_offset, + GLSLRegister::Type::Integer); + op_c = std::to_string(instr.lea.rz.entry_a); + + break; + } + + case OpCode::Id::LEA_HI: + default: { + op_b = regs.GetRegisterAsInteger(instr.gpr8); + op_a = std::to_string(instr.lea.imm.entry_a); + op_c = std::to_string(instr.lea.imm.entry_b); + LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + if (instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex)) { + LOG_ERROR(HW_GPU, "Unhandled LEA Predicate"); + UNREACHABLE(); + } + const std::string value = '(' + op_a + " + (" + op_b + "*(1 << " + op_c + ")))"; + regs.SetRegisterToInteger(instr.gpr0, true, 0, value, 1, 1); + + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", opcode->GetName()); @@ -1786,15 +1853,47 @@ private: coord = "vec2 coords = vec2(" + x + ", " + y + ");"; texture_type = Tegra::Shader::TextureType::Texture2D; } + // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias + // or lod. + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); const std::string sampler = GetSampler(instr.sampler, texture_type, false); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. + shader.AddLine("{"); ++shader.scope; shader.AddLine(coord); - const std::string texture = "texture(" + sampler + ", coords)"; + std::string texture; + switch (instr.tex.process_mode) { + case Tegra::Shader::TextureProcessMode::None: { + texture = "texture(" + sampler + ", coords)"; + break; + } + case Tegra::Shader::TextureProcessMode::LZ: { + texture = "textureLod(" + sampler + ", coords, 0.0)"; + break; + } + case Tegra::Shader::TextureProcessMode::LB: + case Tegra::Shader::TextureProcessMode::LBA: { + // TODO: Figure if A suffix changes the equation at all. + texture = "texture(" + sampler + ", coords, " + op_c + ')'; + break; + } + case Tegra::Shader::TextureProcessMode::LL: + case Tegra::Shader::TextureProcessMode::LLA: { + // TODO: Figure if A suffix changes the equation at all. + texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; + break; + } + default: { + texture = "texture(" + sampler + ", coords)"; + LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", + static_cast<u32>(instr.tex.process_mode.Value())); + UNREACHABLE(); + } + } size_t dest_elem{}; for (size_t elem = 0; elem < 4; ++elem) { if (!instr.tex.IsComponentEnabled(elem)) { @@ -2087,6 +2186,30 @@ private: } break; } + case OpCode::Type::PredicateSetRegister: { + const std::string op_a = + GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0); + const std::string op_b = + GetPredicateCondition(instr.pset.pred29, instr.pset.neg_pred29 != 0); + + const std::string second_pred = + GetPredicateCondition(instr.pset.pred39, instr.pset.neg_pred39 != 0); + + const std::string combiner = GetPredicateCombiner(instr.pset.op); + + const std::string predicate = + '(' + op_a + ") " + GetPredicateCombiner(instr.pset.cond) + " (" + op_b + ')'; + const std::string result = '(' + predicate + ") " + combiner + " (" + second_pred + ')'; + if (instr.pset.bf == 0) { + const std::string value = '(' + result + ") ? 0xFFFFFFFF : 0"; + regs.SetRegisterToInteger(instr.gpr0, false, 0, value, 1, 1); + } else { + const std::string value = '(' + result + ") ? 1.0 : 0.0"; + regs.SetRegisterToFloat(instr.gpr0, 0, value, 1, 1); + } + + break; + } case OpCode::Type::PredicateSetPredicate: { const std::string op_a = GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index ccff3e342..96d916b07 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -369,6 +369,12 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, * Draws the emulated screens to the emulator window. */ void RendererOpenGL::DrawScreen() { + if (renderer_settings.set_background_color) { + // Update background color before drawing + glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, + 0.0f); + } + const auto& layout = render_window.GetFramebufferLayout(); const auto& screen = layout.screen; 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() { qt_config->beginGroup("Audio"); Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.enable_audio_stretching = + qt_config->value("enable_audio_stretching", true).toBool(); Settings::values.audio_device_id = qt_config->value("output_device", "auto").toString().toStdString(); Settings::values.volume = qt_config->value("volume", 1).toFloat(); @@ -230,6 +232,7 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); qt_config->setValue("volume", Settings::values.volume); 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() { } ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); + // The device list cannot be pre-populated (nor listed) until the output sink is known. updateAudioDevices(new_sink_index); @@ -67,6 +69,7 @@ void ConfigureAudio::applyConfiguration() { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); + Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); Settings::values.audio_device_id = ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) .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 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="toggle_audio_stretching"> + <property name="toolTip"> + <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> + </property> + <property name="text"> + <string>Enable audio stretching</string> + </property> + </widget> + </item> <item> <layout class="QHBoxLayout"> <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 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/core.h" +#include <array> +#include <utility> + +#include "common/common_types.h" #include "core/settings.h" #include "ui_configure_gamelist.h" -#include "ui_settings.h" #include "yuzu/configuration/configure_gamelist.h" +#include "yuzu/ui_settings.h" + +namespace { +constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{ + std::make_pair(0, QT_TR_NOOP("None")), + std::make_pair(32, QT_TR_NOOP("Small (32x32)")), + std::make_pair(64, QT_TR_NOOP("Standard (64x64)")), + std::make_pair(128, QT_TR_NOOP("Large (128x128)")), + std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")), +}}; + +constexpr std::array<const char*, 4> row_text_names{{ + QT_TR_NOOP("Filename"), + QT_TR_NOOP("Filetype"), + QT_TR_NOOP("Title ID"), + QT_TR_NOOP("Title Name"), +}}; +} // Anonymous namespace ConfigureGameList::ConfigureGameList(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGameList) { ui->setupUi(this); - static const std::vector<std::pair<u32, std::string>> default_icon_sizes{ - std::make_pair(0, "None"), std::make_pair(32, "Small"), - std::make_pair(64, "Standard"), std::make_pair(128, "Large"), - std::make_pair(256, "Full Size"), - }; - - for (const auto& size : default_icon_sizes) { - ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" + - std::to_string(size.first) + "x" + - std::to_string(size.first) + ")"), - size.first); - } - - static const std::vector<std::string> row_text_names{ - "Filename", - "Filetype", - "Title ID", - "Title Name", - }; - - for (size_t i = 0; i < row_text_names.size(); ++i) { - ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]), - QVariant::fromValue(i)); - ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]), - QVariant::fromValue(i)); - } + InitializeIconSizeComboBox(); + InitializeRowComboBoxes(); this->setConfiguration(); } -ConfigureGameList::~ConfigureGameList() {} +ConfigureGameList::~ConfigureGameList() = default; + +void ConfigureGameList::applyConfiguration() { + UISettings::values.show_unknown = ui->show_unknown->isChecked(); + UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); + UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + Settings::Apply(); +} void ConfigureGameList::setConfiguration() { ui->show_unknown->setChecked(UISettings::values.show_unknown); @@ -54,10 +58,39 @@ void ConfigureGameList::setConfiguration() { ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); } -void ConfigureGameList::applyConfiguration() { - UISettings::values.show_unknown = ui->show_unknown->isChecked(); - UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); - UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); - UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); - Settings::Apply(); +void ConfigureGameList::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + return; + } + + QWidget::changeEvent(event); +} + +void ConfigureGameList::RetranslateUI() { + ui->retranslateUi(this); + + for (int i = 0; i < ui->icon_size_combobox->count(); i++) { + ui->icon_size_combobox->setItemText(i, tr(default_icon_sizes[i].second)); + } + + for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { + const QString name = tr(row_text_names[i]); + + ui->row_1_text_combobox->setItemText(i, name); + ui->row_2_text_combobox->setItemText(i, name); + } +} + +void ConfigureGameList::InitializeIconSizeComboBox() { + for (const auto& size : default_icon_sizes) { + ui->icon_size_combobox->addItem(size.second, size.first); + } +} + +void ConfigureGameList::InitializeRowComboBoxes() { + for (size_t i = 0; i < row_text_names.size(); ++i) { + ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); + ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); + } } 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: private: void setConfiguration(); -private: + void changeEvent(QEvent*) override; + void RetranslateUI(); + + void InitializeIconSizeComboBox(); + void InitializeRowComboBoxes(); + std::unique_ptr<Ui::ConfigureGameList> ui; }; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index ee1287028..839d58f59 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QColorDialog> #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" @@ -16,6 +17,14 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) ui->frame_limit->setEnabled(Settings::values.use_frame_limit); connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, &QSpinBox::setEnabled); + connect(ui->bg_button, &QPushButton::clicked, this, [this] { + const QColor new_bg_color = QColorDialog::getColor(bg_color); + if (!new_bg_color.isValid()) + return; + bg_color = new_bg_color; + ui->bg_button->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(bg_color.name())); + }); } ConfigureGraphics::~ConfigureGraphics() = default; @@ -65,6 +74,10 @@ void ConfigureGraphics::setConfiguration() { ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); ui->frame_limit->setValue(Settings::values.frame_limit); ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers); + bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, + Settings::values.bg_blue); + ui->bg_button->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(bg_color.name())); } void ConfigureGraphics::applyConfiguration() { @@ -73,4 +86,7 @@ void ConfigureGraphics::applyConfiguration() { Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); Settings::values.frame_limit = ui->frame_limit->value(); Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked(); + Settings::values.bg_red = static_cast<float>(bg_color.redF()); + Settings::values.bg_green = static_cast<float>(bg_color.greenF()); + Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 5497a55f7..9bda26fd6 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -25,4 +25,5 @@ private: private: std::unique_ptr<Ui::ConfigureGraphics> ui; + QColor bg_color; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 3bc18c26e..8fc00af1b 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -96,6 +96,27 @@ </item> </layout> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="bg_label"> + <property name="text"> + <string>Background Color:</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="bg_button"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 8c6e16d47..3b3b551bb 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -366,7 +366,7 @@ void GameList::LoadCompatibilityList() { QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); QJsonArray arr = json.array(); - for (const QJsonValue& value : arr) { + for (const QJsonValueRef& value : arr) { QJsonObject game = value.toObject(); if (game.contains("compatibility") && game["compatibility"].isDouble()) { @@ -374,9 +374,9 @@ void GameList::LoadCompatibilityList() { QString directory = game["directory"].toString(); QJsonArray ids = game["releases"].toArray(); - for (const QJsonValue& value : ids) { - QJsonObject object = value.toObject(); - QString id = object["id"].toString(); + for (const QJsonValueRef& id_ref : ids) { + QJsonObject id_object = id_ref.toObject(); + QString id = id_object["id"].toString(); compatibility_list.emplace( id.toUpper().toStdString(), std::make_pair(QString::number(compatibility), directory)); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e36914f14..05a4a55e8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -447,6 +447,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() { unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); if (!GLAD_GL_ARB_base_instance) unsupported_ext.append("ARB_base_instance"); + if (!GLAD_GL_ARB_texture_storage) + unsupported_ext.append("ARB_texture_storage"); // Extensions required to support some texture formats. 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() { // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); 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 = # auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) output_engine = +# Whether or not to enable the audio-stretching post-processing effect. +# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, +# at the cost of increasing audio latency. +# 0: No, 1 (default): Yes +enable_audio_stretching = + # Which audio device to use. # auto (default): Auto-select output_device = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 1c4717123..d213929bd 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -94,6 +94,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); if (!GLAD_GL_ARB_base_instance) unsupported_ext.push_back("ARB_base_instance"); + if (!GLAD_GL_ARB_texture_storage) + unsupported_ext.push_back("ARB_texture_storage"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) |