diff options
33 files changed, 506 insertions, 142 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 60ec58eda..8b1734f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -253,11 +253,82 @@ if(ENABLE_QT) | |||
| 253 | 253 | ||
| 254 | # Check for system Qt on Linux, fallback to bundled Qt | 254 | # Check for system Qt on Linux, fallback to bundled Qt |
| 255 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") | 255 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") |
| 256 | if (NOT YUZU_USE_BUNDLED_QT) | 256 | find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets) |
| 257 | find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets QUIET) | 257 | if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT) |
| 258 | if (NOT Qt5_FOUND) | 258 | # Check for dependencies, then enable bundled Qt download |
| 259 | set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE) | 259 | |
| 260 | # Check that the system GLIBCXX version is compatible | ||
| 261 | find_program(OBJDUMP objdump) | ||
| 262 | if ("${OBJDUMP}" STREQUAL "OBJDUMP-NOTFOUND") | ||
| 263 | message(FATAL_ERROR "Required program `objdump` not found.") | ||
| 260 | endif() | 264 | endif() |
| 265 | find_library(LIBSTDCXX libstdc++.so.6) | ||
| 266 | execute_process( | ||
| 267 | COMMAND | ||
| 268 | ${OBJDUMP} -T ${LIBSTDCXX} | ||
| 269 | COMMAND | ||
| 270 | grep GLIBCXX_3.4.28 | ||
| 271 | COMMAND | ||
| 272 | sed "s/[0-9a-f]*.* //" | ||
| 273 | COMMAND | ||
| 274 | sed "s/ .*//" | ||
| 275 | COMMAND | ||
| 276 | sort -u | ||
| 277 | OUTPUT_VARIABLE | ||
| 278 | GLIBCXX_MET | ||
| 279 | ) | ||
| 280 | if (NOT GLIBCXX_MET) | ||
| 281 | message(FATAL_ERROR "Qt too old or not found, and bundled Qt package is not \ | ||
| 282 | compatible with this system. Either install Qt ${QT_VERSION}, or provide the path \ | ||
| 283 | to Qt by setting the variable Qt5_ROOT.") | ||
| 284 | endif() | ||
| 285 | |||
| 286 | # Check for headers | ||
| 287 | Include(FindPkgConfig REQUIRED) | ||
| 288 | pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0) | ||
| 289 | if (NOT QT_DEP_GLU_FOUND) | ||
| 290 | message(FATAL_ERROR "Qt bundled pacakge dependency `glu` not found. \ | ||
| 291 | Perhaps `libglu1-mesa-dev` needs to be installed?") | ||
| 292 | endif() | ||
| 293 | pkg_check_modules(QT_DEP_MESA QUIET dri>=20.0.8) | ||
| 294 | if (NOT QT_DEP_MESA_FOUND) | ||
| 295 | message(FATAL_ERROR "Qt bundled pacakge dependency `dri` not found. \ | ||
| 296 | Perhaps `mesa-common-dev` needs to be installed?") | ||
| 297 | endif() | ||
| 298 | |||
| 299 | # Check for X libraries | ||
| 300 | set(BUNDLED_QT_REQUIREMENTS | ||
| 301 | libxcb-icccm.so.4 | ||
| 302 | libxcb-image.so.0 | ||
| 303 | libxcb-keysyms.so.1 | ||
| 304 | libxcb-randr.so.0 | ||
| 305 | libxcb-render-util.so.0 | ||
| 306 | libxcb-render.so.0 | ||
| 307 | libxcb-shape.so.0 | ||
| 308 | libxcb-shm.so.0 | ||
| 309 | libxcb-sync.so.1 | ||
| 310 | libxcb-xfixes.so.0 | ||
| 311 | libxcb-xinerama.so.0 | ||
| 312 | libxcb-xkb.so.1 | ||
| 313 | libxcb.so.1 | ||
| 314 | libxkbcommon-x11.so.0 | ||
| 315 | libxkbcommon.so.0 | ||
| 316 | ) | ||
| 317 | set(UNRESOLVED_QT_DEPS "") | ||
| 318 | foreach (REQUIREMENT ${BUNDLED_QT_REQUIREMENTS}) | ||
| 319 | find_library(BUNDLED_QT_${REQUIREMENT} ${REQUIREMENT}) | ||
| 320 | if ("${BUNDLED_QT_${REQUIREMENT}}" STREQUAL "BUNDLED_QT_${REQUIREMENT}-NOTFOUND") | ||
| 321 | set(UNRESOLVED_QT_DEPS ${UNRESOLVED_QT_DEPS} ${REQUIREMENT}) | ||
| 322 | endif() | ||
| 323 | unset(BUNDLED_QT_${REQUIREMENT}) | ||
| 324 | endforeach() | ||
| 325 | unset(BUNDLED_QT_REQUIREMENTS) | ||
| 326 | |||
| 327 | if (NOT "${UNRESOLVED_QT_DEPS}" STREQUAL "") | ||
| 328 | message(FATAL_ERROR "Bundled Qt package missing required dependencies: ${UNRESOLVED_QT_DEPS}") | ||
| 329 | endif() | ||
| 330 | |||
| 331 | set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE) | ||
| 261 | endif() | 332 | endif() |
| 262 | if (YUZU_USE_BUNDLED_QT) | 333 | if (YUZU_USE_BUNDLED_QT) |
| 263 | # Binary package currently does not support Qt webengine, so make sure it's disabled | 334 | # Binary package currently does not support Qt webengine, so make sure it's disabled |
| @@ -473,7 +544,15 @@ if (YUZU_USE_BUNDLED_FFMPEG) | |||
| 473 | 544 | ||
| 474 | # FFmpeg has source that requires one of nasm or yasm to assemble it. | 545 | # FFmpeg has source that requires one of nasm or yasm to assemble it. |
| 475 | # REQUIRED throws an error if not found here during configuration rather than during compilation. | 546 | # REQUIRED throws an error if not found here during configuration rather than during compilation. |
| 476 | find_program(ASSEMBLER NAMES nasm yasm REQUIRED) | 547 | find_program(ASSEMBLER NAMES nasm yasm) |
| 548 | if ("${ASSEMBLER}" STREQUAL "ASSEMBLER-NOTFOUND") | ||
| 549 | message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.") | ||
| 550 | endif() | ||
| 551 | |||
| 552 | find_program(AUTOCONF autoconf) | ||
| 553 | if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND") | ||
| 554 | message(FATAL_ERROR "Required program `autoconf` not found.") | ||
| 555 | endif() | ||
| 477 | 556 | ||
| 478 | set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg) | 557 | set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg) |
| 479 | set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg) | 558 | set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg) |
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 8b9e6433c..151ddc462 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt | |||
| @@ -5,6 +5,17 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE) | |||
| 5 | # GNU toolchains for some reason doesn't work with the later half of this CMakeLists after | 5 | # GNU toolchains for some reason doesn't work with the later half of this CMakeLists after |
| 6 | # updating to 1.0.24, so we do it the old-fashioned way for now. | 6 | # updating to 1.0.24, so we do it the old-fashioned way for now. |
| 7 | 7 | ||
| 8 | # Require autoconf and libtoolize here, rather than crash during compilation | ||
| 9 | find_program(AUTOCONF autoconf) | ||
| 10 | if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND") | ||
| 11 | message(FATAL_ERROR "Required program `autoconf` not found.") | ||
| 12 | endif() | ||
| 13 | |||
| 14 | find_program(LIBTOOLIZE libtoolize) | ||
| 15 | if ("${LIBTOOLIZE}" STREQUAL "LIBTOOLIZE-NOTFOUND") | ||
| 16 | message(FATAL_ERROR "Required program `libtoolize` not found.") | ||
| 17 | endif() | ||
| 18 | |||
| 8 | set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb") | 19 | set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb") |
| 9 | set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb") | 20 | set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb") |
| 10 | 21 | ||
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f30dd49a3..1c3dde31d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -49,6 +49,7 @@ if (MSVC) | |||
| 49 | /W3 | 49 | /W3 |
| 50 | /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled | 50 | /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled |
| 51 | /we4101 # 'identifier': unreferenced local variable | 51 | /we4101 # 'identifier': unreferenced local variable |
| 52 | /we4189 # 'identifier': local variable is initialized but not referenced | ||
| 52 | /we4265 # 'class': class has virtual functions, but destructor is not virtual | 53 | /we4265 # 'class': class has virtual functions, but destructor is not virtual |
| 53 | /we4388 # signed/unsigned mismatch | 54 | /we4388 # signed/unsigned mismatch |
| 54 | /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect | 55 | /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect |
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 80ffddb10..ccd5ca6cc 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "audio_core/voice_context.h" | 12 | #include "audio_core/voice_context.h" |
| 13 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 14 | #include "common/settings.h" | 14 | #include "common/settings.h" |
| 15 | #include "core/core_timing.h" | ||
| 15 | #include "core/memory.h" | 16 | #include "core/memory.h" |
| 16 | 17 | ||
| 17 | namespace { | 18 | namespace { |
| @@ -68,7 +69,9 @@ namespace { | |||
| 68 | } // namespace | 69 | } // namespace |
| 69 | 70 | ||
| 70 | namespace AudioCore { | 71 | namespace AudioCore { |
| 71 | AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, | 72 | constexpr s32 NUM_BUFFERS = 2; |
| 73 | |||
| 74 | AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_, | ||
| 72 | AudioCommon::AudioRendererParameter params, | 75 | AudioCommon::AudioRendererParameter params, |
| 73 | Stream::ReleaseCallback&& release_callback, | 76 | Stream::ReleaseCallback&& release_callback, |
| 74 | std::size_t instance_number) | 77 | std::size_t instance_number) |
| @@ -77,7 +80,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory | |||
| 77 | sink_context(params.sink_count), splitter_context(), | 80 | sink_context(params.sink_count), splitter_context(), |
| 78 | voices(params.voice_count), memory{memory_}, | 81 | voices(params.voice_count), memory{memory_}, |
| 79 | command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, | 82 | command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, |
| 80 | memory) { | 83 | memory), |
| 84 | core_timing{core_timing_} { | ||
| 81 | behavior_info.SetUserRevision(params.revision); | 85 | behavior_info.SetUserRevision(params.revision); |
| 82 | splitter_context.Initialize(behavior_info, params.splitter_count, | 86 | splitter_context.Initialize(behavior_info, params.splitter_count, |
| 83 | params.num_splitter_send_channels); | 87 | params.num_splitter_send_channels); |
| @@ -86,16 +90,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory | |||
| 86 | stream = audio_out->OpenStream( | 90 | stream = audio_out->OpenStream( |
| 87 | core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, | 91 | core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, |
| 88 | fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); | 92 | fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); |
| 89 | audio_out->StartStream(stream); | 93 | process_event = Core::Timing::CreateEvent( |
| 90 | 94 | fmt::format("AudioRenderer-Instance{}-Process", instance_number), | |
| 91 | QueueMixedBuffer(0); | 95 | [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); }); |
| 92 | QueueMixedBuffer(1); | 96 | for (s32 i = 0; i < NUM_BUFFERS; ++i) { |
| 93 | QueueMixedBuffer(2); | 97 | QueueMixedBuffer(i); |
| 94 | QueueMixedBuffer(3); | 98 | } |
| 95 | } | 99 | } |
| 96 | 100 | ||
| 97 | AudioRenderer::~AudioRenderer() = default; | 101 | AudioRenderer::~AudioRenderer() = default; |
| 98 | 102 | ||
| 103 | ResultCode AudioRenderer::Start() { | ||
| 104 | audio_out->StartStream(stream); | ||
| 105 | ReleaseAndQueueBuffers(); | ||
| 106 | return ResultSuccess; | ||
| 107 | } | ||
| 108 | |||
| 109 | ResultCode AudioRenderer::Stop() { | ||
| 110 | audio_out->StopStream(stream); | ||
| 111 | return ResultSuccess; | ||
| 112 | } | ||
| 113 | |||
| 99 | u32 AudioRenderer::GetSampleRate() const { | 114 | u32 AudioRenderer::GetSampleRate() const { |
| 100 | return worker_params.sample_rate; | 115 | return worker_params.sample_rate; |
| 101 | } | 116 | } |
| @@ -114,7 +129,7 @@ Stream::State AudioRenderer::GetStreamState() const { | |||
| 114 | 129 | ||
| 115 | ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, | 130 | ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, |
| 116 | std::vector<u8>& output_params) { | 131 | std::vector<u8>& output_params) { |
| 117 | 132 | std::scoped_lock lock{mutex}; | |
| 118 | InfoUpdater info_updater{input_params, output_params, behavior_info}; | 133 | InfoUpdater info_updater{input_params, output_params, behavior_info}; |
| 119 | 134 | ||
| 120 | if (!info_updater.UpdateBehaviorInfo(behavior_info)) { | 135 | if (!info_updater.UpdateBehaviorInfo(behavior_info)) { |
| @@ -194,9 +209,6 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param | |||
| 194 | LOG_ERROR(Audio, "Audio buffers were not consumed!"); | 209 | LOG_ERROR(Audio, "Audio buffers were not consumed!"); |
| 195 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; | 210 | return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
| 196 | } | 211 | } |
| 197 | |||
| 198 | ReleaseAndQueueBuffers(); | ||
| 199 | |||
| 200 | return ResultSuccess; | 212 | return ResultSuccess; |
| 201 | } | 213 | } |
| 202 | 214 | ||
| @@ -220,10 +232,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | |||
| 220 | command_generator.PostCommand(); | 232 | command_generator.PostCommand(); |
| 221 | // Base sample size | 233 | // Base sample size |
| 222 | std::size_t BUFFER_SIZE{worker_params.sample_count}; | 234 | std::size_t BUFFER_SIZE{worker_params.sample_count}; |
| 223 | // Samples | 235 | // Samples, making sure to clear |
| 224 | std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); | 236 | std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0); |
| 225 | // Make sure to clear our samples | ||
| 226 | std::memset(buffer.data(), 0, buffer.size() * sizeof(s16)); | ||
| 227 | 237 | ||
| 228 | if (sink_context.InUse()) { | 238 | if (sink_context.InUse()) { |
| 229 | const auto stream_channel_count = stream->GetNumChannels(); | 239 | const auto stream_channel_count = stream->GetNumChannels(); |
| @@ -315,10 +325,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | |||
| 315 | } | 325 | } |
| 316 | 326 | ||
| 317 | void AudioRenderer::ReleaseAndQueueBuffers() { | 327 | void AudioRenderer::ReleaseAndQueueBuffers() { |
| 318 | const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; | 328 | if (!stream->IsPlaying()) { |
| 319 | for (const auto& tag : released_buffers) { | 329 | return; |
| 320 | QueueMixedBuffer(tag); | ||
| 321 | } | 330 | } |
| 331 | |||
| 332 | { | ||
| 333 | std::scoped_lock lock{mutex}; | ||
| 334 | const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; | ||
| 335 | for (const auto& tag : released_buffers) { | ||
| 336 | QueueMixedBuffer(tag); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | const f32 sample_rate = static_cast<f32>(GetSampleRate()); | ||
| 341 | const f32 sample_count = static_cast<f32>(GetSampleCount()); | ||
| 342 | const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); | ||
| 343 | const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1; | ||
| 344 | const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1)); | ||
| 345 | core_timing.ScheduleEvent(next_event_time, process_event, {}); | ||
| 322 | } | 346 | } |
| 323 | 347 | ||
| 324 | } // namespace AudioCore | 348 | } // namespace AudioCore |
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index 18567f618..88fdd13dd 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <mutex> | ||
| 9 | #include <vector> | 10 | #include <vector> |
| 10 | 11 | ||
| 11 | #include "audio_core/behavior_info.h" | 12 | #include "audio_core/behavior_info.h" |
| @@ -45,6 +46,8 @@ public: | |||
| 45 | 46 | ||
| 46 | [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, | 47 | [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, |
| 47 | std::vector<u8>& output_params); | 48 | std::vector<u8>& output_params); |
| 49 | [[nodiscard]] ResultCode Start(); | ||
| 50 | [[nodiscard]] ResultCode Stop(); | ||
| 48 | void QueueMixedBuffer(Buffer::Tag tag); | 51 | void QueueMixedBuffer(Buffer::Tag tag); |
| 49 | void ReleaseAndQueueBuffers(); | 52 | void ReleaseAndQueueBuffers(); |
| 50 | [[nodiscard]] u32 GetSampleRate() const; | 53 | [[nodiscard]] u32 GetSampleRate() const; |
| @@ -68,6 +71,9 @@ private: | |||
| 68 | Core::Memory::Memory& memory; | 71 | Core::Memory::Memory& memory; |
| 69 | CommandGenerator command_generator; | 72 | CommandGenerator command_generator; |
| 70 | std::size_t elapsed_frame_count{}; | 73 | std::size_t elapsed_frame_count{}; |
| 74 | Core::Timing::CoreTiming& core_timing; | ||
| 75 | std::shared_ptr<Core::Timing::EventType> process_event; | ||
| 76 | std::mutex mutex; | ||
| 71 | }; | 77 | }; |
| 72 | 78 | ||
| 73 | } // namespace AudioCore | 79 | } // namespace AudioCore |
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 437cc5ccd..27437f1ea 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp | |||
| @@ -795,7 +795,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta | |||
| 795 | state.lowpass_1 = 0.0f; | 795 | state.lowpass_1 = 0.0f; |
| 796 | } else { | 796 | } else { |
| 797 | const auto a = 1.0f - hf_gain; | 797 | const auto a = 1.0f - hf_gain; |
| 798 | const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / | 798 | const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference / |
| 799 | static_cast<f32>(info.sample_rate))); | 799 | static_cast<f32>(info.sample_rate))); |
| 800 | const auto c = std::sqrt(b * b - 4.0f * a * a); | 800 | const auto c = std::sqrt(b * b - 4.0f * a * a); |
| 801 | 801 | ||
| @@ -843,7 +843,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta | |||
| 843 | } | 843 | } |
| 844 | 844 | ||
| 845 | const auto max_early_delay = state.early_delay_line.GetMaxDelay(); | 845 | const auto max_early_delay = state.early_delay_line.GetMaxDelay(); |
| 846 | const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); | 846 | const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f); |
| 847 | for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { | 847 | for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { |
| 848 | const auto length = AudioCommon::CalculateDelaySamples( | 848 | const auto length = AudioCommon::CalculateDelaySamples( |
| 849 | sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); | 849 | sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); |
| @@ -1004,7 +1004,8 @@ void CommandGenerator::GenerateFinalMixCommand() { | |||
| 1004 | } | 1004 | } |
| 1005 | 1005 | ||
| 1006 | s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | 1006 | s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, |
| 1007 | s32 sample_count, s32 channel, std::size_t mix_offset) { | 1007 | s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, |
| 1008 | s32 channel, std::size_t mix_offset) { | ||
| 1008 | const auto& in_params = voice_info.GetInParams(); | 1009 | const auto& in_params = voice_info.GetInParams(); |
| 1009 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | 1010 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; |
| 1010 | if (wave_buffer.buffer_address == 0) { | 1011 | if (wave_buffer.buffer_address == 0) { |
| @@ -1013,14 +1014,12 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s | |||
| 1013 | if (wave_buffer.buffer_size == 0) { | 1014 | if (wave_buffer.buffer_size == 0) { |
| 1014 | return 0; | 1015 | return 0; |
| 1015 | } | 1016 | } |
| 1016 | if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { | 1017 | if (sample_end_offset < sample_start_offset) { |
| 1017 | return 0; | 1018 | return 0; |
| 1018 | } | 1019 | } |
| 1019 | const auto samples_remaining = | 1020 | const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; |
| 1020 | (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; | ||
| 1021 | const auto start_offset = | 1021 | const auto start_offset = |
| 1022 | ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * | 1022 | ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(s16); |
| 1023 | sizeof(s16); | ||
| 1024 | const auto buffer_pos = wave_buffer.buffer_address + start_offset; | 1023 | const auto buffer_pos = wave_buffer.buffer_address + start_offset; |
| 1025 | const auto samples_processed = std::min(sample_count, samples_remaining); | 1024 | const auto samples_processed = std::min(sample_count, samples_remaining); |
| 1026 | 1025 | ||
| @@ -1044,8 +1043,8 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s | |||
| 1044 | } | 1043 | } |
| 1045 | 1044 | ||
| 1046 | s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, | 1045 | s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, |
| 1047 | s32 sample_count, [[maybe_unused]] s32 channel, | 1046 | s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, |
| 1048 | std::size_t mix_offset) { | 1047 | [[maybe_unused]] s32 channel, std::size_t mix_offset) { |
| 1049 | const auto& in_params = voice_info.GetInParams(); | 1048 | const auto& in_params = voice_info.GetInParams(); |
| 1050 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | 1049 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; |
| 1051 | if (wave_buffer.buffer_address == 0) { | 1050 | if (wave_buffer.buffer_address == 0) { |
| @@ -1054,7 +1053,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s | |||
| 1054 | if (wave_buffer.buffer_size == 0) { | 1053 | if (wave_buffer.buffer_size == 0) { |
| 1055 | return 0; | 1054 | return 0; |
| 1056 | } | 1055 | } |
| 1057 | if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { | 1056 | if (sample_end_offset < sample_start_offset) { |
| 1058 | return 0; | 1057 | return 0; |
| 1059 | } | 1058 | } |
| 1060 | 1059 | ||
| @@ -1079,10 +1078,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s | |||
| 1079 | s32 coef1 = coeffs[idx * 2]; | 1078 | s32 coef1 = coeffs[idx * 2]; |
| 1080 | s32 coef2 = coeffs[idx * 2 + 1]; | 1079 | s32 coef2 = coeffs[idx * 2 + 1]; |
| 1081 | 1080 | ||
| 1082 | const auto samples_remaining = | 1081 | const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; |
| 1083 | (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; | ||
| 1084 | const auto samples_processed = std::min(sample_count, samples_remaining); | 1082 | const auto samples_processed = std::min(sample_count, samples_remaining); |
| 1085 | const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset; | 1083 | const auto sample_pos = dsp_state.offset + sample_start_offset; |
| 1086 | 1084 | ||
| 1087 | const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; | 1085 | const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; |
| 1088 | auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + | 1086 | auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + |
| @@ -1210,9 +1208,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o | |||
| 1210 | } | 1208 | } |
| 1211 | 1209 | ||
| 1212 | std::size_t temp_mix_offset{}; | 1210 | std::size_t temp_mix_offset{}; |
| 1213 | bool is_buffer_completed{false}; | ||
| 1214 | auto samples_remaining = sample_count; | 1211 | auto samples_remaining = sample_count; |
| 1215 | while (samples_remaining > 0 && !is_buffer_completed) { | 1212 | while (samples_remaining > 0) { |
| 1216 | const auto samples_to_output = std::min(samples_remaining, min_required_samples); | 1213 | const auto samples_to_output = std::min(samples_remaining, min_required_samples); |
| 1217 | const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; | 1214 | const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; |
| 1218 | 1215 | ||
| @@ -1229,24 +1226,38 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o | |||
| 1229 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; | 1226 | const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; |
| 1230 | // No more data can be read | 1227 | // No more data can be read |
| 1231 | if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { | 1228 | if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { |
| 1232 | is_buffer_completed = true; | ||
| 1233 | break; | 1229 | break; |
| 1234 | } | 1230 | } |
| 1235 | 1231 | ||
| 1236 | if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && | 1232 | if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && |
| 1237 | wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { | 1233 | wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { |
| 1238 | // TODO(ogniK): ADPCM loop context | 1234 | memory.ReadBlock(wave_buffer.context_address, &dsp_state.context, |
| 1235 | sizeof(ADPCMContext)); | ||
| 1236 | } | ||
| 1237 | |||
| 1238 | s32 samples_offset_start; | ||
| 1239 | s32 samples_offset_end; | ||
| 1240 | if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 && | ||
| 1241 | wave_buffer.loop_end_sample != 0 && | ||
| 1242 | wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) { | ||
| 1243 | samples_offset_start = wave_buffer.loop_start_sample; | ||
| 1244 | samples_offset_end = wave_buffer.loop_end_sample; | ||
| 1245 | } else { | ||
| 1246 | samples_offset_start = wave_buffer.start_sample_offset; | ||
| 1247 | samples_offset_end = wave_buffer.end_sample_offset; | ||
| 1239 | } | 1248 | } |
| 1240 | 1249 | ||
| 1241 | s32 samples_decoded{0}; | 1250 | s32 samples_decoded{0}; |
| 1242 | switch (in_params.sample_format) { | 1251 | switch (in_params.sample_format) { |
| 1243 | case SampleFormat::Pcm16: | 1252 | case SampleFormat::Pcm16: |
| 1244 | samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, | 1253 | samples_decoded = |
| 1245 | channel, temp_mix_offset); | 1254 | DecodePcm16(voice_info, dsp_state, samples_offset_start, samples_offset_end, |
| 1255 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1246 | break; | 1256 | break; |
| 1247 | case SampleFormat::Adpcm: | 1257 | case SampleFormat::Adpcm: |
| 1248 | samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, | 1258 | samples_decoded = |
| 1249 | channel, temp_mix_offset); | 1259 | DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, |
| 1260 | samples_to_read - samples_read, channel, temp_mix_offset); | ||
| 1250 | break; | 1261 | break; |
| 1251 | default: | 1262 | default: |
| 1252 | UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); | 1263 | UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); |
| @@ -1257,15 +1268,19 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o | |||
| 1257 | dsp_state.offset += samples_decoded; | 1268 | dsp_state.offset += samples_decoded; |
| 1258 | dsp_state.played_sample_count += samples_decoded; | 1269 | dsp_state.played_sample_count += samples_decoded; |
| 1259 | 1270 | ||
| 1260 | if (dsp_state.offset >= | 1271 | if (dsp_state.offset >= (samples_offset_end - samples_offset_start) || |
| 1261 | (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) || | ||
| 1262 | samples_decoded == 0) { | 1272 | samples_decoded == 0) { |
| 1263 | // Reset our sample offset | 1273 | // Reset our sample offset |
| 1264 | dsp_state.offset = 0; | 1274 | dsp_state.offset = 0; |
| 1265 | if (wave_buffer.is_looping) { | 1275 | if (wave_buffer.is_looping) { |
| 1266 | if (samples_decoded == 0) { | 1276 | dsp_state.loop_count++; |
| 1277 | if (wave_buffer.loop_count > 0 && | ||
| 1278 | (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) { | ||
| 1267 | // End of our buffer | 1279 | // End of our buffer |
| 1268 | is_buffer_completed = true; | 1280 | voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); |
| 1281 | } | ||
| 1282 | |||
| 1283 | if (samples_decoded == 0) { | ||
| 1269 | break; | 1284 | break; |
| 1270 | } | 1285 | } |
| 1271 | 1286 | ||
| @@ -1273,15 +1288,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o | |||
| 1273 | dsp_state.played_sample_count = 0; | 1288 | dsp_state.played_sample_count = 0; |
| 1274 | } | 1289 | } |
| 1275 | } else { | 1290 | } else { |
| 1276 | |||
| 1277 | // Update our wave buffer states | 1291 | // Update our wave buffer states |
| 1278 | dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; | 1292 | voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); |
| 1279 | dsp_state.wave_buffer_consumed++; | ||
| 1280 | dsp_state.wave_buffer_index = | ||
| 1281 | (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 1282 | if (wave_buffer.end_of_stream) { | ||
| 1283 | dsp_state.played_sample_count = 0; | ||
| 1284 | } | ||
| 1285 | } | 1293 | } |
| 1286 | } | 1294 | } |
| 1287 | } | 1295 | } |
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 2ebb755b0..673e4fbef 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h | |||
| @@ -86,10 +86,10 @@ private: | |||
| 86 | std::vector<u8>& work_buffer); | 86 | std::vector<u8>& work_buffer); |
| 87 | void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); | 87 | void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); |
| 88 | // DSP Code | 88 | // DSP Code |
| 89 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, | 89 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, |
| 90 | s32 channel, std::size_t mix_offset); | 90 | s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); |
| 91 | s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, | 91 | s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, |
| 92 | s32 channel, std::size_t mix_offset); | 92 | s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); |
| 93 | void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, | 93 | void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, |
| 94 | s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); | 94 | s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); |
| 95 | 95 | ||
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp index 4a5b1b4ab..9b4ca1851 100644 --- a/src/audio_core/info_updater.cpp +++ b/src/audio_core/info_updater.cpp | |||
| @@ -189,9 +189,6 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, | |||
| 189 | if (voice_in_params.is_new) { | 189 | if (voice_in_params.is_new) { |
| 190 | // Default our values for our voice | 190 | // Default our values for our voice |
| 191 | voice_info.Initialize(); | 191 | voice_info.Initialize(); |
| 192 | if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) { | ||
| 193 | continue; | ||
| 194 | } | ||
| 195 | 192 | ||
| 196 | // Zero out our voice states | 193 | // Zero out our voice states |
| 197 | for (std::size_t channel = 0; channel < channel_count; channel++) { | 194 | for (std::size_t channel = 0; channel < channel_count; channel++) { |
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index 867b8fc6b..d8c954b60 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp | |||
| @@ -66,7 +66,7 @@ void ServerVoiceInfo::Initialize() { | |||
| 66 | in_params.last_volume = 0.0f; | 66 | in_params.last_volume = 0.0f; |
| 67 | in_params.biquad_filter.fill({}); | 67 | in_params.biquad_filter.fill({}); |
| 68 | in_params.wave_buffer_count = 0; | 68 | in_params.wave_buffer_count = 0; |
| 69 | in_params.wave_bufffer_head = 0; | 69 | in_params.wave_buffer_head = 0; |
| 70 | in_params.mix_id = AudioCommon::NO_MIX; | 70 | in_params.mix_id = AudioCommon::NO_MIX; |
| 71 | in_params.splitter_info_id = AudioCommon::NO_SPLITTER; | 71 | in_params.splitter_info_id = AudioCommon::NO_SPLITTER; |
| 72 | in_params.additional_params_address = 0; | 72 | in_params.additional_params_address = 0; |
| @@ -75,7 +75,7 @@ void ServerVoiceInfo::Initialize() { | |||
| 75 | out_params.played_sample_count = 0; | 75 | out_params.played_sample_count = 0; |
| 76 | out_params.wave_buffer_consumed = 0; | 76 | out_params.wave_buffer_consumed = 0; |
| 77 | in_params.voice_drop_flag = false; | 77 | in_params.voice_drop_flag = false; |
| 78 | in_params.buffer_mapped = false; | 78 | in_params.buffer_mapped = true; |
| 79 | in_params.wave_buffer_flush_request_count = 0; | 79 | in_params.wave_buffer_flush_request_count = 0; |
| 80 | in_params.was_biquad_filter_enabled.fill(false); | 80 | in_params.was_biquad_filter_enabled.fill(false); |
| 81 | 81 | ||
| @@ -126,7 +126,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, | |||
| 126 | in_params.volume = voice_in.volume; | 126 | in_params.volume = voice_in.volume; |
| 127 | in_params.biquad_filter = voice_in.biquad_filter; | 127 | in_params.biquad_filter = voice_in.biquad_filter; |
| 128 | in_params.wave_buffer_count = voice_in.wave_buffer_count; | 128 | in_params.wave_buffer_count = voice_in.wave_buffer_count; |
| 129 | in_params.wave_bufffer_head = voice_in.wave_buffer_head; | 129 | in_params.wave_buffer_head = voice_in.wave_buffer_head; |
| 130 | if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { | 130 | if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { |
| 131 | const auto in_request_count = in_params.wave_buffer_flush_request_count; | 131 | const auto in_request_count = in_params.wave_buffer_flush_request_count; |
| 132 | const auto voice_request_count = voice_in.wave_buffer_flush_request_count; | 132 | const auto voice_request_count = voice_in.wave_buffer_flush_request_count; |
| @@ -185,14 +185,16 @@ void ServerVoiceInfo::UpdateWaveBuffers( | |||
| 185 | wave_buffer.buffer_size = 0; | 185 | wave_buffer.buffer_size = 0; |
| 186 | wave_buffer.context_address = 0; | 186 | wave_buffer.context_address = 0; |
| 187 | wave_buffer.context_size = 0; | 187 | wave_buffer.context_size = 0; |
| 188 | wave_buffer.loop_start_sample = 0; | ||
| 189 | wave_buffer.loop_end_sample = 0; | ||
| 188 | wave_buffer.sent_to_dsp = true; | 190 | wave_buffer.sent_to_dsp = true; |
| 189 | } | 191 | } |
| 190 | 192 | ||
| 191 | // Mark all our wave buffers as invalid | 193 | // Mark all our wave buffers as invalid |
| 192 | for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); | 194 | for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); |
| 193 | channel++) { | 195 | channel++) { |
| 194 | for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) { | 196 | for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) { |
| 195 | is_valid = false; | 197 | voice_states[channel]->is_wave_buffer_valid[i] = false; |
| 196 | } | 198 | } |
| 197 | } | 199 | } |
| 198 | } | 200 | } |
| @@ -211,7 +213,7 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, | |||
| 211 | const WaveBuffer& in_wave_buffer, SampleFormat sample_format, | 213 | const WaveBuffer& in_wave_buffer, SampleFormat sample_format, |
| 212 | bool is_buffer_valid, | 214 | bool is_buffer_valid, |
| 213 | [[maybe_unused]] BehaviorInfo& behavior_info) { | 215 | [[maybe_unused]] BehaviorInfo& behavior_info) { |
| 214 | if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { | 216 | if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) { |
| 215 | out_wavebuffer.buffer_address = 0; | 217 | out_wavebuffer.buffer_address = 0; |
| 216 | out_wavebuffer.buffer_size = 0; | 218 | out_wavebuffer.buffer_size = 0; |
| 217 | } | 219 | } |
| @@ -219,11 +221,40 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, | |||
| 219 | if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { | 221 | if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { |
| 220 | // Validate sample offset sizings | 222 | // Validate sample offset sizings |
| 221 | if (sample_format == SampleFormat::Pcm16) { | 223 | if (sample_format == SampleFormat::Pcm16) { |
| 222 | const auto buffer_size = in_wave_buffer.buffer_size; | 224 | const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); |
| 223 | if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 || | 225 | const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset; |
| 224 | (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) || | 226 | const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset; |
| 225 | (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) { | 227 | if (0 > start || start > buffer_size || 0 > end || end > buffer_size) { |
| 226 | // TODO(ogniK): Write error info | 228 | // TODO(ogniK): Write error info |
| 229 | LOG_ERROR(Audio, | ||
| 230 | "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " | ||
| 231 | "offsets were " | ||
| 232 | "{:08X} - 0x{:08X}", | ||
| 233 | buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset, | ||
| 234 | sizeof(s16) * in_wave_buffer.end_sample_offset); | ||
| 235 | return; | ||
| 236 | } | ||
| 237 | } else if (sample_format == SampleFormat::Adpcm) { | ||
| 238 | const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size); | ||
| 239 | const s64 start_frames = in_wave_buffer.start_sample_offset / 14; | ||
| 240 | const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0 | ||
| 241 | ? 0 | ||
| 242 | : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 + | ||
| 243 | (in_wave_buffer.start_sample_offset % 2); | ||
| 244 | const s64 start = start_frames * 8 + start_extra; | ||
| 245 | const s64 end_frames = in_wave_buffer.end_sample_offset / 14; | ||
| 246 | const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0 | ||
| 247 | ? 0 | ||
| 248 | : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 + | ||
| 249 | (in_wave_buffer.end_sample_offset % 2); | ||
| 250 | const s64 end = end_frames * 8 + end_extra; | ||
| 251 | if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size || | ||
| 252 | in_wave_buffer.end_sample_offset < 0 || end > buffer_size) { | ||
| 253 | LOG_ERROR(Audio, | ||
| 254 | "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " | ||
| 255 | "offsets were " | ||
| 256 | "{:08X} - 0x{:08X}", | ||
| 257 | in_wave_buffer.buffer_size, start, end); | ||
| 227 | return; | 258 | return; |
| 228 | } | 259 | } |
| 229 | } | 260 | } |
| @@ -239,29 +270,34 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, | |||
| 239 | out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; | 270 | out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; |
| 240 | out_wavebuffer.context_address = in_wave_buffer.context_address; | 271 | out_wavebuffer.context_address = in_wave_buffer.context_address; |
| 241 | out_wavebuffer.context_size = in_wave_buffer.context_size; | 272 | out_wavebuffer.context_size = in_wave_buffer.context_size; |
| 273 | out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample; | ||
| 274 | out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample; | ||
| 242 | in_params.buffer_mapped = | 275 | in_params.buffer_mapped = |
| 243 | in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; | 276 | in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; |
| 244 | // TODO(ogniK): Pool mapper attachment | 277 | // TODO(ogniK): Pool mapper attachment |
| 245 | // TODO(ogniK): IsAdpcmLoopContextBugFixed | 278 | // TODO(ogniK): IsAdpcmLoopContextBugFixed |
| 279 | if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 && | ||
| 280 | in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) { | ||
| 281 | } else { | ||
| 282 | out_wavebuffer.context_address = 0; | ||
| 283 | out_wavebuffer.context_size = 0; | ||
| 284 | } | ||
| 246 | } | 285 | } |
| 247 | } | 286 | } |
| 248 | 287 | ||
| 249 | void ServerVoiceInfo::WriteOutStatus( | 288 | void ServerVoiceInfo::WriteOutStatus( |
| 250 | VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, | 289 | VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, |
| 251 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { | 290 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { |
| 252 | if (voice_in.is_new) { | 291 | if (voice_in.is_new || in_params.is_new) { |
| 253 | in_params.is_new = true; | 292 | in_params.is_new = true; |
| 254 | voice_out.wave_buffer_consumed = 0; | 293 | voice_out.wave_buffer_consumed = 0; |
| 255 | voice_out.played_sample_count = 0; | 294 | voice_out.played_sample_count = 0; |
| 256 | voice_out.voice_dropped = false; | 295 | voice_out.voice_dropped = false; |
| 257 | } else if (!in_params.is_new) { | ||
| 258 | voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed; | ||
| 259 | voice_out.played_sample_count = voice_states[0]->played_sample_count; | ||
| 260 | voice_out.voice_dropped = in_params.voice_drop_flag; | ||
| 261 | } else { | 296 | } else { |
| 262 | voice_out.wave_buffer_consumed = 0; | 297 | const auto& state = voice_states[0]; |
| 263 | voice_out.played_sample_count = 0; | 298 | voice_out.wave_buffer_consumed = state->wave_buffer_consumed; |
| 264 | voice_out.voice_dropped = false; | 299 | voice_out.played_sample_count = state->played_sample_count; |
| 300 | voice_out.voice_dropped = state->voice_dropped; | ||
| 265 | } | 301 | } |
| 266 | } | 302 | } |
| 267 | 303 | ||
| @@ -283,7 +319,8 @@ ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { | |||
| 283 | 319 | ||
| 284 | bool ServerVoiceInfo::ShouldSkip() const { | 320 | bool ServerVoiceInfo::ShouldSkip() const { |
| 285 | // TODO(ogniK): Handle unmapped wave buffers or parameters | 321 | // TODO(ogniK): Handle unmapped wave buffers or parameters |
| 286 | return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag; | 322 | return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped || |
| 323 | in_params.voice_drop_flag; | ||
| 287 | } | 324 | } |
| 288 | 325 | ||
| 289 | bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { | 326 | bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { |
| @@ -381,7 +418,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration( | |||
| 381 | void ServerVoiceInfo::FlushWaveBuffers( | 418 | void ServerVoiceInfo::FlushWaveBuffers( |
| 382 | u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, | 419 | u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, |
| 383 | s32 channel_count) { | 420 | s32 channel_count) { |
| 384 | auto wave_head = in_params.wave_bufffer_head; | 421 | auto wave_head = in_params.wave_buffer_head; |
| 385 | 422 | ||
| 386 | for (u8 i = 0; i < flush_count; i++) { | 423 | for (u8 i = 0; i < flush_count; i++) { |
| 387 | in_params.wave_buffer[wave_head].sent_to_dsp = true; | 424 | in_params.wave_buffer[wave_head].sent_to_dsp = true; |
| @@ -401,6 +438,17 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { | |||
| 401 | return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); | 438 | return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); |
| 402 | } | 439 | } |
| 403 | 440 | ||
| 441 | void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state, | ||
| 442 | const ServerWaveBuffer& wave_buffer) { | ||
| 443 | dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; | ||
| 444 | dsp_state.wave_buffer_consumed++; | ||
| 445 | dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; | ||
| 446 | dsp_state.loop_count = 0; | ||
| 447 | if (wave_buffer.end_of_stream) { | ||
| 448 | dsp_state.played_sample_count = 0; | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 404 | VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { | 452 | VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { |
| 405 | for (std::size_t i = 0; i < voice_count; i++) { | 453 | for (std::size_t i = 0; i < voice_count; i++) { |
| 406 | voice_channel_resources.emplace_back(static_cast<s32>(i)); | 454 | voice_channel_resources.emplace_back(static_cast<s32>(i)); |
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h index 70359cadb..e1050897b 100644 --- a/src/audio_core/voice_context.h +++ b/src/audio_core/voice_context.h | |||
| @@ -60,10 +60,12 @@ struct WaveBuffer { | |||
| 60 | u8 is_looping{}; | 60 | u8 is_looping{}; |
| 61 | u8 end_of_stream{}; | 61 | u8 end_of_stream{}; |
| 62 | u8 sent_to_server{}; | 62 | u8 sent_to_server{}; |
| 63 | INSERT_PADDING_BYTES(5); | 63 | INSERT_PADDING_BYTES(1); |
| 64 | s32 loop_count{}; | ||
| 64 | u64 context_address{}; | 65 | u64 context_address{}; |
| 65 | u64 context_size{}; | 66 | u64 context_size{}; |
| 66 | INSERT_PADDING_BYTES(8); | 67 | u32 loop_start_sample{}; |
| 68 | u32 loop_end_sample{}; | ||
| 67 | }; | 69 | }; |
| 68 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); | 70 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); |
| 69 | 71 | ||
| @@ -76,6 +78,9 @@ struct ServerWaveBuffer { | |||
| 76 | bool end_of_stream{}; | 78 | bool end_of_stream{}; |
| 77 | VAddr context_address{}; | 79 | VAddr context_address{}; |
| 78 | std::size_t context_size{}; | 80 | std::size_t context_size{}; |
| 81 | s32 loop_count{}; | ||
| 82 | u32 loop_start_sample{}; | ||
| 83 | u32 loop_end_sample{}; | ||
| 79 | bool sent_to_dsp{true}; | 84 | bool sent_to_dsp{true}; |
| 80 | }; | 85 | }; |
| 81 | 86 | ||
| @@ -108,6 +113,7 @@ struct VoiceState { | |||
| 108 | u32 external_context_size; | 113 | u32 external_context_size; |
| 109 | bool is_external_context_used; | 114 | bool is_external_context_used; |
| 110 | bool voice_dropped; | 115 | bool voice_dropped; |
| 116 | s32 loop_count; | ||
| 111 | }; | 117 | }; |
| 112 | 118 | ||
| 113 | class VoiceChannelResource { | 119 | class VoiceChannelResource { |
| @@ -206,7 +212,7 @@ public: | |||
| 206 | float last_volume{}; | 212 | float last_volume{}; |
| 207 | std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; | 213 | std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; |
| 208 | s32 wave_buffer_count{}; | 214 | s32 wave_buffer_count{}; |
| 209 | s16 wave_bufffer_head{}; | 215 | s16 wave_buffer_head{}; |
| 210 | INSERT_PADDING_BYTES(2); | 216 | INSERT_PADDING_BYTES(2); |
| 211 | BehaviorFlags behavior_flags{}; | 217 | BehaviorFlags behavior_flags{}; |
| 212 | VAddr additional_params_address{}; | 218 | VAddr additional_params_address{}; |
| @@ -252,6 +258,7 @@ public: | |||
| 252 | void FlushWaveBuffers(u8 flush_count, | 258 | void FlushWaveBuffers(u8 flush_count, |
| 253 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, | 259 | std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, |
| 254 | s32 channel_count); | 260 | s32 channel_count); |
| 261 | void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer); | ||
| 255 | 262 | ||
| 256 | private: | 263 | private: |
| 257 | std::vector<s16> stored_samples; | 264 | std::vector<s16> stored_samples; |
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 53b8b7ca0..7c0950bb0 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -345,8 +345,10 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( | |||
| 345 | static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, | 345 | static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, |
| 346 | const Service::FileSystem::FileSystemController& fs_controller) { | 346 | const Service::FileSystem::FileSystemController& fs_controller) { |
| 347 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 347 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 348 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); | ||
| 348 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || | 349 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || |
| 349 | load_dir == nullptr || load_dir->GetSize() <= 0) { | 350 | ((load_dir == nullptr || load_dir->GetSize() <= 0) && |
| 351 | (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) { | ||
| 350 | return; | 352 | return; |
| 351 | } | 353 | } |
| 352 | 354 | ||
| @@ -356,7 +358,10 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 356 | } | 358 | } |
| 357 | 359 | ||
| 358 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 360 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 359 | auto patch_dirs = load_dir->GetSubdirectories(); | 361 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); |
| 362 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { | ||
| 363 | patch_dirs.push_back(sdmc_load_dir); | ||
| 364 | } | ||
| 360 | std::sort(patch_dirs.begin(), patch_dirs.end(), | 365 | std::sort(patch_dirs.begin(), patch_dirs.end(), |
| 361 | [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); | 366 | [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); |
| 362 | 367 | ||
| @@ -402,7 +407,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 402 | } | 407 | } |
| 403 | 408 | ||
| 404 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, | 409 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, |
| 405 | VirtualFile update_raw) const { | 410 | VirtualFile update_raw, bool apply_layeredfs) const { |
| 406 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", | 411 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", |
| 407 | title_id, static_cast<u8>(type)); | 412 | title_id, static_cast<u8>(type)); |
| 408 | 413 | ||
| @@ -442,7 +447,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content | |||
| 442 | } | 447 | } |
| 443 | 448 | ||
| 444 | // LayeredFS | 449 | // LayeredFS |
| 445 | ApplyLayeredFS(romfs, title_id, type, fs_controller); | 450 | if (apply_layeredfs) { |
| 451 | ApplyLayeredFS(romfs, title_id, type, fs_controller); | ||
| 452 | } | ||
| 446 | 453 | ||
| 447 | return romfs; | 454 | return romfs; |
| 448 | } | 455 | } |
| @@ -524,6 +531,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u | |||
| 524 | } | 531 | } |
| 525 | } | 532 | } |
| 526 | 533 | ||
| 534 | // SDMC mod directory (RomFS LayeredFS) | ||
| 535 | const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); | ||
| 536 | if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 && | ||
| 537 | IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) { | ||
| 538 | const auto mod_disabled = | ||
| 539 | std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); | ||
| 540 | out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS"); | ||
| 541 | } | ||
| 542 | |||
| 527 | // DLC | 543 | // DLC |
| 528 | const auto dlc_entries = | 544 | const auto dlc_entries = |
| 529 | content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); | 545 | content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index fb1853035..3be871f35 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -64,7 +64,8 @@ public: | |||
| 64 | // - LayeredFS | 64 | // - LayeredFS |
| 65 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | 65 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, |
| 66 | ContentRecordType type = ContentRecordType::Program, | 66 | ContentRecordType type = ContentRecordType::Program, |
| 67 | VirtualFile update_raw = nullptr) const; | 67 | VirtualFile update_raw = nullptr, |
| 68 | bool apply_layeredfs = true) const; | ||
| 68 | 69 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 70 | // Returns a vector of pairs between patch names and patch versions. |
| 70 | // i.e. Update 3.2.2 will return {"Update", "3.2.2"} | 71 | // i.e. Update 3.2.2 will return {"Update", "3.2.2"} |
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index cb56d8f2d..e5c72cd4d 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp | |||
| @@ -12,23 +12,32 @@ namespace FileSys { | |||
| 12 | 12 | ||
| 13 | constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB | 13 | constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB |
| 14 | 14 | ||
| 15 | SDMCFactory::SDMCFactory(VirtualDir dir_) | 15 | SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_) |
| 16 | : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>( | 16 | : sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)), |
| 17 | GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), | 17 | contents(std::make_unique<RegisteredCache>( |
| 18 | [](const VirtualFile& file, const NcaID& id) { | 18 | GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"), |
| 19 | return NAX{file, id}.GetDecrypted(); | 19 | [](const VirtualFile& file, const NcaID& id) { |
| 20 | })), | 20 | return NAX{file, id}.GetDecrypted(); |
| 21 | })), | ||
| 21 | placeholder(std::make_unique<PlaceholderCache>( | 22 | placeholder(std::make_unique<PlaceholderCache>( |
| 22 | GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {} | 23 | GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {} |
| 23 | 24 | ||
| 24 | SDMCFactory::~SDMCFactory() = default; | 25 | SDMCFactory::~SDMCFactory() = default; |
| 25 | 26 | ||
| 26 | ResultVal<VirtualDir> SDMCFactory::Open() const { | 27 | ResultVal<VirtualDir> SDMCFactory::Open() const { |
| 27 | return MakeResult<VirtualDir>(dir); | 28 | return MakeResult<VirtualDir>(sd_dir); |
| 29 | } | ||
| 30 | |||
| 31 | VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const { | ||
| 32 | // LayeredFS doesn't work on updates and title id-less homebrew | ||
| 33 | if (title_id == 0 || (title_id & 0xFFF) == 0x800) { | ||
| 34 | return nullptr; | ||
| 35 | } | ||
| 36 | return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id)); | ||
| 28 | } | 37 | } |
| 29 | 38 | ||
| 30 | VirtualDir SDMCFactory::GetSDMCContentDirectory() const { | 39 | VirtualDir SDMCFactory::GetSDMCContentDirectory() const { |
| 31 | return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents"); | 40 | return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents"); |
| 32 | } | 41 | } |
| 33 | 42 | ||
| 34 | RegisteredCache* SDMCFactory::GetSDMCContents() const { | 43 | RegisteredCache* SDMCFactory::GetSDMCContents() const { |
| @@ -40,11 +49,11 @@ PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const { | |||
| 40 | } | 49 | } |
| 41 | 50 | ||
| 42 | VirtualDir SDMCFactory::GetImageDirectory() const { | 51 | VirtualDir SDMCFactory::GetImageDirectory() const { |
| 43 | return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album"); | 52 | return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album"); |
| 44 | } | 53 | } |
| 45 | 54 | ||
| 46 | u64 SDMCFactory::GetSDMCFreeSpace() const { | 55 | u64 SDMCFactory::GetSDMCFreeSpace() const { |
| 47 | return GetSDMCTotalSpace() - dir->GetSize(); | 56 | return GetSDMCTotalSpace() - sd_dir->GetSize(); |
| 48 | } | 57 | } |
| 49 | 58 | ||
| 50 | u64 SDMCFactory::GetSDMCTotalSpace() const { | 59 | u64 SDMCFactory::GetSDMCTotalSpace() const { |
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 2bb92ba93..3a3d11f3a 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h | |||
| @@ -16,11 +16,12 @@ class PlaceholderCache; | |||
| 16 | /// File system interface to the SDCard archive | 16 | /// File system interface to the SDCard archive |
| 17 | class SDMCFactory { | 17 | class SDMCFactory { |
| 18 | public: | 18 | public: |
| 19 | explicit SDMCFactory(VirtualDir dir); | 19 | explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_); |
| 20 | ~SDMCFactory(); | 20 | ~SDMCFactory(); |
| 21 | 21 | ||
| 22 | ResultVal<VirtualDir> Open() const; | 22 | ResultVal<VirtualDir> Open() const; |
| 23 | 23 | ||
| 24 | VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const; | ||
| 24 | VirtualDir GetSDMCContentDirectory() const; | 25 | VirtualDir GetSDMCContentDirectory() const; |
| 25 | 26 | ||
| 26 | RegisteredCache* GetSDMCContents() const; | 27 | RegisteredCache* GetSDMCContents() const; |
| @@ -32,7 +33,8 @@ public: | |||
| 32 | u64 GetSDMCTotalSpace() const; | 33 | u64 GetSDMCTotalSpace() const; |
| 33 | 34 | ||
| 34 | private: | 35 | private: |
| 35 | VirtualDir dir; | 36 | VirtualDir sd_dir; |
| 37 | VirtualDir sd_mod_dir; | ||
| 36 | 38 | ||
| 37 | std::unique_ptr<RegisteredCache> contents; | 39 | std::unique_ptr<RegisteredCache> contents; |
| 38 | std::unique_ptr<PlaceholderCache> placeholder; | 40 | std::unique_ptr<PlaceholderCache> placeholder; |
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 800feba6e..7583d68b2 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -96,7 +96,7 @@ private: | |||
| 96 | void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { | 96 | void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { |
| 97 | LOG_DEBUG(Service_Audio, "(STUBBED) called"); | 97 | LOG_DEBUG(Service_Audio, "(STUBBED) called"); |
| 98 | 98 | ||
| 99 | std::vector<u8> output_params(ctx.GetWriteBufferSize()); | 99 | std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0); |
| 100 | auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); | 100 | auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); |
| 101 | 101 | ||
| 102 | if (result.IsSuccess()) { | 102 | if (result.IsSuccess()) { |
| @@ -110,17 +110,19 @@ private: | |||
| 110 | void Start(Kernel::HLERequestContext& ctx) { | 110 | void Start(Kernel::HLERequestContext& ctx) { |
| 111 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 111 | LOG_WARNING(Service_Audio, "(STUBBED) called"); |
| 112 | 112 | ||
| 113 | IPC::ResponseBuilder rb{ctx, 2}; | 113 | const auto result = renderer->Start(); |
| 114 | 114 | ||
| 115 | rb.Push(ResultSuccess); | 115 | IPC::ResponseBuilder rb{ctx, 2}; |
| 116 | rb.Push(result); | ||
| 116 | } | 117 | } |
| 117 | 118 | ||
| 118 | void Stop(Kernel::HLERequestContext& ctx) { | 119 | void Stop(Kernel::HLERequestContext& ctx) { |
| 119 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 120 | LOG_WARNING(Service_Audio, "(STUBBED) called"); |
| 120 | 121 | ||
| 121 | IPC::ResponseBuilder rb{ctx, 2}; | 122 | const auto result = renderer->Stop(); |
| 122 | 123 | ||
| 123 | rb.Push(ResultSuccess); | 124 | IPC::ResponseBuilder rb{ctx, 2}; |
| 125 | rb.Push(result); | ||
| 124 | } | 126 | } |
| 125 | 127 | ||
| 126 | void QuerySystemEvent(Kernel::HLERequestContext& ctx) { | 128 | void QuerySystemEvent(Kernel::HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 3c16fe6c7..4a9b13e45 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -703,6 +703,16 @@ FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id) | |||
| 703 | return bis_factory->GetModificationLoadRoot(title_id); | 703 | return bis_factory->GetModificationLoadRoot(title_id); |
| 704 | } | 704 | } |
| 705 | 705 | ||
| 706 | FileSys::VirtualDir FileSystemController::GetSDMCModificationLoadRoot(u64 title_id) const { | ||
| 707 | LOG_TRACE(Service_FS, "Opening SDMC mod load root for tid={:016X}", title_id); | ||
| 708 | |||
| 709 | if (sdmc_factory == nullptr) { | ||
| 710 | return nullptr; | ||
| 711 | } | ||
| 712 | |||
| 713 | return sdmc_factory->GetSDMCModificationLoadRoot(title_id); | ||
| 714 | } | ||
| 715 | |||
| 706 | FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const { | 716 | FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const { |
| 707 | LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); | 717 | LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); |
| 708 | 718 | ||
| @@ -733,20 +743,23 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove | |||
| 733 | } | 743 | } |
| 734 | 744 | ||
| 735 | using YuzuPath = Common::FS::YuzuPath; | 745 | using YuzuPath = Common::FS::YuzuPath; |
| 746 | const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir); | ||
| 747 | const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents"; | ||
| 736 | const auto rw_mode = FileSys::Mode::ReadWrite; | 748 | const auto rw_mode = FileSys::Mode::ReadWrite; |
| 737 | 749 | ||
| 738 | auto nand_directory = | 750 | auto nand_directory = |
| 739 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); | 751 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); |
| 740 | auto sd_directory = | 752 | auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode); |
| 741 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode); | ||
| 742 | auto load_directory = | 753 | auto load_directory = |
| 743 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); | 754 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); |
| 755 | auto sd_load_directory = | ||
| 756 | vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read); | ||
| 744 | auto dump_directory = | 757 | auto dump_directory = |
| 745 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); | 758 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); |
| 746 | 759 | ||
| 747 | if (bis_factory == nullptr) { | 760 | if (bis_factory == nullptr) { |
| 748 | bis_factory = | 761 | bis_factory = std::make_unique<FileSys::BISFactory>( |
| 749 | std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); | 762 | nand_directory, std::move(load_directory), std::move(dump_directory)); |
| 750 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, | 763 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, |
| 751 | bis_factory->GetSystemNANDContents()); | 764 | bis_factory->GetSystemNANDContents()); |
| 752 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, | 765 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, |
| @@ -759,7 +772,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove | |||
| 759 | } | 772 | } |
| 760 | 773 | ||
| 761 | if (sdmc_factory == nullptr) { | 774 | if (sdmc_factory == nullptr) { |
| 762 | sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); | 775 | sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory), |
| 776 | std::move(sd_load_directory)); | ||
| 763 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, | 777 | system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, |
| 764 | sdmc_factory->GetSDMCContents()); | 778 | sdmc_factory->GetSDMCContents()); |
| 765 | } | 779 | } |
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index b6b1b9220..d387af3cb 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h | |||
| @@ -115,6 +115,7 @@ public: | |||
| 115 | FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const; | 115 | FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const; |
| 116 | FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const; | 116 | FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const; |
| 117 | 117 | ||
| 118 | FileSys::VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const; | ||
| 118 | FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; | 119 | FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; |
| 119 | FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; | 120 | FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; |
| 120 | 121 | ||
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp index 100138d11..2fafd077f 100755 --- a/src/input_common/analog_from_button.cpp +++ b/src/input_common/analog_from_button.cpp | |||
| @@ -27,6 +27,7 @@ public: | |||
| 27 | down->SetCallback(callbacks); | 27 | down->SetCallback(callbacks); |
| 28 | left->SetCallback(callbacks); | 28 | left->SetCallback(callbacks); |
| 29 | right->SetCallback(callbacks); | 29 | right->SetCallback(callbacks); |
| 30 | modifier->SetCallback(callbacks); | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | bool IsAngleGreater(float old_angle, float new_angle) const { | 33 | bool IsAngleGreater(float old_angle, float new_angle) const { |
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 2208e1922..c9cff7450 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt | |||
| @@ -18,7 +18,10 @@ set(SHADER_FILES | |||
| 18 | vulkan_uint8.comp | 18 | vulkan_uint8.comp |
| 19 | ) | 19 | ) |
| 20 | 20 | ||
| 21 | find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED) | 21 | find_program(GLSLANGVALIDATOR "glslangValidator") |
| 22 | if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND") | ||
| 23 | message(FATAL_ERROR "Required program `glslangValidator` not found.") | ||
| 24 | endif() | ||
| 22 | 25 | ||
| 23 | set(GLSL_FLAGS "") | 26 | set(GLSL_FLAGS "") |
| 24 | set(QUIET_FLAG "--quiet") | 27 | set(QUIET_FLAG "--quiet") |
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 320ee8d30..63d8ad42a 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h | |||
| @@ -42,6 +42,8 @@ public: | |||
| 42 | 42 | ||
| 43 | [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; | 43 | [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; |
| 44 | 44 | ||
| 45 | [[nodiscard]] virtual std::string GetDeviceVendor() const = 0; | ||
| 46 | |||
| 45 | // Getter/setter functions: | 47 | // Getter/setter functions: |
| 46 | // ------------------------ | 48 | // ------------------------ |
| 47 | 49 | ||
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 3f4532ca7..3b00614e7 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp | |||
| @@ -202,13 +202,13 @@ Device::Device() { | |||
| 202 | LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available"); | 202 | LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available"); |
| 203 | throw std::runtime_error{"Insufficient version"}; | 203 | throw std::runtime_error{"Insufficient version"}; |
| 204 | } | 204 | } |
| 205 | const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); | 205 | vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); |
| 206 | const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); | 206 | const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); |
| 207 | const std::vector extensions = GetExtensions(); | 207 | const std::vector extensions = GetExtensions(); |
| 208 | 208 | ||
| 209 | const bool is_nvidia = vendor == "NVIDIA Corporation"; | 209 | const bool is_nvidia = vendor_name == "NVIDIA Corporation"; |
| 210 | const bool is_amd = vendor == "ATI Technologies Inc."; | 210 | const bool is_amd = vendor_name == "ATI Technologies Inc."; |
| 211 | const bool is_intel = vendor == "Intel"; | 211 | const bool is_intel = vendor_name == "Intel"; |
| 212 | 212 | ||
| 213 | #ifdef __unix__ | 213 | #ifdef __unix__ |
| 214 | const bool is_linux = true; | 214 | const bool is_linux = true; |
| @@ -275,6 +275,56 @@ Device::Device() { | |||
| 275 | } | 275 | } |
| 276 | } | 276 | } |
| 277 | 277 | ||
| 278 | std::string Device::GetVendorName() const { | ||
| 279 | if (vendor_name == "NVIDIA Corporation") { | ||
| 280 | return "NVIDIA"; | ||
| 281 | } | ||
| 282 | if (vendor_name == "ATI Technologies Inc.") { | ||
| 283 | return "AMD"; | ||
| 284 | } | ||
| 285 | if (vendor_name == "Intel") { | ||
| 286 | // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris. | ||
| 287 | // Simply return `INTEL` for those as well as the Windows driver. | ||
| 288 | return "INTEL"; | ||
| 289 | } | ||
| 290 | if (vendor_name == "Intel Open Source Technology Center") { | ||
| 291 | return "I965"; | ||
| 292 | } | ||
| 293 | if (vendor_name == "Mesa Project") { | ||
| 294 | return "I915"; | ||
| 295 | } | ||
| 296 | if (vendor_name == "Mesa/X.org") { | ||
| 297 | // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return | ||
| 298 | // MESA instead of one of those driver names. | ||
| 299 | return "MESA"; | ||
| 300 | } | ||
| 301 | if (vendor_name == "AMD") { | ||
| 302 | return "RADEONSI"; | ||
| 303 | } | ||
| 304 | if (vendor_name == "nouveau") { | ||
| 305 | return "NOUVEAU"; | ||
| 306 | } | ||
| 307 | if (vendor_name == "X.Org") { | ||
| 308 | return "R600"; | ||
| 309 | } | ||
| 310 | if (vendor_name == "Collabora Ltd") { | ||
| 311 | return "ZINK"; | ||
| 312 | } | ||
| 313 | if (vendor_name == "Intel Corporation") { | ||
| 314 | return "OPENSWR"; | ||
| 315 | } | ||
| 316 | if (vendor_name == "Microsoft Corporation") { | ||
| 317 | return "D3D12"; | ||
| 318 | } | ||
| 319 | if (vendor_name == "NVIDIA") { | ||
| 320 | // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default | ||
| 321 | // strategy would have returned `NVIDIA` here for this driver, the same result as the | ||
| 322 | // proprietary driver. | ||
| 323 | return "TEGRA"; | ||
| 324 | } | ||
| 325 | return vendor_name; | ||
| 326 | } | ||
| 327 | |||
| 278 | Device::Device(std::nullptr_t) { | 328 | Device::Device(std::nullptr_t) { |
| 279 | max_uniform_buffers.fill(std::numeric_limits<u32>::max()); | 329 | max_uniform_buffers.fill(std::numeric_limits<u32>::max()); |
| 280 | uniform_buffer_alignment = 4; | 330 | uniform_buffer_alignment = 4; |
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index f24bd0c7b..2c2b13767 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h | |||
| @@ -22,6 +22,8 @@ public: | |||
| 22 | explicit Device(); | 22 | explicit Device(); |
| 23 | explicit Device(std::nullptr_t); | 23 | explicit Device(std::nullptr_t); |
| 24 | 24 | ||
| 25 | [[nodiscard]] std::string GetVendorName() const; | ||
| 26 | |||
| 25 | u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept { | 27 | u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept { |
| 26 | return max_uniform_buffers[static_cast<std::size_t>(shader_type)]; | 28 | return max_uniform_buffers[static_cast<std::size_t>(shader_type)]; |
| 27 | } | 29 | } |
| @@ -130,6 +132,7 @@ private: | |||
| 130 | static bool TestVariableAoffi(); | 132 | static bool TestVariableAoffi(); |
| 131 | static bool TestPreciseBug(); | 133 | static bool TestPreciseBug(); |
| 132 | 134 | ||
| 135 | std::string vendor_name; | ||
| 133 | std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{}; | 136 | std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{}; |
| 134 | std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{}; | 137 | std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{}; |
| 135 | size_t uniform_buffer_alignment{}; | 138 | size_t uniform_buffer_alignment{}; |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 23948feed..a2c1599f7 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -341,6 +341,20 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 | |||
| 341 | [[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset, | 341 | [[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset, |
| 342 | VideoCommon::SubresourceLayers subresource, GLenum target) { | 342 | VideoCommon::SubresourceLayers subresource, GLenum target) { |
| 343 | switch (target) { | 343 | switch (target) { |
| 344 | case GL_TEXTURE_1D: | ||
| 345 | return CopyOrigin{ | ||
| 346 | .level = static_cast<GLint>(subresource.base_level), | ||
| 347 | .x = static_cast<GLint>(offset.x), | ||
| 348 | .y = static_cast<GLint>(0), | ||
| 349 | .z = static_cast<GLint>(0), | ||
| 350 | }; | ||
| 351 | case GL_TEXTURE_1D_ARRAY: | ||
| 352 | return CopyOrigin{ | ||
| 353 | .level = static_cast<GLint>(subresource.base_level), | ||
| 354 | .x = static_cast<GLint>(offset.x), | ||
| 355 | .y = static_cast<GLint>(0), | ||
| 356 | .z = static_cast<GLint>(subresource.base_layer), | ||
| 357 | }; | ||
| 344 | case GL_TEXTURE_2D_ARRAY: | 358 | case GL_TEXTURE_2D_ARRAY: |
| 345 | case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: | 359 | case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: |
| 346 | return CopyOrigin{ | 360 | return CopyOrigin{ |
| @@ -366,6 +380,18 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 | |||
| 366 | VideoCommon::SubresourceLayers dst_subresource, | 380 | VideoCommon::SubresourceLayers dst_subresource, |
| 367 | GLenum target) { | 381 | GLenum target) { |
| 368 | switch (target) { | 382 | switch (target) { |
| 383 | case GL_TEXTURE_1D: | ||
| 384 | return CopyRegion{ | ||
| 385 | .width = static_cast<GLsizei>(extent.width), | ||
| 386 | .height = static_cast<GLsizei>(1), | ||
| 387 | .depth = static_cast<GLsizei>(1), | ||
| 388 | }; | ||
| 389 | case GL_TEXTURE_1D_ARRAY: | ||
| 390 | return CopyRegion{ | ||
| 391 | .width = static_cast<GLsizei>(extent.width), | ||
| 392 | .height = static_cast<GLsizei>(1), | ||
| 393 | .depth = static_cast<GLsizei>(dst_subresource.num_layers), | ||
| 394 | }; | ||
| 369 | case GL_TEXTURE_2D_ARRAY: | 395 | case GL_TEXTURE_2D_ARRAY: |
| 370 | case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: | 396 | case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: |
| 371 | return CopyRegion{ | 397 | return CopyRegion{ |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index cc19a110f..0b66f8332 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h | |||
| @@ -70,6 +70,10 @@ public: | |||
| 70 | return &rasterizer; | 70 | return &rasterizer; |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | [[nodiscard]] std::string GetDeviceVendor() const override { | ||
| 74 | return device.GetVendorName(); | ||
| 75 | } | ||
| 76 | |||
| 73 | private: | 77 | private: |
| 74 | /// Initializes the OpenGL state and creates persistent objects. | 78 | /// Initializes the OpenGL state and creates persistent objects. |
| 75 | void InitOpenGLObjects(); | 79 | void InitOpenGLObjects(); |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 72071316c..d7d17e110 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h | |||
| @@ -47,6 +47,10 @@ public: | |||
| 47 | return &rasterizer; | 47 | return &rasterizer; |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | [[nodiscard]] std::string GetDeviceVendor() const override { | ||
| 51 | return device.GetDriverName(); | ||
| 52 | } | ||
| 53 | |||
| 50 | private: | 54 | private: |
| 51 | void Report() const; | 55 | void Report() const; |
| 52 | 56 | ||
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c7cfd02b6..d8dbd3824 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -1057,9 +1057,6 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA | |||
| 1057 | std::vector<ImageId> right_aliased_ids; | 1057 | std::vector<ImageId> right_aliased_ids; |
| 1058 | std::vector<ImageId> bad_overlap_ids; | 1058 | std::vector<ImageId> bad_overlap_ids; |
| 1059 | ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { | 1059 | ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { |
| 1060 | if (info.type != overlap.info.type) { | ||
| 1061 | return; | ||
| 1062 | } | ||
| 1063 | if (info.type == ImageType::Linear) { | 1060 | if (info.type == ImageType::Linear) { |
| 1064 | if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) { | 1061 | if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) { |
| 1065 | // Alias linear images with the same pitch | 1062 | // Alias linear images with the same pitch |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 23814afd2..f214510da 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -532,6 +532,27 @@ bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags want | |||
| 532 | return (supported_usage & wanted_usage) == wanted_usage; | 532 | return (supported_usage & wanted_usage) == wanted_usage; |
| 533 | } | 533 | } |
| 534 | 534 | ||
| 535 | std::string Device::GetDriverName() const { | ||
| 536 | switch (driver_id) { | ||
| 537 | case VK_DRIVER_ID_AMD_PROPRIETARY: | ||
| 538 | return "AMD"; | ||
| 539 | case VK_DRIVER_ID_AMD_OPEN_SOURCE: | ||
| 540 | return "AMDVLK"; | ||
| 541 | case VK_DRIVER_ID_MESA_RADV: | ||
| 542 | return "RADV"; | ||
| 543 | case VK_DRIVER_ID_NVIDIA_PROPRIETARY: | ||
| 544 | return "NVIDIA"; | ||
| 545 | case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS: | ||
| 546 | return "INTEL"; | ||
| 547 | case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA: | ||
| 548 | return "ANV"; | ||
| 549 | case VK_DRIVER_ID_MESA_LLVMPIPE: | ||
| 550 | return "LAVAPIPE"; | ||
| 551 | default: | ||
| 552 | return vendor_name; | ||
| 553 | } | ||
| 554 | } | ||
| 555 | |||
| 535 | void Device::CheckSuitability(bool requires_swapchain) const { | 556 | void Device::CheckSuitability(bool requires_swapchain) const { |
| 536 | std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions; | 557 | std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions; |
| 537 | bool has_swapchain = false; | 558 | bool has_swapchain = false; |
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 88b298196..96c0f8c60 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h | |||
| @@ -45,6 +45,9 @@ public: | |||
| 45 | /// Reports a shader to Nsight Aftermath. | 45 | /// Reports a shader to Nsight Aftermath. |
| 46 | void SaveShader(const std::vector<u32>& spirv) const; | 46 | void SaveShader(const std::vector<u32>& spirv) const; |
| 47 | 47 | ||
| 48 | /// Returns the name of the VkDriverId reported from Vulkan. | ||
| 49 | std::string GetDriverName() const; | ||
| 50 | |||
| 48 | /// Returns the dispatch loader with direct function pointers of the device. | 51 | /// Returns the dispatch loader with direct function pointers of the device. |
| 49 | const vk::DeviceDispatch& GetDispatchLoader() const { | 52 | const vk::DeviceDispatch& GetDispatchLoader() const { |
| 50 | return dld; | 53 | return dld; |
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index efdc6aa50..7a6f84d96 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp | |||
| @@ -143,24 +143,25 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) { | |||
| 143 | } | 143 | } |
| 144 | 144 | ||
| 145 | void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { | 145 | void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { |
| 146 | MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); | 146 | MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); |
| 147 | ev->accept(); | 147 | ev->accept(); |
| 148 | } | 148 | } |
| 149 | 149 | ||
| 150 | void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { | 150 | void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { |
| 151 | MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); | 151 | MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); |
| 152 | MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); | 152 | MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); |
| 153 | ev->accept(); | 153 | ev->accept(); |
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { | 156 | void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { |
| 157 | MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); | 157 | MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); |
| 158 | MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); | 158 | MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); |
| 159 | ev->accept(); | 159 | ev->accept(); |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { | 162 | void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { |
| 163 | MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120); | 163 | MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, |
| 164 | ev->angleDelta().y() / 120); | ||
| 164 | ev->accept(); | 165 | ev->accept(); |
| 165 | } | 166 | } |
| 166 | 167 | ||
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index da956c99b..e44907be8 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -521,7 +521,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 521 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); | 521 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); |
| 522 | remove_menu->addSeparator(); | 522 | remove_menu->addSeparator(); |
| 523 | QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); | 523 | QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); |
| 524 | QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); | 524 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); |
| 525 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); | ||
| 526 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); | ||
| 525 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | 527 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |
| 526 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 528 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 527 | context_menu.addSeparator(); | 529 | context_menu.addSeparator(); |
| @@ -570,8 +572,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 570 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { | 572 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { |
| 571 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); | 573 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); |
| 572 | }); | 574 | }); |
| 573 | connect(dump_romfs, &QAction::triggered, | 575 | connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { |
| 574 | [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); | 576 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); |
| 577 | }); | ||
| 578 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { | ||
| 579 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); | ||
| 580 | }); | ||
| 575 | connect(copy_tid, &QAction::triggered, | 581 | connect(copy_tid, &QAction::triggered, |
| 576 | [this, program_id]() { emit CopyTIDRequested(program_id); }); | 582 | [this, program_id]() { emit CopyTIDRequested(program_id); }); |
| 577 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | 583 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index b630e34ff..50402da51 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -45,6 +45,11 @@ enum class GameListRemoveTarget { | |||
| 45 | CustomConfiguration, | 45 | CustomConfiguration, |
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | enum class DumpRomFSTarget { | ||
| 49 | Normal, | ||
| 50 | SDMC, | ||
| 51 | }; | ||
| 52 | |||
| 48 | enum class InstalledEntryType { | 53 | enum class InstalledEntryType { |
| 49 | Game, | 54 | Game, |
| 50 | Update, | 55 | Update, |
| @@ -92,7 +97,7 @@ signals: | |||
| 92 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); | 97 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); |
| 93 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 98 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 94 | const std::string& game_path); | 99 | const std::string& game_path); |
| 95 | void DumpRomFSRequested(u64 program_id, const std::string& game_path); | 100 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 96 | void CopyTIDRequested(u64 program_id); | 101 | void CopyTIDRequested(u64 program_id); |
| 97 | void NavigateToGamedbEntryRequested(u64 program_id, | 102 | void NavigateToGamedbEntryRequested(u64 program_id, |
| 98 | const CompatibilityList& compatibility_list); | 103 | const CompatibilityList& compatibility_list); |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 6f5b2f6d6..f462cd072 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -104,6 +104,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 104 | #include "input_common/main.h" | 104 | #include "input_common/main.h" |
| 105 | #include "util/overlay_dialog.h" | 105 | #include "util/overlay_dialog.h" |
| 106 | #include "video_core/gpu.h" | 106 | #include "video_core/gpu.h" |
| 107 | #include "video_core/renderer_base.h" | ||
| 107 | #include "video_core/shader_notify.h" | 108 | #include "video_core/shader_notify.h" |
| 108 | #include "yuzu/about_dialog.h" | 109 | #include "yuzu/about_dialog.h" |
| 109 | #include "yuzu/bootmanager.h" | 110 | #include "yuzu/bootmanager.h" |
| @@ -1422,8 +1423,12 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S | |||
| 1422 | title_name = Common::FS::PathToUTF8String( | 1423 | title_name = Common::FS::PathToUTF8String( |
| 1423 | std::filesystem::path{filename.toStdU16String()}.filename()); | 1424 | std::filesystem::path{filename.toStdU16String()}.filename()); |
| 1424 | } | 1425 | } |
| 1426 | const bool is_64bit = system.Kernel().CurrentProcess()->Is64BitProcess(); | ||
| 1427 | const auto instruction_set_suffix = is_64bit ? " (64-bit)" : " (32-bit)"; | ||
| 1428 | title_name += instruction_set_suffix; | ||
| 1425 | LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); | 1429 | LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); |
| 1426 | UpdateWindowTitle(title_name, title_version); | 1430 | const auto gpu_vendor = system.GPU().Renderer().GetDeviceVendor(); |
| 1431 | UpdateWindowTitle(title_name, title_version, gpu_vendor); | ||
| 1427 | 1432 | ||
| 1428 | loading_screen->Prepare(system.GetAppLoader()); | 1433 | loading_screen->Prepare(system.GetAppLoader()); |
| 1429 | loading_screen->show(); | 1434 | loading_screen->show(); |
| @@ -1877,7 +1882,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g | |||
| 1877 | } | 1882 | } |
| 1878 | } | 1883 | } |
| 1879 | 1884 | ||
| 1880 | void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { | 1885 | void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, |
| 1886 | DumpRomFSTarget target) { | ||
| 1881 | const auto failed = [this] { | 1887 | const auto failed = [this] { |
| 1882 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | 1888 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), |
| 1883 | tr("There was an error copying the RomFS files or the user " | 1889 | tr("There was an error copying the RomFS files or the user " |
| @@ -1905,7 +1911,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 1905 | return; | 1911 | return; |
| 1906 | } | 1912 | } |
| 1907 | 1913 | ||
| 1908 | const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir); | 1914 | const auto dump_dir = |
| 1915 | target == DumpRomFSTarget::Normal | ||
| 1916 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) | ||
| 1917 | : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; | ||
| 1909 | const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); | 1918 | const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); |
| 1910 | 1919 | ||
| 1911 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); | 1920 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); |
| @@ -1915,7 +1924,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 1915 | if (*romfs_title_id == program_id) { | 1924 | if (*romfs_title_id == program_id) { |
| 1916 | const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); | 1925 | const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); |
| 1917 | const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; | 1926 | const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; |
| 1918 | romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program); | 1927 | romfs = |
| 1928 | pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); | ||
| 1919 | } else { | 1929 | } else { |
| 1920 | romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); | 1930 | romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); |
| 1921 | } | 1931 | } |
| @@ -2852,8 +2862,8 @@ void GMainWindow::MigrateConfigFiles() { | |||
| 2852 | } | 2862 | } |
| 2853 | } | 2863 | } |
| 2854 | 2864 | ||
| 2855 | void GMainWindow::UpdateWindowTitle(const std::string& title_name, | 2865 | void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version, |
| 2856 | const std::string& title_version) { | 2866 | std::string_view gpu_vendor) { |
| 2857 | const auto branch_name = std::string(Common::g_scm_branch); | 2867 | const auto branch_name = std::string(Common::g_scm_branch); |
| 2858 | const auto description = std::string(Common::g_scm_desc); | 2868 | const auto description = std::string(Common::g_scm_desc); |
| 2859 | const auto build_id = std::string(Common::g_build_id); | 2869 | const auto build_id = std::string(Common::g_build_id); |
| @@ -2866,7 +2876,8 @@ void GMainWindow::UpdateWindowTitle(const std::string& title_name, | |||
| 2866 | if (title_name.empty()) { | 2876 | if (title_name.empty()) { |
| 2867 | setWindowTitle(QString::fromStdString(window_title)); | 2877 | setWindowTitle(QString::fromStdString(window_title)); |
| 2868 | } else { | 2878 | } else { |
| 2869 | const auto run_title = fmt::format("{} | {} | {}", window_title, title_name, title_version); | 2879 | const auto run_title = |
| 2880 | fmt::format("{} | {} | {} | {}", window_title, title_name, title_version, gpu_vendor); | ||
| 2870 | setWindowTitle(QString::fromStdString(run_title)); | 2881 | setWindowTitle(QString::fromStdString(run_title)); |
| 2871 | } | 2882 | } |
| 2872 | } | 2883 | } |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 11f152cbe..45c8310e1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -34,6 +34,7 @@ class QProgressDialog; | |||
| 34 | class WaitTreeWidget; | 34 | class WaitTreeWidget; |
| 35 | enum class GameListOpenTarget; | 35 | enum class GameListOpenTarget; |
| 36 | enum class GameListRemoveTarget; | 36 | enum class GameListRemoveTarget; |
| 37 | enum class DumpRomFSTarget; | ||
| 37 | enum class InstalledEntryType; | 38 | enum class InstalledEntryType; |
| 38 | class GameListPlaceholder; | 39 | class GameListPlaceholder; |
| 39 | 40 | ||
| @@ -244,7 +245,7 @@ private slots: | |||
| 244 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); | 245 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); |
| 245 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 246 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 246 | const std::string& game_path); | 247 | const std::string& game_path); |
| 247 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); | 248 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 248 | void OnGameListCopyTID(u64 program_id); | 249 | void OnGameListCopyTID(u64 program_id); |
| 249 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 250 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 250 | const CompatibilityList& compatibility_list); | 251 | const CompatibilityList& compatibility_list); |
| @@ -287,8 +288,8 @@ private: | |||
| 287 | InstallResult InstallNSPXCI(const QString& filename); | 288 | InstallResult InstallNSPXCI(const QString& filename); |
| 288 | InstallResult InstallNCA(const QString& filename); | 289 | InstallResult InstallNCA(const QString& filename); |
| 289 | void MigrateConfigFiles(); | 290 | void MigrateConfigFiles(); |
| 290 | void UpdateWindowTitle(const std::string& title_name = {}, | 291 | void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, |
| 291 | const std::string& title_version = {}); | 292 | std::string_view gpu_vendor = {}); |
| 292 | void UpdateStatusBar(); | 293 | void UpdateStatusBar(); |
| 293 | void UpdateStatusButtons(); | 294 | void UpdateStatusButtons(); |
| 294 | void UpdateUISettings(); | 295 | void UpdateUISettings(); |