[PATCH v6 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 -- v6: 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 | 56 ++++++++++++++++++++++++++++++++++++ dlls/dsound/tests/dsound8.c | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/dlls/dsound/tests/dsound.c b/dlls/dsound/tests/dsound.c index a7d6fa0fc3b..9fb46c1fdef 100644 --- a/dlls/dsound/tests/dsound.c +++ b/dlls/dsound/tests/dsound.c @@ -2192,6 +2192,61 @@ 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) +{ + DSBUFFERDESC bufdesc = {.dwSize = sizeof(bufdesc)}; + IDirectSoundBuffer *primary1, *primary2; + IDirectSound *dso1, *dso2; + DSBCAPS caps; + HRESULT hr; + LONG vol; + + hr = DirectSoundCreate(NULL, &dso1, NULL); + ok(hr == DS_OK || hr == DSERR_NODRIVER || hr == DSERR_ALLOCATED || hr == E_FAIL, + "DirectSoundCreate() failed: %08lx\n", hr); + if (FAILED(hr)) + return; + + hr = DirectSoundCreate(NULL, &dso2, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + hr = IDirectSound_SetCooperativeLevel(dso1, get_hwnd(), DSSCL_PRIORITY); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + hr = IDirectSound_SetCooperativeLevel(dso2, get_hwnd(), DSSCL_PRIORITY); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Create a primary buffer on dso1 with CTRL3D but without CTRLVOLUME */ + bufdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D; + hr = IDirectSound_CreateSoundBuffer(dso1, &bufdesc, &primary1, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Create a primary buffer on dso2 with CTRLVOLUME */ + bufdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; + hr = IDirectSound_CreateSoundBuffer(dso2, &bufdesc, &primary2, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* 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 */ + hr = IDirectSoundBuffer_GetVolume(primary2, &vol); + todo_wine ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Verify dso2's primary buffer has CTRLVOLUME */ + memset(&caps, 0, sizeof(caps)); + caps.dwSize = sizeof(caps); + hr = IDirectSoundBuffer_GetCaps(primary2, &caps); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + todo_wine ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); + + IDirectSoundBuffer_Release(primary2); + IDirectSoundBuffer_Release(primary1); + IDirectSound_Release(dso2); + IDirectSound_Release(dso1); +} + START_TEST(dsound) { CoInitialize(NULL); @@ -2201,6 +2256,7 @@ START_TEST(dsound) IDirectSound_tests(); dsound_tests(); test_hw_buffers(); + test_primary_independent(); CoUninitialize(); } diff --git a/dlls/dsound/tests/dsound8.c b/dlls/dsound/tests/dsound8.c index d0e3b4d6a41..ded85c84a3c 100644 --- a/dlls/dsound/tests/dsound8.c +++ b/dlls/dsound/tests/dsound8.c @@ -1931,6 +1931,61 @@ 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) +{ + DSBUFFERDESC bufdesc = {.dwSize = sizeof(bufdesc)}; + IDirectSoundBuffer *primary1, *primary2; + IDirectSound8 *dso1, *dso2; + DSBCAPS caps; + HRESULT hr; + LONG vol; + + hr = DirectSoundCreate8(NULL, &dso1, NULL); + ok(hr == DS_OK || hr == DSERR_NODRIVER || hr == DSERR_ALLOCATED || hr == E_FAIL, + "DirectSoundCreate8() failed: %08lx\n", hr); + if (FAILED(hr)) + return; + + hr = DirectSoundCreate8(NULL, &dso2, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + hr = IDirectSound8_SetCooperativeLevel(dso1, get_hwnd(), DSSCL_PRIORITY); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + hr = IDirectSound8_SetCooperativeLevel(dso2, get_hwnd(), DSSCL_PRIORITY); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Create a primary buffer on dso1 with CTRL3D but without CTRLVOLUME */ + bufdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D; + hr = IDirectSound8_CreateSoundBuffer(dso1, &bufdesc, &primary1, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Create a primary buffer on dso2 with CTRLVOLUME */ + bufdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; + hr = IDirectSound8_CreateSoundBuffer(dso2, &bufdesc, &primary2, NULL); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* 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 */ + hr = IDirectSoundBuffer_GetVolume(primary2, &vol); + todo_wine ok(hr == DS_OK, "Got hr %#lx.\n", hr); + + /* Verify dso2's primary buffer has CTRLVOLUME */ + memset(&caps, 0, sizeof(caps)); + caps.dwSize = sizeof(caps); + hr = IDirectSoundBuffer_GetCaps(primary2, &caps); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); + todo_wine ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); + + IDirectSoundBuffer_Release(primary2); + IDirectSoundBuffer_Release(primary1); + IDirectSound8_Release(dso2); + IDirectSound8_Release(dso1); +} + START_TEST(dsound8) { DWORD cookie; @@ -1956,5 +2011,7 @@ START_TEST(dsound8) CoRevokeClassObject(cookie); + 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 | 6 +++--- dlls/dsound/tests/dsound8.c | 6 +++--- 3 files changed, 6 insertions(+), 23 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 9fb46c1fdef..44b5136d7be 100644 --- a/dlls/dsound/tests/dsound.c +++ b/dlls/dsound/tests/dsound.c @@ -2227,19 +2227,19 @@ static void test_primary_independent(void) ok(hr == DS_OK, "Got hr %#lx.\n", hr); /* Check whether the two IDirectSound objects share a primary buffer */ - todo_wine ok(primary1 != primary2, + ok(primary1 != primary2, "Two IDirectSound objects should have independent primary buffers\n"); /* GetVolume on dso2's primary buffer should succeed */ hr = IDirectSoundBuffer_GetVolume(primary2, &vol); - todo_wine ok(hr == DS_OK, "Got hr %#lx.\n", hr); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); /* Verify dso2's primary buffer has CTRLVOLUME */ memset(&caps, 0, sizeof(caps)); caps.dwSize = sizeof(caps); hr = IDirectSoundBuffer_GetCaps(primary2, &caps); ok(hr == DS_OK, "Got hr %#lx.\n", hr); - todo_wine ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); + ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); IDirectSoundBuffer_Release(primary2); IDirectSoundBuffer_Release(primary1); diff --git a/dlls/dsound/tests/dsound8.c b/dlls/dsound/tests/dsound8.c index ded85c84a3c..07b10af45e0 100644 --- a/dlls/dsound/tests/dsound8.c +++ b/dlls/dsound/tests/dsound8.c @@ -1966,19 +1966,19 @@ static void test_primary_independent(void) ok(hr == DS_OK, "Got hr %#lx.\n", hr); /* Check whether the two IDirectSound objects share a primary buffer */ - todo_wine ok(primary1 != primary2, + ok(primary1 != primary2, "Two IDirectSound objects should have independent primary buffers\n"); /* GetVolume on dso2's primary buffer should succeed */ hr = IDirectSoundBuffer_GetVolume(primary2, &vol); - todo_wine ok(hr == DS_OK, "Got hr %#lx.\n", hr); + ok(hr == DS_OK, "Got hr %#lx.\n", hr); /* Verify dso2's primary buffer has CTRLVOLUME */ memset(&caps, 0, sizeof(caps)); caps.dwSize = sizeof(caps); hr = IDirectSoundBuffer_GetCaps(primary2, &caps); ok(hr == DS_OK, "Got hr %#lx.\n", hr); - todo_wine ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); + ok(caps.dwFlags & DSBCAPS_CTRLVOLUME, "Unexpected dwFlags %#lx.\n", caps.dwFlags); IDirectSoundBuffer_Release(primary2); IDirectSoundBuffer_Release(primary1); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10735
On Fri May 1 15:34:59 2026 +0000, Jon Koops wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/10735/diffs?diff_id=264715&start_sha=83d80459bdd06ab123063d8eb4fd4305a5fe44fb#96748e63d710204a30c174f68d1a129a66b2ee83_2218_2210) Done. The second DirectSoundCreate now simply asserts `DS_OK`.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138537
On Fri May 1 15:34:59 2026 +0000, Jon Koops wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/10735/diffs?diff_id=264715&start_sha=83d80459bdd06ab123063d8eb4fd4305a5fe44fb#96748e63d710204a30c174f68d1a129a66b2ee83_2223_2213) Removed all defensive if/goto guards, only the first `DirectSoundCreate` call is checked and bailed out on if needed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138538
On Fri May 1 15:34:59 2026 +0000, Jon Koops wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/10735/diffs?diff_id=264715&start_sha=83d80459bdd06ab123063d8eb4fd4305a5fe44fb#96748e63d710204a30c174f68d1a129a66b2ee83_2238_2220) Replaced all these defensive checks with simple `ok()` calls like the other tests.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138539
On Tue Apr 28 10:49:15 2026 +0000, Matteo Bruni wrote:
```suggestion:-6+0 hr = IDirectSoundBuffer_GetVolume(primary2, &vol); todo_wine ok(hr == DS_OK, "Unexpected hr %08lx\n", hr); ```
Done. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138540
On Fri May 1 15:34:59 2026 +0000, Jon Koops wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/10735/diffs?diff_id=264715&start_sha=83d80459bdd06ab123063d8eb4fd4305a5fe44fb#96748e63d710204a30c174f68d1a129a66b2ee83_2267_2238) Done.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138541
On Fri May 1 15:38:48 2026 +0000, Matteo Bruni wrote:
The MR looks good in principle. It's a bit unfortunate that you happened to follow the style of older tests, which I find quite ugly, but alas. One thing that I'd like to see here is a second "instance" of the same test but checking `DirectSoundCreate8()` (see dsound8.c). It's probably unlikely that it will show different results, but this is a good time to look for that. I went ahead and added the exact same test to `dsound8` as well.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138542
On Tue Apr 28 12:28:42 2026 +0000, Matteo Bruni wrote:
My preference would be for something like my suggested changes, or the test immediately above yours. To be clear, there's nothing wrong on your part: I'm just a bit annoyed that there's so much old code around that makes for bad style examples :sweat_smile: I took a look at the more recently created tests and tried to match their conventions as closely as possible. If you like I can spend some time porting over the old test style for dsound and make sure they are all consistent, which would be a follow up PR, lmk.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10735#note_138543
participants (2)
-
Jon Koops -
Jon Koops (@jonkoops)