diff --git a/UI/AdhocServerScreen.cpp b/UI/AdhocServerScreen.cpp index 040d77d9d0a4..6bbf492d05b7 100644 --- a/UI/AdhocServerScreen.cpp +++ b/UI/AdhocServerScreen.cpp @@ -726,7 +726,8 @@ void AdhocServerScreen::CreatePopupContents(UI::ViewGroup *parent) { CollapsibleSection *publicSection = innerView->Add(new CollapsibleSection(n->T("Public server list"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); for (const auto &entry : entries) { - if (entry.hidden) + // Show even hidden entries, as long as they are chosen currently. + if (entry.hidden && entry.host != g_Config.sProAdhocServer) continue; AddButtonFromEntry(publicSection, entry, false); } diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp index 9118f55202b0..48de6f9bdb70 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperToolsScreen.cpp @@ -213,7 +213,7 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) { #endif #if PPSSPP_PLATFORM(IOS) - list->Add(new NoticeView(NoticeLevel::WARN, ms->T("Moving the memstick directory is NOT recommended on iOS"), "")); + list->Add(new NoticeView(NoticeLevel::WARN, ms->T("Moving the memstick directory is NOT recommended on iOS"), ""))->SetWrapText(true); list->Add(new Choice(sy->T("Set Memory Stick folder")))->OnClick.Add( [=](UI::EventParams &) { SetMemStickDirDarwin(GetRequesterToken()); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 4e173331223e..bf38d479a97a 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1851,7 +1851,12 @@ ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) { void EmuScreen::runImDebugger() { if (!lastImguiEnabled_ && g_Config.bShowImDebugger) { +#if !defined(MOBILE_DEVICE) + // On mobile devices (specifically iOS) we don't want to pop the keyboard + // on activating imgui. Instead, we should do it when a text edit field in imgui gets focus, + // although we'll still have ugly overlap problems. System_NotifyUIEvent(UIEventNotification::TEXT_GOTFOCUS); +#endif VERBOSE_LOG(Log::System, "activating keyboard"); } else if (lastImguiEnabled_ && !g_Config.bShowImDebugger) { System_NotifyUIEvent(UIEventNotification::TEXT_LOSTFOCUS); diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index ffde4e5f707c..2c30cae16418 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -150,245 +150,269 @@ void WASAPIContext::ClearErrorString() { errorString_.clear(); } -bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode latencyMode, bool *revertedToDefault) { - Stop(); - - *revertedToDefault = false; - - ComPtr device; - if (uniqueId.empty()) { - // Use the default device. - HRESULT hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); - if (FAILED(hr)) { - SetErrorString("Failed to get the default endpoint", hr); - return false; - } - } else { - // Use whatever device. - std::wstring wId = ConvertUTF8ToWString(uniqueId); - HRESULT hr = enumerator_->GetDevice(wId.c_str(), &device); - if (FAILED(hr)) { - // Fallback to default device - INFO_LOG(Log::Audio, "Falling back to default device...\n"); - *revertedToDefault = true; - hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); - if (FAILED(hr)) { - SetErrorString("Failed to fallback", hr); - return false; - } - } - } - - AudioDeviceDesc desc{}; - GetDeviceDesc(device.Get(), &desc); - INFO_LOG(Log::Audio, "Activating audio device: %s : %s", desc.name.c_str(), desc.uniqueId.c_str()); - - { - std::lock_guard guard(deviceLock_); - curDeviceId_ = desc.uniqueId; - curDeviceName_ = desc.name; - } - +bool WASAPIContext::TryInitAudioClient3(IMMDevice *device, LatencyMode latencyMode) { HRESULT hr = E_FAIL; // Try IAudioClient3 first if not in "safe" mode. It's probably safe anyway, but still, let's use the legacy client as a safe fallback option. if (latencyMode != LatencyMode::Safe) { hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, nullptr, (void**)&audioClient3_); + } else { + // Proceed to AudioClient. + INFO_LOG(Log::Audio, "LatencyMode::Safe is set, skipping AudioClient3 and going directly to AudioClient"); + return false; } - // Get rid of any old tempBuf_. - tempBuf_.reset(); + if (!SUCCEEDED(hr)) { + audioClient3_.Reset(); + return false; + } - if (SUCCEEDED(hr)) { - hr = audioClient3_->GetMixFormat(&format_); + hr = audioClient3_->GetMixFormat(&format_); + if (FAILED(hr)) { + audioClient3_.Reset(); + SetErrorString("AudioClient3 GetMixFormat failed", hr); + return false; + } + curSamplesPerSec_ = format_->nSamplesPerSec; + + // We only use AudioClient3 if we got the format we wanted (stereo float). + if (format_->nChannels != 2 || Classify(format_) != AudioFormat::Float) { + // Let's fall back to the old path. The docs seem to be wrong, if you try to create an + // AudioClient3 with low latency audio with AUTOCONVERTPCM, you get the error 0x88890021. + INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", format_->nChannels); + audioClient3_.Reset(); + // Free the format before falling through - AudioClient will allocate a new one + CoTaskMemFree(format_); + format_ = nullptr; + return false; + } else { + hr = audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames_, &fundamentalPeriodFrames_, &minPeriodFrames_, &maxPeriodFrames_); if (FAILED(hr)) { audioClient3_.Reset(); - SetErrorString("AudioClient3 GetMixFormat failed", hr); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetSharedModeEnginePeriod failed", hr); return false; } - curSamplesPerSec_ = format_->nSamplesPerSec; - // We only use AudioClient3 if we got the format we wanted (stereo float). - if (format_->nChannels != 2 || Classify(format_) != AudioFormat::Float) { - // Let's fall back to the old path. The docs seem to be wrong, if you try to create an - // AudioClient3 with low latency audio with AUTOCONVERTPCM, you get the error 0x88890021. - INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", format_->nChannels); + INFO_LOG(Log::Audio, "AudioClient3: default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames_, (int)fundamentalPeriodFrames_, (int)minPeriodFrames_, (int)maxPeriodFrames_); + INFO_LOG(Log::Audio, "initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames_, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames_, format_->nSamplesPerSec)); + + hr = audioClient3_->InitializeSharedAudioStream( + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + minPeriodFrames_, + format_, + nullptr + ); + if (FAILED(hr)) { + WARN_LOG(Log::Audio, "Error initializing AudioClient3 shared audio stream: %08lx", hr); audioClient3_.Reset(); - // Free the format before falling through - AudioClient will allocate a new one CoTaskMemFree(format_); format_ = nullptr; - // Fall through to AudioClient creation below. - } else { - hr = audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames_, &fundamentalPeriodFrames_, &minPeriodFrames_, &maxPeriodFrames_); - if (FAILED(hr)) { - audioClient3_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient3 GetSharedModeEnginePeriod failed", hr); - return false; - } - - INFO_LOG(Log::Audio, "AudioClient3: default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames_, (int)fundamentalPeriodFrames_, (int)minPeriodFrames_, (int)maxPeriodFrames_); - INFO_LOG(Log::Audio, "initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames_, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames_, format_->nSamplesPerSec)); - - audioEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); - HRESULT result = audioClient3_->InitializeSharedAudioStream( - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - minPeriodFrames_, - format_, - nullptr - ); - if (FAILED(result)) { - WARN_LOG(Log::Audio, "Error initializing AudioClient3 shared audio stream: %08lx", result); - audioClient3_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient3 init failed", hr); - return false; - } - actualPeriodFrames_ = minPeriodFrames_; - - hr = audioClient3_->GetBufferSize(&reportedBufferSize_); - if (FAILED(hr)) { - audioClient3_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient3 GetBufferSize failed", hr); - return false; - } - - hr = audioClient3_->SetEventHandle(audioEvent_); - if (FAILED(hr)) { - audioClient3_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient3 SetEventHandle failed", hr); - return false; - } + SetErrorString("AudioClient3 init failed", hr); + return false; + } + actualPeriodFrames_ = minPeriodFrames_; - hr = audioClient3_->GetService(IID_PPV_ARGS(&renderClient_)); - if (FAILED(hr)) { - audioClient3_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient3 GetService failed", hr); - return false; - } + hr = audioClient3_->GetBufferSize(&reportedBufferSize_); + if (FAILED(hr)) { + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetBufferSize failed", hr); + return false; } - } - if (!audioClient3_) { - // Fallback to IAudioClient (older OS) - HRESULT hr = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&audioClient_); + hr = audioClient3_->SetEventHandle(audioEvent_); if (FAILED(hr)) { - SetErrorString("Failed to activate audio device", hr); + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 SetEventHandle failed", hr); return false; } - hr = audioClient_->GetMixFormat(&format_); + hr = audioClient3_->GetService(IID_PPV_ARGS(&renderClient_)); if (FAILED(hr)) { - _dbg_assert_(false); + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetService failed", hr); return false; } + } + return true; +} - // If there are too many channels, try asking for a 2-channel output format. - DWORD extraStreamFlags = 0; - const AudioFormat fmt = Classify(format_); - - curSamplesPerSec_ = format_->nSamplesPerSec; - - bool createBuffer = false; - if (fmt == AudioFormat::Float) { - if (format_->nChannels != 2) { - INFO_LOG(Log::Audio, "Got %d channels, asking for stereo instead", format_->nChannels); - WAVEFORMATEXTENSIBLE stereo; - BuildStereoFloatFormat((const WAVEFORMATEXTENSIBLE *)format_, &stereo); - - WAVEFORMATEX *closestMatch = nullptr; - const HRESULT result = audioClient_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (const WAVEFORMATEX *)&stereo, &closestMatch); - if (result == S_OK) { - // We got the format! Use it and set as current. - _dbg_assert_(!closestMatch); - format_ = (WAVEFORMATEX *)CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); - memcpy(format_, &stereo, sizeof(WAVEFORMATEX) + stereo.Format.cbSize); - extraStreamFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; - INFO_LOG(Log::Audio, "Successfully asked for two channels"); - } else if (result == S_FALSE) { - // We got another format. Meh, let's just use what we got. - if (closestMatch) { - WARN_LOG(Log::Audio, "Didn't get the format we wanted, but got: %lu ch=%d", closestMatch->nSamplesPerSec, closestMatch->nChannels); - CoTaskMemFree(closestMatch); - } else { - WARN_LOG(Log::Audio, "Failed to fall back to two channels. Using workarounds."); - } - createBuffer = true; +bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMode) { + // Fallback to IAudioClient (older OS) + HRESULT hr = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&audioClient_); + if (FAILED(hr)) { + SetErrorString("Failed to activate audio device", hr); + return false; + } + + hr = audioClient_->GetMixFormat(&format_); + if (FAILED(hr)) { + audioClient_.Reset(); + SetErrorString("AudioClient GetMixFormat failed", hr); + return false; + } + + // If there are too many channels, try asking for a 2-channel output format. + DWORD extraStreamFlags = 0; + const AudioFormat fmt = Classify(format_); + + curSamplesPerSec_ = format_->nSamplesPerSec; + + bool createBuffer = false; + if (fmt == AudioFormat::Float) { + if (format_->nChannels != 2) { + INFO_LOG(Log::Audio, "Got %d channels, asking for stereo instead", format_->nChannels); + WAVEFORMATEXTENSIBLE stereo; + BuildStereoFloatFormat((const WAVEFORMATEXTENSIBLE *)format_, &stereo); + + WAVEFORMATEX *closestMatch = nullptr; + const HRESULT result = audioClient_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (const WAVEFORMATEX *)&stereo, &closestMatch); + if (result == S_OK) { + // We got the format! Use it and set as current. + _dbg_assert_(!closestMatch); + WAVEFORMATEX *newFormat = (WAVEFORMATEX *)CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + _dbg_assert_(newFormat); + memcpy(newFormat, &stereo, sizeof(WAVEFORMATEX) + stereo.Format.cbSize); + CoTaskMemFree(format_); + format_ = newFormat; + extraStreamFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; + INFO_LOG(Log::Audio, "Successfully asked for two channels"); + } else if (result == S_FALSE) { + // We got another format. Meh, let's just use what we got. + if (closestMatch) { + WARN_LOG(Log::Audio, "Didn't get the format we wanted, but got: %lu ch=%d", closestMatch->nSamplesPerSec, closestMatch->nChannels); + CoTaskMemFree(closestMatch); } else { - WARN_LOG(Log::Audio, "Got other error %08lx", result); - _dbg_assert_(!closestMatch); + WARN_LOG(Log::Audio, "Failed to fall back to two channels. Using workarounds."); } + createBuffer = true; } else { - // All good, nothing to convert. - _dbg_assert_(format_); + WARN_LOG(Log::Audio, "Got other error %08lx", result); + _dbg_assert_(!closestMatch); } } else { - // Some other format. - WARN_LOG(Log::Audio, "Format not float, applying conversion."); - createBuffer = true; + // All good, nothing to convert. + _dbg_assert_(format_); } + } else { + // Some other format. + WARN_LOG(Log::Audio, "Format not float, applying conversion."); + createBuffer = true; + } - // Get engine period info - REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; - audioClient_->GetDevicePeriod(&defaultPeriod, &minPeriod); + // Get engine period info + REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; + audioClient_->GetDevicePeriod(&defaultPeriod, &minPeriod); - audioEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); + const REFERENCE_TIME duration = minPeriod; + hr = audioClient_->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | extraStreamFlags, + duration, // This is a minimum, the result might be larger. We use GetBufferSize to check. + 0, // ref duration, always 0 in shared mode. + format_, + nullptr + ); - const REFERENCE_TIME duration = minPeriod; - hr = audioClient_->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | extraStreamFlags, - duration, // This is a minimum, the result might be larger. We use GetBufferSize to check. - 0, // ref duration, always 0 in shared mode. - format_, - nullptr - ); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient init failed", hr); + return false; + } - if (FAILED(hr)) { - audioClient_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient init failed", hr); - return false; - } + hr = audioClient_->GetBufferSize(&reportedBufferSize_); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient GetBufferSize failed", hr); + return false; + } + actualPeriodFrames_ = reportedBufferSize_; // we don't have a better estimate. - hr = audioClient_->GetBufferSize(&reportedBufferSize_); - if (FAILED(hr)) { - audioClient_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient GetBufferSize failed", hr); - return false; - } - actualPeriodFrames_ = reportedBufferSize_; // we don't have a better estimate. + hr = audioClient_->SetEventHandle(audioEvent_); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient SetEventHandle failed", hr); + return false; + } - hr = audioClient_->SetEventHandle(audioEvent_); + hr = audioClient_->GetService(IID_PPV_ARGS(&renderClient_)); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient GetService failed", hr); + return false; + } + + if (createBuffer) { + tempBuf_ = std::make_unique(reportedBufferSize_ * 2); + } + return true; +} + +bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode latencyMode, bool *revertedToDefault) { + Stop(); + + *revertedToDefault = false; + + ComPtr device; + if (uniqueId.empty()) { + // Use the default device. + HRESULT hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); if (FAILED(hr)) { - audioClient_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient SetEventHandle failed", hr); + SetErrorString("Failed to get the default endpoint", hr); return false; } - - hr = audioClient_->GetService(IID_PPV_ARGS(&renderClient_)); + } else { + // Use whatever device. + std::wstring wId = ConvertUTF8ToWString(uniqueId); + HRESULT hr = enumerator_->GetDevice(wId.c_str(), &device); if (FAILED(hr)) { - audioClient_.Reset(); - CoTaskMemFree(format_); - format_ = nullptr; - SetErrorString("AudioClient GetService failed", hr); - return false; + // Fallback to default device + INFO_LOG(Log::Audio, "Falling back to default device...\n"); + *revertedToDefault = true; + hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); + if (FAILED(hr)) { + SetErrorString("Failed to fallback", hr); + return false; + } } + } + + AudioDeviceDesc desc{}; + GetDeviceDesc(device.Get(), &desc); + INFO_LOG(Log::Audio, "Activating audio device: %s : %s", desc.name.c_str(), desc.uniqueId.c_str()); - if (createBuffer) { - tempBuf_ = std::make_unique(reportedBufferSize_ * 2); + { + std::lock_guard guard(deviceLock_); + curDeviceId_ = desc.uniqueId; + curDeviceName_ = desc.name; + } + + // Get rid of any old tempBuf_. + tempBuf_.reset(); + + // This is used by both paths. + audioEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + if (!TryInitAudioClient3(device.Get(), latencyMode)) { + if (!TryInitAudioClient(device.Get(), latencyMode)) { + // Failed both client types. + CloseHandle(audioEvent_); + audioEvent_ = nullptr; + return false; } } diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index cc85b9459c0b..bda493dfc1f8 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -79,6 +79,9 @@ class WASAPIContext : public AudioBackend { void SetErrorString(std::string_view str, HRESULT hr); void ClearErrorString(); + bool TryInitAudioClient3(IMMDevice *device, LatencyMode latencyMode); + bool TryInitAudioClient(IMMDevice *device, LatencyMode latencyMode); + void Start(); void Stop(); diff --git a/assets/compat.ini b/assets/compat.ini index 5f98444cdb7d..6c9b577fbabd 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1244,6 +1244,11 @@ ULUS10186 = true # Fortix (#20436) NPUZ00016 = true NPEZ00096 = true +# Jetpack Joyride +NPEZ00444 = true +NPEZ00469 = true +NPUZ00292 = true +NPEZ00470 = true [MemstickFixedFree] # Assassin's Creed : Bloodlines - issue #12761