[PATCH v2 0/2] MR10735: dsound: Create independent devices for each DirectSoundCreate call.
On Windows, each DirectSoundCreate call returns an IDirectSound object with its own independent primary buffer. Wine reuses a single DirectSoundDevice for all IDirectSound objects targeting the same audio endpoint, causing them to share a primary buffer. This breaks games that create multiple IDirectSound objects with different primary buffer flags. For example, Star Wars Episode I Racer creates a primary buffer with DSBCAPS_CTRL3D for in-game audio, then its Smush video engine creates a separate IDirectSound and requests a primary buffer with DSBCAPS_CTRLVOLUME. On Wine, it gets back the existing buffer without CTRLVOLUME, GetVolume fails with DSERR_CONTROLUNAVAIL, and the cutscene audio path silently gives up. The device reuse was introduced in 2005 (commit a2f1fd3aca4) when dsound talked directly to waveOut, where opening the same hardware device twice would have caused conflicts. In 2011 (commit e786998daff) dsound was reimplemented on WASAPI, but the reuse was carried forward. Since Wine's dsound already uses WASAPI shared mode, which is designed for multiple audio clients on the same endpoint, the reuse is no longer necessary. Tested with Star Wars Episode I Racer, cutscene audio plays correctly with the fix applied. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=41047 -- v2: dsound: Create independent devices for each DirectSoundCreate call. dsound/tests: Test primary buffer independence across IDirectSound objects. https://gitlab.winehq.org/wine/wine/-/merge_requests/10735
From: Jon Koops <jonkoops@gmail.com> On Windows, two separate DirectSoundCreate calls produce independent primary buffers, each with their own capability flags. Test this by creating a primary buffer with DSBCAPS_CTRL3D on one IDirectSound object, then creating a primary buffer with DSBCAPS_CTRLVOLUME on another, and verifying that each buffer retains its own flags and that GetVolume succeeds on the second. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=41047 Signed-off-by: Jon Koops <jonkoops@gmail.com> --- dlls/dsound/tests/dsound.c | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/dlls/dsound/tests/dsound.c b/dlls/dsound/tests/dsound.c index a7d6fa0fc3b..4821701353c 100644 --- a/dlls/dsound/tests/dsound.c +++ b/dlls/dsound/tests/dsound.c @@ -2192,6 +2192,95 @@ static void test_implicit_mta(void) ok(test_apt_data.type == APTTYPE_UNITIALIZED, "got apt type %d.\n", test_apt_data.type); } +static void test_primary_independent(void) +{ + HRESULT rc; + LPDIRECTSOUND dso1=NULL; + LPDIRECTSOUND dso2=NULL; + LPDIRECTSOUNDBUFFER primary1=NULL; + LPDIRECTSOUNDBUFFER primary2=NULL; + DSBUFFERDESC bufdesc; + DSBCAPS bufcaps; + LONG vol; + + rc=DirectSoundCreate(NULL,&dso1,NULL); + ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||rc==E_FAIL, + "DirectSoundCreate() failed: %08lx\n",rc); + if (rc!=DS_OK) + return; + + rc=DirectSoundCreate(NULL,&dso2,NULL); + ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||rc==E_FAIL, + "DirectSoundCreate() failed: %08lx\n",rc); + if (rc!=DS_OK) { + IDirectSound_Release(dso1); + return; + } + + rc=IDirectSound_SetCooperativeLevel(dso1,get_hwnd(),DSSCL_PRIORITY); + ok(rc==DS_OK,"IDirectSound_SetCooperativeLevel(dso1) failed: %08lx\n",rc); + if (rc!=DS_OK) + goto EXIT; + + rc=IDirectSound_SetCooperativeLevel(dso2,get_hwnd(),DSSCL_PRIORITY); + ok(rc==DS_OK,"IDirectSound_SetCooperativeLevel(dso2) failed: %08lx\n",rc); + if (rc!=DS_OK) + goto EXIT; + + /* Create a primary buffer on dso1 with CTRL3D but without CTRLVOLUME */ + ZeroMemory(&bufdesc, sizeof(bufdesc)); + bufdesc.dwSize=sizeof(bufdesc); + bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRL3D; + rc=IDirectSound_CreateSoundBuffer(dso1,&bufdesc,&primary1,NULL); + ok(rc==DS_OK && primary1!=NULL, + "IDirectSound_CreateSoundBuffer(dso1) failed: %08lx\n",rc); + if (rc!=DS_OK || primary1==NULL) + goto EXIT; + + /* Create a primary buffer on dso2 with CTRLVOLUME */ + ZeroMemory(&bufdesc, sizeof(bufdesc)); + bufdesc.dwSize=sizeof(bufdesc); + bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRLVOLUME; + rc=IDirectSound_CreateSoundBuffer(dso2,&bufdesc,&primary2,NULL); + ok(rc==DS_OK && primary2!=NULL, + "IDirectSound_CreateSoundBuffer(dso2) failed: %08lx\n",rc); + if (rc!=DS_OK || primary2==NULL) + goto RELEASE1; + + /* Check whether the two IDirectSound objects share a primary buffer */ + todo_wine + ok(primary1!=primary2, + "Two IDirectSound objects should have independent primary buffers\n"); + + /* GetVolume on dso2's primary buffer should succeed */ + todo_wine + { + rc=IDirectSoundBuffer_GetVolume(primary2,&vol); + ok(rc==DS_OK, + "IDirectSoundBuffer_GetVolume(primary2) failed: %08lx\n",rc); + } + + /* Verify dso2's primary buffer has CTRLVOLUME */ + ZeroMemory(&bufcaps, sizeof(bufcaps)); + bufcaps.dwSize=sizeof(bufcaps); + rc=IDirectSoundBuffer_GetCaps(primary2,&bufcaps); + ok(rc==DS_OK,"IDirectSoundBuffer_GetCaps(primary2) failed: %08lx\n",rc); + if (rc==DS_OK) { + todo_wine + ok(bufcaps.dwFlags & DSBCAPS_CTRLVOLUME, + "primary2 should have DSBCAPS_CTRLVOLUME: %08lx\n",bufcaps.dwFlags); + } + + IDirectSoundBuffer_Release(primary2); + +RELEASE1: + IDirectSoundBuffer_Release(primary1); + +EXIT: + IDirectSound_Release(dso2); + IDirectSound_Release(dso1); +} + START_TEST(dsound) { CoInitialize(NULL); @@ -2201,6 +2290,7 @@ START_TEST(dsound) IDirectSound_tests(); dsound_tests(); test_hw_buffers(); + test_primary_independent(); CoUninitialize(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10735
From: Jon Koops <jonkoops@gmail.com> On Windows, each DirectSoundCreate call returns an IDirectSound object with its own independent primary buffer. Wine reuses a single DirectSoundDevice for all IDirectSound objects targeting the same audio endpoint, causing them to share a primary buffer. This breaks games that use multiple IDirectSound objects with different primary buffer flags. The device reuse was introduced in 2005 (commit a2f1fd3aca4) when dsound talked directly to waveOut, where opening the same hardware device twice would have caused conflicts. In 2011 (commit e786998daff) dsound was reimplemented on WASAPI, but the reuse was carried forward. Since Wine's dsound already uses WASAPI shared mode, which is designed for multiple audio clients on the same endpoint, the reuse is no longer necessary. Remove the device reuse lookup so that each DirectSoundCreate call creates a fresh DirectSoundDevice. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=41047 Signed-off-by: Jon Koops <jonkoops@gmail.com> --- dlls/dsound/dsound.c | 17 ----------------- dlls/dsound/tests/dsound.c | 11 +++-------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/dlls/dsound/dsound.c b/dlls/dsound/dsound.c index c21090d95e2..b5c53829a5b 100644 --- a/dlls/dsound/dsound.c +++ b/dlls/dsound/dsound.c @@ -187,13 +187,6 @@ static HRESULT DirectSoundDevice_Create(DirectSoundDevice ** ppDevice) return DS_OK; } -static ULONG DirectSoundDevice_AddRef(DirectSoundDevice * device) -{ - ULONG ref = InterlockedIncrement(&(device->ref)); - TRACE("(%p) ref %ld\n", device, ref); - return ref; -} - static ULONG DirectSoundDevice_Release(DirectSoundDevice * device) { HRESULT hr; @@ -281,16 +274,6 @@ static HRESULT DirectSoundDevice_Initialize(DirectSoundDevice ** ppDevice, LPCGU EnterCriticalSection(&DSOUND_renderers_lock); - LIST_FOR_EACH_ENTRY(device, &DSOUND_renderers, DirectSoundDevice, entry){ - if(IsEqualGUID(&device->guid, &devGUID)){ - IMMDevice_Release(mmdevice); - DirectSoundDevice_AddRef(device); - *ppDevice = device; - LeaveCriticalSection(&DSOUND_renderers_lock); - return DS_OK; - } - } - hr = DirectSoundDevice_Create(&device); if(FAILED(hr)){ WARN("DirectSoundDevice_Create failed\n"); diff --git a/dlls/dsound/tests/dsound.c b/dlls/dsound/tests/dsound.c index 4821701353c..d7d90758d13 100644 --- a/dlls/dsound/tests/dsound.c +++ b/dlls/dsound/tests/dsound.c @@ -2248,17 +2248,13 @@ static void test_primary_independent(void) goto RELEASE1; /* Check whether the two IDirectSound objects share a primary buffer */ - todo_wine ok(primary1!=primary2, "Two IDirectSound objects should have independent primary buffers\n"); /* GetVolume on dso2's primary buffer should succeed */ - todo_wine - { - rc=IDirectSoundBuffer_GetVolume(primary2,&vol); - ok(rc==DS_OK, - "IDirectSoundBuffer_GetVolume(primary2) failed: %08lx\n",rc); - } + rc=IDirectSoundBuffer_GetVolume(primary2,&vol); + ok(rc==DS_OK, + "IDirectSoundBuffer_GetVolume(primary2) failed: %08lx\n",rc); /* Verify dso2's primary buffer has CTRLVOLUME */ ZeroMemory(&bufcaps, sizeof(bufcaps)); @@ -2266,7 +2262,6 @@ static void test_primary_independent(void) rc=IDirectSoundBuffer_GetCaps(primary2,&bufcaps); ok(rc==DS_OK,"IDirectSoundBuffer_GetCaps(primary2) failed: %08lx\n",rc); if (rc==DS_OK) { - todo_wine ok(bufcaps.dwFlags & DSBCAPS_CTRLVOLUME, "primary2 should have DSBCAPS_CTRLVOLUME: %08lx\n",bufcaps.dwFlags); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10735
Also note that DirectSoundDevice-\>ref can now be cleaned up, as there isn't any actual ref counting happening anymore, it is only ever set to 1, and then set to 0, which immediately destroys the device right after. However, I have opted to keep that cleanup out of this PR to keep it simple to review. I'll see if that can be handled in a follow up PR. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_137510
participants (2)
-
Jon Koops -
Jon Koops (@jonkoops)