From 852adf0d214b875a07b770afb11a140c3631478e Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 19 Jan 2022 00:52:00 +1300 Subject: [PATCH 01/10] optimize DirectSound Buffer's StopEx function --- .../DSOUND/DirectSound/DirectSoundBuffer.cpp | 65 ++++++++++--------- .../DSOUND/DirectSound/DirectSoundInline.hpp | 3 +- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index 1f9634a88..e21ae0107 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -1594,52 +1594,54 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_StopEx) hRet = pThis->EmuDirectSoundBuffer8->Stop(); pThis->Xb_rtStopEx = 0LL; } - else { - bool isLooping; - if ((pThis->EmuPlayFlags & X_DSBPLAY_LOOPING) > 0) { - isLooping = true; - } - else { - isLooping = false; - } + else if(dwFlags & X_DSBSTOPEX_ENVELOPE) { + bool isLooping = pThis->EmuPlayFlags & X_DSBPLAY_LOOPING; - if ((dwFlags & X_DSBSTOPEX_ENVELOPE) > 0) { - if (rtTimeStamp == 0LL) { - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - pThis->Xb_rtStopEx = getTime.QuadPart; - } - else { - pThis->Xb_rtStopEx = rtTimeStamp; - } - pThis->Xb_rtStopEx += (pThis->Xb_EnvolopeDesc.dwRelease * 512) / 48000; + double releaseSamples = pThis->Xb_EnvolopeDesc.dwRelease * 512.0; + + if (rtTimeStamp == 0LL) { + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + pThis->Xb_rtStopEx = getTime.QuadPart; } else { pThis->Xb_rtStopEx = rtTimeStamp; } + const double samplesToTicks = 10000000 / 48000.0; + xbox::REFERENCE_TIME releaseTicks = static_cast(releaseSamples * samplesToTicks); + pThis->Xb_rtStopEx += releaseTicks; - if ((dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) > 0) { - // Release from loop region. - pThis->EmuPlayFlags &= ~X_DSBPLAY_LOOPING; - } - - DWORD dwValue, dwStatus; + DWORD currentPos, dwStatus; pThis->EmuDirectSoundBuffer8->GetStatus(&dwStatus); if (pThis->EmuBufferToggle != X_DSB_TOGGLE_DEFAULT) { - pThis->EmuDirectSoundBuffer8->GetCurrentPosition(nullptr, &dwValue); + pThis->EmuDirectSoundBuffer8->GetCurrentPosition(nullptr, ¤tPos); hRet = pThis->EmuDirectSoundBuffer8->Stop(); - DSoundBufferResizeUpdate(pHybridThis, pThis->EmuPlayFlags, hRet, 0, pThis->X_BufferCacheSize); + // Determine the range of bytes we need to play + // Test case: Outrun 2006 - converting large buffers tanks the FPS + // Is set within DSoundBufferRegionCurrentLocation function + DWORD bufferRangeStart; + DWORD bufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybridThis, pThis->EmuPlayFlags, bufferRangeStart, bufferRangeSize); - dwValue += pThis->EmuRegionPlayStartOffset; - if (isLooping) { - dwValue += pThis->EmuRegionLoopStartOffset; + if (pThis->EmuBufferToggle == X_DSB_TOGGLE_LOOP) { + // if we are to release from loop region, then we need change the size to end of actual buffer cache. + if (dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) { + bufferRangeSize = pThis->X_BufferCacheSize - bufferRangeStart; + } } + DSoundBufferResizeUpdate(pHybridThis, pThis->EmuPlayFlags, hRet, bufferRangeStart, bufferRangeSize); + pThis->EmuBufferToggle = X_DSB_TOGGLE_DEFAULT; - pThis->EmuDirectSoundBuffer8->SetCurrentPosition(dwValue); + pThis->EmuDirectSoundBuffer8->SetCurrentPosition(currentPos); + } + + if (dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) { + // Release from loop region. + pThis->EmuPlayFlags &= ~X_DSBPLAY_LOOPING; } if (dwStatus & DSBSTATUS_PLAYING && rtTimeStamp != 0LL) { @@ -1650,6 +1652,9 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_StopEx) pThis->Xb_rtStopEx = 0LL; } } + else { + LOG_TEST_CASE("Expected X_DSBSTOPEX_ENVELOPE"); + } } return hRet; diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp index 3c42651b6..1b4aad730 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp @@ -569,7 +569,6 @@ static inline void DSoundBufferResizeUpdate( static inline void DSoundBufferRegionCurrentLocation( xbox::XbHybridDSBuffer* pHybridThis, DWORD dwPlayFlags, - HRESULT &hRet, DWORD &Xb_dwStartOffset, DWORD &Xb_dwByteLength) { @@ -608,7 +607,7 @@ static inline void DSoundBufferUpdate( DWORD Xb_dwByteLength; DWORD Xb_dwStartOffset; - DSoundBufferRegionCurrentLocation(pHybridThis, dwPlayFlags, hRet, Xb_dwStartOffset, Xb_dwByteLength); + DSoundBufferRegionCurrentLocation(pHybridThis, dwPlayFlags, Xb_dwStartOffset, Xb_dwByteLength); DSoundBufferResizeUpdate(pHybridThis, dwPlayFlags, hRet, Xb_dwStartOffset, Xb_dwByteLength); } From 60dbf241e8bda38ee9141c950b050074a47df699 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sun, 27 Feb 2022 22:48:23 +1300 Subject: [PATCH 02/10] Stream audio from Xbox DirectSoundBuffers to handle cases where titles write to sound buffers after they are created. Note titles do not have to lock the buffer or otherwise call any API to write to sound buffers. Every few ms, for each sound buffer currently playing, write a chunk of sound data ahead of the current audio play cursor. Fixes audio issues in titles including: - NBA Live 2005 (no audio) - Crash Tag Team Racing (audio looping incorrectly) - Madagascar (audio looping incorrectly) --- .../hle/DSOUND/DirectSound/DirectSound.cpp | 122 +++++++++++++++++- .../DSOUND/DirectSound/DirectSoundGlobal.cpp | 2 + .../DSOUND/DirectSound/DirectSoundGlobal.hpp | 7 + 3 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index f0e44fedd..a079ef8a4 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -365,22 +365,134 @@ xbox::void_xt WINAPI xbox::EMUPATCH(DirectSoundDoWork)() return; } -// For Async process purpose only + +void StreamBufferAudio(xbox::XbHybridDSBuffer* pHybridBuffer, float msToCopy) { + auto pThis = pHybridBuffer->emuDSBuffer; + auto dsb = pThis->EmuDirectSoundBuffer8; + bool isAdpcm = pThis->EmuFlags & DSE_FLAG_XADPCM; + + DWORD xBufferRangeStart; + DWORD xBufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybridBuffer, pThis->EmuPlayFlags, xBufferRangeStart, xBufferRangeSize); + + DWORD hostBufferSize = pThis->EmuBufferDesc.dwBufferBytes; + + DWORD playCursor; + DWORD writeCursor; + dsb->GetCurrentPosition(&playCursor, &writeCursor); + + DWORD cursorGap = writeCursor >= playCursor + ? (writeCursor - playCursor) + : hostBufferSize - playCursor + writeCursor; + + // Determine where to copy data from. + // Note: The DirectSound write cursor can sit quite far ahead of the play cursor, + // but copying closer to the play cursor can introduce weird looping or + // latency issues + // Test case: NBA Live 2005 (writes to a very small buffer expecting low latency, can crackle at > 1ms stream interval) + // Test case: Halo (intro video delay when writing from play cursor) + DWORD writeOffset = writeCursor + cursorGap * g_dsBufferStreaming.tweakCopyOffset; + DWORD writeSize = std::min( + (DWORD)(pThis->EmuBufferDesc.lpwfxFormat->nAvgBytesPerSec * msToCopy / 1000), + hostBufferSize + ); + + DWORD blockSize = isAdpcm + ? XBOX_ADPCM_DSTSIZE * pThis->EmuBufferDesc.lpwfxFormat->nChannels + : pThis->EmuBufferDesc.lpwfxFormat->nBlockAlign; + + // ADPCM block alignment + writeOffset = ((writeOffset + blockSize / 2) / blockSize) * blockSize; + writeSize = ((writeSize + blockSize / 2) / blockSize) * blockSize; + writeOffset %= hostBufferSize; + + DWORD xWriteOffset = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, writeOffset); + + assert(xBufferRangeStart + xBufferRangeSize > xWriteOffset); + if (isAdpcm) { + assert(writeOffset % XBOX_ADPCM_DSTSIZE == 0); + assert(xWriteOffset % XBOX_ADPCM_SRCSIZE == 0); + } + + LPVOID lplpvAudioPtr1, lplpvAudioPtr2; + DWORD lplpvAudioBytes1, lplpvAudioBytes2; + HRESULT hRet = pThis->EmuDirectSoundBuffer8->Lock(writeOffset, writeSize, + &lplpvAudioPtr1, &lplpvAudioBytes1, + &lplpvAudioPtr2, &lplpvAudioBytes2, + 0); + + if (hRet != 0) { + CxbxrKrnlAbort("DirectSoundBuffer Lock Failed!"); + } + + if (lplpvAudioPtr1 && pThis->X_BufferCache != nullptr) { + DSoundBufferOutputXBtoHost( + pThis->EmuFlags, + pThis->EmuBufferDesc, + ((PBYTE)pThis->X_BufferCache + xBufferRangeStart + xWriteOffset), + DSoundBufferGetXboxBufferSize(pThis->EmuFlags, lplpvAudioBytes1), + lplpvAudioPtr1, + lplpvAudioBytes1 + ); + + if (lplpvAudioPtr2) { + DSoundBufferOutputXBtoHost( + pThis->EmuFlags, + pThis->EmuBufferDesc, + ((PBYTE)pThis->X_BufferCache + xBufferRangeStart + 0), + DSoundBufferGetXboxBufferSize(pThis->EmuFlags, lplpvAudioBytes2), + lplpvAudioPtr2, + lplpvAudioBytes2 + ); + } + + HRESULT hRet = dsb->Unlock(lplpvAudioPtr1, lplpvAudioBytes1, lplpvAudioPtr2, lplpvAudioBytes2); + + if (hRet != DS_OK) { + CxbxrKrnlAbort("DirectSoundBuffer Unlock Failed!"); + } + } +} + static void dsound_thread_worker(LPVOID nullPtr) { g_AffinityPolicy->SetAffinityOther(); + const int dsStreamInterval = 300; + int waitCounter = 0; + while (true) { + // FIXME time this loop more accurately + // and account for variation in the length of Sleep calls + // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often // unless console is open with logging enabled. This is the cause of stopping intro videos often. - Sleep(300); + Sleep(g_dsBufferStreaming.streamInterval); + waitCounter += g_dsBufferStreaming.streamInterval; + // Enforce mutex guard lock only occur inside below bracket for proper compile build. { DSoundMutexGuardLock; - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - DirectSoundDoWork_Stream(getTime); + if (waitCounter > g_dsBufferStreaming.streamInterval) { + waitCounter = 0; + + // For Async process purpose only + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + DirectSoundDoWork_Stream(getTime); + } + + // Stream sound buffer audio + // because the title may change the content of sound buffers at any time + for (auto& pBuffer : g_pDSoundBufferCache) { + DWORD status; + HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); + if (hRet == 0 && status & DSBSTATUS_PLAYING) { + auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; + StreamBufferAudio(pBuffer, streamMs); + } + } } } } diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index 3e367ef68..1f483dd84 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -54,6 +54,8 @@ DWORD g_dwXbMemAllocated = 0; DWORD g_dwFree2DBuffers = 0; DWORD g_dwFree3DBuffers = 0; +DsBufferStreaming g_dsBufferStreaming; + void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_show_audio_stats) { DSoundMutexGuardLock; diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp index b5e37c6c7..23e650ed5 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp @@ -61,6 +61,13 @@ extern DWORD g_dwXbMemAllocated; extern DWORD g_dwFree2DBuffers; extern DWORD g_dwFree3DBuffers; +struct DsBufferStreaming { + DWORD streamInterval = 1; + DWORD streamAhead = 50; + float tweakCopyOffset = 0; +}; +extern DsBufferStreaming g_dsBufferStreaming; + // size of DirectSound cache max size #define X_DIRECTSOUND_CACHE_MAX 0x800 From 7528d9a4e33fcd16e4287e21d152d0ecdbb7ddb7 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 2 Mar 2022 21:19:01 +1300 Subject: [PATCH 03/10] Change locking APIs to nops to prevent interference DirectSoundBuffer streaming. --- .../DSOUND/DirectSound/DirectSoundBuffer.cpp | 101 ++++-------------- 1 file changed, 21 insertions(+), 80 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index e21ae0107..14398b175 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -437,55 +437,26 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Lock) LOG_FUNC_END; EmuDirectSoundBuffer* pThis = pHybridThis->emuDSBuffer; - HRESULT hRet = D3D_OK; - DWORD pcmSize = DSoundBufferGetPCMBufferSize(pThis->EmuFlags, dwBytes); - DWORD pcmOffset = DSoundBufferGetPCMBufferSize(pThis->EmuFlags, dwOffset); - DSoundGenericUnlock(pThis->EmuFlags, - pThis->EmuDirectSoundBuffer8, - pThis->EmuBufferDesc, - pThis->Host_lock, - pThis->X_BufferCache, - pThis->X_lock.dwLockOffset, - pThis->X_lock.dwLockBytes1, - pThis->X_lock.dwLockBytes2); + // Xbox directsound doesn't require locking buffers + // This Xbox api only exists to match PC - if (ppvAudioPtr2 == xbox::zeroptr) { - hRet = pThis->EmuDirectSoundBuffer8->Lock(pcmOffset, pcmSize, &pThis->Host_lock.pLockPtr1, &pThis->Host_lock.dwLockBytes1, - nullptr, 0, dwFlags); - pThis->Host_lock.pLockPtr2 = nullptr; - } else { - hRet = pThis->EmuDirectSoundBuffer8->Lock(pcmOffset, pcmSize, &pThis->Host_lock.pLockPtr1, &pThis->Host_lock.dwLockBytes1, - &pThis->Host_lock.pLockPtr2, &pThis->Host_lock.dwLockBytes2, dwFlags); - } - - if (hRet != DS_OK) { - CxbxrKrnlAbort("IDirectSoundBuffer_Lock Failed!"); - } - - // Host lock position - pThis->Host_lock.dwLockOffset = pcmOffset; - pThis->Host_lock.dwLockFlags = dwFlags; - pThis->X_lock.dwLockFlags = dwFlags; - - // Emulate to xbox's lock position - pThis->X_lock.dwLockOffset = dwOffset; - *ppvAudioPtr1 = pThis->X_lock.pLockPtr1 = ((LPBYTE)pThis->X_BufferCache + dwOffset); - *pdwAudioBytes1 = pThis->X_lock.dwLockBytes1 = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, pThis->Host_lock.dwLockBytes1); - if (pThis->Host_lock.pLockPtr2 != nullptr) { - *ppvAudioPtr2 = pThis->X_lock.pLockPtr2 = pThis->X_BufferCache; - *pdwAudioBytes2 = pThis->X_lock.dwLockBytes2 = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, pThis->Host_lock.dwLockBytes2); - } else { - // If secondary pointers are not used, then set them as zero. - // There are applications bug didn't check for audio pointer that is null pointer which should not use invalid audio bytes. - // Since internal functions do set them zero. We'll set them here as well. - if (ppvAudioPtr2 != xbox::zeroptr) { - *ppvAudioPtr2 = xbox::zeroptr; - } - if (pdwAudioBytes2 != xbox::zeroptr) { - *pdwAudioBytes2 = 0; - } - } + if (dwOffset + dwBytes <= pThis->X_BufferCacheSize) { + *pdwAudioBytes1 = dwBytes; + *ppvAudioPtr1 = (PBYTE)pThis->X_BufferCache + dwOffset; + if (ppvAudioPtr2 != nullptr) { + *ppvAudioPtr2 = nullptr; + *pdwAudioBytes2 = 0; + } + } + else { + *pdwAudioBytes1 = pThis->X_BufferCacheSize - dwOffset; + *ppvAudioPtr1 = (PBYTE)pThis->X_BufferCache + dwOffset; + if (ppvAudioPtr2 != nullptr) { + *pdwAudioBytes2 = dwBytes - *pdwAudioBytes1; + *ppvAudioPtr2 = (PBYTE)pThis->X_BufferCache; + } + } LOG_FUNC_BEGIN_ARG_RESULT LOG_FUNC_ARG_RESULT(ppvAudioPtr1) @@ -494,7 +465,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Lock) LOG_FUNC_ARG_RESULT(pdwAudioBytes2) LOG_FUNC_END_ARG_RESULT; - RETURN_RESULT_CHECK(hRet); + RETURN(DS_OK); } // ****************************************************************** @@ -509,38 +480,8 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Unlock) dword_xt pdwAudioBytes2 ) { - DSoundMutexGuardLock; - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(pHybridThis) - LOG_FUNC_ARG(ppvAudioPtr1) - LOG_FUNC_ARG(pdwAudioBytes1) - LOG_FUNC_ARG(ppvAudioPtr2) - LOG_FUNC_ARG(pdwAudioBytes2) - LOG_FUNC_END; - - EmuDirectSoundBuffer* pThis = pHybridThis->emuDSBuffer; - // TODO: Find out why pThis->EmuLockPtr1 is nullptr... (workaround atm is to check if it is not a nullptr.) - if (pThis->X_BufferCache != xbox::zeroptr && pThis->Host_lock.pLockPtr1 != nullptr) { - - memcpy_s((PBYTE)pThis->X_BufferCache + pThis->X_lock.dwLockOffset, - pThis->X_BufferCacheSize - pThis->X_lock.dwLockOffset, - pThis->X_lock.pLockPtr1, - pThis->X_lock.dwLockBytes1); - - if (pThis->Host_lock.pLockPtr2 != nullptr) { - memcpy_s(pThis->X_BufferCache, pThis->X_BufferCacheSize, pThis->X_lock.pLockPtr2, pThis->X_lock.dwLockBytes2); - } - } - - DSoundGenericUnlock(pThis->EmuFlags, - pThis->EmuDirectSoundBuffer8, - pThis->EmuBufferDesc, - pThis->Host_lock, - pThis->X_BufferCache, - pThis->X_lock.dwLockOffset, - pThis->X_lock.dwLockBytes1, - pThis->X_lock.dwLockBytes2); + // Xbox directsound doesn't require locking buffers + // This Xbox api only exists to match PC return DS_OK; } From 4c302641363b0397b4479e03d74ddc9af6e3934b Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 23 Feb 2022 15:53:19 +1300 Subject: [PATCH 04/10] DirectSoundBuffer visualization debug view - Visualize play progress, play region, loop region - Buffer streaming controls - Scale visualization to window by RadWolfie --- src/core/common/imgui/audio.cpp | 6 +- src/core/common/imgui/settings.h | 1 + .../DSOUND/DirectSound/DirectSoundGlobal.cpp | 102 ++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/core/common/imgui/audio.cpp b/src/core/common/imgui/audio.cpp index ba7637ca8..40e949cdd 100644 --- a/src/core/common/imgui/audio.cpp +++ b/src/core/common/imgui/audio.cpp @@ -25,6 +25,7 @@ void ImGuiAudio::DrawMenu() { if (ImGui::BeginMenu("Audio")) { ImGui::MenuItem("Debug General Cache Stats", NULL, &m_windows.cache_stats_general); + ImGui::MenuItem("SoundBuffer Visualization", NULL, &m_windows.cache_visualization); ImGui::EndMenu(); } } @@ -32,6 +33,9 @@ void ImGuiAudio::DrawMenu() void ImGuiAudio::DrawWidgets(bool is_focus, ImGuiWindowFlags input_handler) { //TODO: In need of make interface class to return generic info in some way. - extern void DSound_PrintStats(bool, ImGuiWindowFlags, bool m_show_audio_stats); + extern void DSound_PrintStats(bool, ImGuiWindowFlags, bool m_show_audio_stats); DSound_PrintStats(is_focus, input_handler, m_windows.cache_stats_general); + + extern void DSound_DrawBufferVisualization(bool, bool *p_show, ImGuiWindowFlags); + DSound_DrawBufferVisualization(is_focus, &m_windows.cache_visualization, input_handler); } diff --git a/src/core/common/imgui/settings.h b/src/core/common/imgui/settings.h index a1790545a..9b9e56682 100644 --- a/src/core/common/imgui/settings.h +++ b/src/core/common/imgui/settings.h @@ -44,6 +44,7 @@ typedef struct { typedef struct { bool cache_stats_general; + bool cache_visualization; bool Reserved[3]; } imgui_audio_windows; diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index 1f483dd84..1818ecdf3 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -32,6 +32,10 @@ #include #include #include "DirectSoundGlobal.hpp" +#include "DirectSoundInline.hpp" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" Settings::s_audio g_XBAudio = { 0 }; std::recursive_mutex g_DSoundMutex; @@ -56,6 +60,104 @@ DWORD g_dwFree3DBuffers = 0; DsBufferStreaming g_dsBufferStreaming; +void DrawAudioProgress(xbox::XbHybridDSBuffer* pHybrid, float scaleWidth, ImDrawList* drawList) { + const auto& pBuffer = pHybrid->emuDSBuffer; + + auto cursor = ImGui::GetCursorScreenPos(); + auto width = ImGui::GetContentRegionAvail().x; + + DWORD rawCursor; + HybridDirectSoundBuffer_GetCurrentPosition(pBuffer->EmuDirectSoundBuffer8, &rawCursor, nullptr, pBuffer->EmuFlags); + float scale = (width / pBuffer->X_BufferCacheSize) / scaleWidth; + float playCursor = rawCursor * scale; + + bool isLooping = pBuffer->EmuPlayFlags & X_DSBPLAY_LOOPING; + + auto colSpan = ImColor(0.8f, 0.1f, 0.1f, 0.3f); + auto colRegion = ImColor(0.1f, 0.8f, 0.1, 0.3f); + auto colRegionLoop = ImColor(0.1f, 0.2f, 0.8, 0.3f); + auto colPlay = ImColor(0.8f, 0.8f, 0.1f, 0.6); + float height = 8; + + float sBuf = height * 0.4; + float sReg = height * 1; + float sPlay = height * 0.4; + + // Buffer + auto start = cursor + ImVec2(0, (height - sBuf) / 2); + drawList->AddRectFilled(start, start + ImVec2(pBuffer->X_BufferCacheSize * scale, sBuf), colSpan, 0); + + DWORD bufferRangeStart; + DWORD bufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybrid, pBuffer->EmuPlayFlags, bufferRangeStart, bufferRangeSize); + + bufferRangeStart *= scale; + bufferRangeSize *= scale; + + // Region + start = cursor + ImVec2(bufferRangeStart, (height - sReg) / 2); + drawList->AddRectFilled(start, start + ImVec2(bufferRangeSize, sReg), isLooping ? colRegionLoop : colRegion); + + // Play area + start = cursor + ImVec2(bufferRangeStart, (height - sPlay) / 2); + drawList->AddRectFilled(start, start + ImVec2(playCursor, sPlay), colPlay); + // Play cursor + start = cursor + ImVec2(bufferRangeStart + playCursor, 0); + drawList->AddLine(start, start + ImVec2(0, height), colPlay); + + ImGui::Dummy(ImVec2(pBuffer->X_BufferCacheSize * scale, height)); +} + +void DSound_DrawBufferVisualization(bool is_focus, bool* p_show, ImGuiWindowFlags input_handler) { + if (!*p_show) return; + + DSoundMutexGuardLock; + + ImGui::SetNextWindowPos(ImVec2(IMGUI_MIN_DIST_SIDE, IMGUI_MIN_DIST_TOP), ImGuiCond_FirstUseEver, ImVec2(0.0f, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(200, 275), ImGuiCond_FirstUseEver); + if (ImGui::Begin("DSBuffer Visualization", p_show, input_handler)) { + + static bool showPlayingOnly = true; + ImGui::Checkbox("Show playing only", &showPlayingOnly); + + static float bufferScale = 1; + ImGui::PushItemWidth(100); + ImGui::DragFloat("Audio Scale", &bufferScale, 1 / 1000.f, 1 / 24000.f, 1.f, "%.7f", ImGuiSliderFlags_Logarithmic); + + if (ImGui::CollapsingHeader("Buffering Controls")) { + ImGui::SliderInt("Stream interval (ms)", (int*)&g_dsBufferStreaming.streamInterval, 0, 50); + ImGui::SliderInt("Stream ahead (ms)", (int*)&g_dsBufferStreaming.streamAhead, 0, 1000); + ImGui::SliderFloat("Tweak copy offset", &g_dsBufferStreaming.tweakCopyOffset, -1, 1); + } + + if (ImGui::BeginChild("DSBuffer Graph", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) { + + auto drawList = ImGui::GetWindowDrawList(); + + int index = 0; + for (const auto& i : g_pDSoundBufferCache) { + if (showPlayingOnly) { + DWORD dwStatus; + auto hRet = i->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&dwStatus); + if (hRet != DS_OK || !(dwStatus & DSBSTATUS_PLAYING)) { + continue; + } + } + + // Required to add controls inside the loop + ImGui::PushID(index++); + + DrawAudioProgress(i, bufferScale, drawList); + + ImGui::PopID(); + } + ImGui::EndChild(); + } + + ImGui::End(); + } +} + void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_show_audio_stats) { DSoundMutexGuardLock; From 4186d4ba8b2a4958221f3f9d72b796c2510d008a Mon Sep 17 00:00:00 2001 From: Anthony Date: Sun, 6 Mar 2022 20:59:42 +1300 Subject: [PATCH 05/10] Avoid expensive calls to DirectSound on buffers unless they've been played at least once --- src/core/hle/DSOUND/DirectSound/DirectSound.cpp | 15 ++++++++++----- src/core/hle/DSOUND/DirectSound/DirectSound.hpp | 4 ++++ .../hle/DSOUND/DirectSound/DirectSoundBuffer.cpp | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index a079ef8a4..934a19e2b 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -486,11 +486,15 @@ static void dsound_thread_worker(LPVOID nullPtr) // Stream sound buffer audio // because the title may change the content of sound buffers at any time for (auto& pBuffer : g_pDSoundBufferCache) { - DWORD status; - HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); - if (hRet == 0 && status & DSBSTATUS_PLAYING) { - auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; - StreamBufferAudio(pBuffer, streamMs); + // Avoid expensive calls to DirectSound on buffers unless they've been played at least once + // Since some titles create a large amount of buffers, but only use a few + if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { + DWORD status; + HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); + if (hRet == 0 && status & DSBSTATUS_PLAYING) { + auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; + StreamBufferAudio(pBuffer, streamMs); + } } } } @@ -1057,6 +1061,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(CDirectSound_SynchPlayback) DSoundBufferSynchPlaybackFlagRemove(pDSBuffer->EmuFlags); EmuLog(LOG_LEVEL::DEBUG, "SynchPlayback - pDSBuffer: %08X; EmuPlayFlags: %08X", *ppDSBuffer, pDSBuffer->EmuPlayFlags); pDSBuffer->EmuDirectSoundBuffer8->Play(0, 0, pDSBuffer->EmuPlayFlags); + pDSBuffer->EmuStreamingInfo.playRequested = true; } } diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp index cefdaf186..abb56bd77 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp @@ -110,6 +110,10 @@ struct EmuDirectSoundBuffer X_DSENVOLOPEDESC Xb_EnvolopeDesc; X_DSVOICEPROPS Xb_VoiceProperties; DWORD Xb_Flags; + struct { + // True if the buffer has been played, and should be considered for streaming + bool playRequested = false; + } EmuStreamingInfo; }; struct XbHybridDSBuffer : DSBUFFER_S::DSBUFFER_I { diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index 14398b175..ab4865b0a 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -80,6 +80,7 @@ void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER &time) pThis->Xb_rtPauseEx = 0LL; pThis->EmuFlags &= ~DSE_FLAG_PAUSE; pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags); + pThis->EmuStreamingInfo.playRequested = true; } if (pThis->Xb_rtStopEx != 0LL && pThis->Xb_rtStopEx <= time.QuadPart) { @@ -601,6 +602,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Play) } if ((pThis->EmuFlags & DSE_FLAG_SYNCHPLAYBACK_CONTROL) == 0) { hRet = pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags); + pThis->EmuStreamingInfo.playRequested = true; } } From feb1f0383d7c08dddb152dfe6ae30070b7ef7e4f Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 12 Mar 2022 09:45:23 +1300 Subject: [PATCH 06/10] Comment some includes --- src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index 1818ecdf3..ba96b6159 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -29,13 +29,12 @@ #include #include "core/common/imgui/ui.hpp" -#include #include #include "DirectSoundGlobal.hpp" -#include "DirectSoundInline.hpp" +#include "DirectSoundInline.hpp" // For GetCurrentPosition, RegionCurrentLocation #define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_internal.h" +#include "imgui_internal.h" // For ImVec Settings::s_audio g_XBAudio = { 0 }; std::recursive_mutex g_DSoundMutex; From baa1cf54704407154b1a4ea5325cb643d42532a3 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 12 Mar 2022 09:53:22 +1300 Subject: [PATCH 07/10] Ensure End() is called after Begin/BeginChild, regardless of return value Note this is not required for other Begin apis --- src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index ba96b6159..b4862eeb8 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -150,11 +150,10 @@ void DSound_DrawBufferVisualization(bool is_focus, bool* p_show, ImGuiWindowFlag ImGui::PopID(); } - ImGui::EndChild(); } - - ImGui::End(); + ImGui::EndChild(); } + ImGui::End(); } void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_show_audio_stats) @@ -252,7 +251,7 @@ void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_sho ImGui::Text("Total active DSStream = %u", isActive); } } - ImGui::End(); } + ImGui::End(); } } From da27e1456b0b9c62eef4f6780b662247d20c09ab Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 12 Mar 2022 09:57:32 +1300 Subject: [PATCH 08/10] Restore Unlock logging --- src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index ab4865b0a..5952b77cc 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -481,6 +481,16 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Unlock) dword_xt pdwAudioBytes2 ) { + // DSoundMutexGuardLock; + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(pHybridThis) + LOG_FUNC_ARG(ppvAudioPtr1) + LOG_FUNC_ARG(pdwAudioBytes1) + LOG_FUNC_ARG(ppvAudioPtr2) + LOG_FUNC_ARG(pdwAudioBytes2) + LOG_FUNC_END; + // Xbox directsound doesn't require locking buffers // This Xbox api only exists to match PC From 49b49889531fb97a6f8f0a0cefac297d5c4fd77b Mon Sep 17 00:00:00 2001 From: Anthony Date: Sun, 13 Mar 2022 21:41:13 +1300 Subject: [PATCH 09/10] Co-locate imgui includes --- src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index b4862eeb8..29f1b86aa 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -27,15 +27,14 @@ #define LOG_PREFIX CXBXR_MODULE::DSOUND #include +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" // For ImVec #include "core/common/imgui/ui.hpp" #include #include "DirectSoundGlobal.hpp" #include "DirectSoundInline.hpp" // For GetCurrentPosition, RegionCurrentLocation -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_internal.h" // For ImVec - Settings::s_audio g_XBAudio = { 0 }; std::recursive_mutex g_DSoundMutex; From aac207c78e0e7ba49d5644dc22b24e5339427196 Mon Sep 17 00:00:00 2001 From: Anthony Date: Mon, 14 Mar 2022 17:09:48 +1300 Subject: [PATCH 10/10] Fix typo --- src/core/hle/DSOUND/DirectSound/DirectSound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index 934a19e2b..d370ff706 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -474,7 +474,7 @@ static void dsound_thread_worker(LPVOID nullPtr) { DSoundMutexGuardLock; - if (waitCounter > g_dsBufferStreaming.streamInterval) { + if (waitCounter > dsStreamInterval) { waitCounter = 0; // For Async process purpose only