Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasapi: Allow both threading models and switch the default to STA #597

Merged
merged 1 commit into from
Aug 8, 2021

Conversation

ishitatsuyuki
Copy link
Collaborator

@ishitatsuyuki ishitatsuyuki commented Aug 6, 2021

COM can prevent undefined behavior in either concurrency model by
performing marshaling when necessary. As a result, CoInitializeEx can be
called with either concurrency model, and in this case STA provides better
compatibility with other code requiring STA, e.g. ASIO backend or winit
drag-and-drop.

To dive into the detail, the entry point of WASAPI, MMDeviceEnumerator, is
registered with "both" threading model, which means that COM objects are
created with whatever the thread's concurrency model is set to. This raises
the concern that when STA is used, marshaling might make audio buffer
operations block on the main thread, breaking continuous audio processing.
However, the implementation actually uses free-threaded marshaller for
interfaces dealing with buffer operations, which effectively bypasses COM's
compatibility marshaling behavior and perform any API calls on the caller's
thread instead. Therefore, the interfaces would operate just fine on either
concurrency model.

For more details on COM's threading model, see [1].

[1] https://thrysoee.dk/InsideCOM+/ch04d.htm

Co-Authored-By: Henrik Rydgård hrydgard@gmail.com

Close #59
Close #348
Close #504
Close #530
Close #538
Close #572

@ishitatsuyuki
Copy link
Collaborator Author

cc @hrydgard @JoshuaBatty

@Ralith
Copy link
Contributor

Ralith commented Aug 7, 2021

Thanks for doing all that research and sorting this out, this is awesome!

Copy link

@maroider maroider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Echoing what @Ralith said: Thanks for fixing this long-standing issue!

I think this PR also fixes #530 and #572.

struct ComInitialized(*mut ());
struct ComInitialized {
result: HRESULT,
_ptr: *mut (),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ptr: *mut (),
_marker: PhantomData<*mut ()>,

Using PhantomData<*mut T> avoids the need to store a pointer while still removing Send/Sync.

// Try to initialize COM with STA by default to avoid compatibility issues with the ASIO
// backend (where CoInitialize() is called by the ASIO SDK) or winit (where drag and drop
// requires STA).
// this call can fail with RPC_E_CHANGED_MODE if another library initialized COM with MTA.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// this call can fail with RPC_E_CHANGED_MODE if another library initialized COM with MTA.
// This call can fail with RPC_E_CHANGED_MODE if another library initialized COM with MTA.

nit: capitalization

COM can prevent undefined behavior in either concurrency model by
performing marshaling when necessary. As a result, CoInitializeEx can be
called with either concurrency model, and in this case STA provides better
compatibility with other code requiring STA, e.g. ASIO backend or winit
drag-and-drop.

To dive into the detail, the entry point of WASAPI, MMDeviceEnumerator, is
registered with "both" threading model, which means that COM objects are
created with whatever the thread's concurrency model is set to. This raises
the concern that when STA is used, marshaling might make audio buffer
operations block on the main thread, breaking continuous audio processing.
However, the implementation actually uses free-threaded marshaller for
interfaces dealing with buffer operations, which effectively bypasses COM's
compatibility marshaling behavior and perform any API calls on the caller's
thread instead. Therefore, the interfaces would operate just fine on either
concurrency model.

For more details on COM's threading model, see [1].

[1] https://thrysoee.dk/InsideCOM+/ch04d.htm

Co-Authored-By: Henrik Rydgård <hrydgard@gmail.com>

Close RustAudio#59
Close RustAudio#348
Close RustAudio#504
Close RustAudio#530
Close RustAudio#538
Close RustAudio#572
@ishitatsuyuki
Copy link
Collaborator Author

Thanks @maroider for the suggested changes!

Copy link
Member

@est31 est31 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@est31 est31 merged commit 508618d into RustAudio:master Aug 8, 2021
@est31
Copy link
Member

est31 commented Aug 8, 2021

This is a big one. i think it warrants a new release, don't you think?

@ishitatsuyuki
Copy link
Collaborator Author

Yeah, let's have a release!

@ishitatsuyuki
Copy link
Collaborator Author

@est31 I don't have publish perms on crates.io, would you mind doing the release? (just a friendly reminder)

@est31
Copy link
Member

est31 commented Aug 8, 2021

@ishitatsuyuki I think it should be enough to file a PR that increases the version. That will trigger the auto publish machinery.

@ishitatsuyuki
Copy link
Collaborator Author

Got it, will do that tomorrow unless someone beat me to it.

@est31 est31 mentioned this pull request Aug 8, 2021
@est31
Copy link
Member

est31 commented Aug 8, 2021

0.13.4 is out: #598

@danwilhelm
Copy link
Contributor

@ishitatsuyuki Thanks for thoroughly researching this issue!

@JoshuaBatty
Copy link
Contributor

Thanks @ishitatsuyuki for doing this. Very much appreciated!

@ishitatsuyuki ishitatsuyuki deleted the com branch August 9, 2021 08:24
relrelb added a commit to relrelb/ruffle that referenced this pull request Sep 3, 2021
It was initialized on a separate thread since 6178dd9,
as a workaround for Windows.

But now thanks to RustAudio/cpal#597, this
is no longer needed, and cpal can be initialized on the same thread
as before.
relrelb added a commit to ruffle-rs/ruffle that referenced this pull request Sep 3, 2021
It was initialized on a separate thread since 6178dd9,
as a workaround for Windows.

But now thanks to RustAudio/cpal#597, this
is no longer needed, and cpal can be initialized on the same thread
as before.
bors bot pushed a commit to bevyengine/bevy that referenced this pull request Apr 4, 2022
# Objective

- Fixes #2096

## Solution

- This PR enables the drag-and-drop feature for winit on windows again, as the collision issue between cpal and winit has been fixed in RustAudio/cpal#597. I confirmed the drag and drop example working on windows 10 with this change.
- ~~It also bumps the rodio version, though this is not strictly necessary.~~
nyanpasu64 added a commit to nyanpasu64/Dn-FamiTracker that referenced this pull request Apr 17, 2022
CSoundInterface previously called CoInitializeEx and CoUninitialize in
its constructor and destructor. However this failed on Windows 7: On
startup or after changing configuration, CSoundInterface::OpenChannel
() failed to call IAudioClient::Initialize(), and it instead returned
0x800401f0 CO_E_NOTINITIALIZED. This is because CSoundInterface()
called CoInitializeEx() on the GUI thread, while
CSoundInterface::OpenChannel() is called on the audio thread. This is a
program bug, and Windows 10 and Wine happened to mask this error for
some reason.

To avoid this issue, call CoInitializeEx and CoUninitialize on the
GUI/audio threads' startup/teardown functions (CFamiTrackerApp and
CSoundGen::InitInstance/ExitInstance()), rather than the
CSoundInterface object's constructor and destructor.

Hopefully there aren't problems with accessing COM objects on the wrong
thread. It doesn't matter where IMMDeviceEnumerator/
IMMDeviceCollection/IMMDevice are constructed and accessed, since those
types are only accessed in startup/teardown(which don't need to be
real-time). All real-time logic talks to IAudioClient and
IAudioRenderClient, which are created on the audio thread in
CSoundInterface::OpenChannel() (and hopefully belong to the audio
thread, and if not they use a free-threaded marshaller anyway) and only
accessed on the audio thread.

COM threading is hard. RustAudio/cpal#597,
mozilla/cubeb#534
nyanpasu64 added a commit to nyanpasu64/Dn-FamiTracker that referenced this pull request May 18, 2022
CSoundInterface previously called CoInitializeEx and CoUninitialize in
its constructor and destructor. However this failed on Windows 7: On
startup or after changing configuration, CSoundInterface::OpenChannel
() failed to call IAudioClient::Initialize(), and it instead returned
0x800401f0 CO_E_NOTINITIALIZED. This is because CSoundInterface()
called CoInitializeEx() on the GUI thread, while
CSoundInterface::OpenChannel() is called on the audio thread. This is a
program bug, and Windows 10 and Wine happened to mask this error for
some reason.

To avoid this issue, call CoInitializeEx and CoUninitialize on the
GUI/audio threads' startup/teardown functions (CFamiTrackerApp and
CSoundGen::InitInstance/ExitInstance()), rather than the
CSoundInterface object's constructor and destructor.

Hopefully there aren't problems with accessing COM objects on the wrong
thread. It doesn't matter where IMMDeviceEnumerator/
IMMDeviceCollection/IMMDevice are constructed and accessed, since those
types are only accessed in startup/teardown(which don't need to be
real-time). All real-time logic talks to IAudioClient and
IAudioRenderClient, which are created on the audio thread in
CSoundInterface::OpenChannel() (and hopefully belong to the audio
thread, and if not they use a free-threaded marshaller anyway) and only
accessed on the audio thread.

COM threading is hard. RustAudio/cpal#597,
mozilla/cubeb#534
nyanpasu64 added a commit to nyanpasu64/Dn-FamiTracker that referenced this pull request May 19, 2022
CSoundInterface previously called CoInitializeEx and CoUninitialize in
its constructor and destructor. However this failed on Windows 7: On
startup or after changing configuration, CSoundInterface::OpenChannel
() failed to call IAudioClient::Initialize(), and it instead returned
0x800401f0 CO_E_NOTINITIALIZED. This is because CSoundInterface()
called CoInitializeEx() on the GUI thread, while
CSoundInterface::OpenChannel() is called on the audio thread. This is a
program bug, and Windows 10 and Wine happened to mask this error for
some reason.

To avoid this issue, call CoInitializeEx and CoUninitialize on the
GUI/audio threads' startup/teardown functions (CFamiTrackerApp and
CSoundGen::InitInstance/ExitInstance()), rather than the
CSoundInterface object's constructor and destructor.

Hopefully there aren't problems with accessing COM objects on the wrong
thread. It doesn't matter where IMMDeviceEnumerator/
IMMDeviceCollection/IMMDevice are constructed and accessed, since those
types are only accessed in startup/teardown(which don't need to be
real-time). All real-time logic talks to IAudioClient and
IAudioRenderClient, which are created on the audio thread in
CSoundInterface::OpenChannel() (and hopefully belong to the audio
thread, and if not they use a free-threaded marshaller anyway) and only
accessed on the audio thread.

COM threading is hard. RustAudio/cpal#597,
mozilla/cubeb#534
aevyrie pushed a commit to aevyrie/bevy that referenced this pull request Jun 7, 2022
# Objective

- Fixes bevyengine#2096

## Solution

- This PR enables the drag-and-drop feature for winit on windows again, as the collision issue between cpal and winit has been fixed in RustAudio/cpal#597. I confirmed the drag and drop example working on windows 10 with this change.
- ~~It also bumps the rodio version, though this is not strictly necessary.~~
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

- Fixes bevyengine#2096

## Solution

- This PR enables the drag-and-drop feature for winit on windows again, as the collision issue between cpal and winit has been fixed in RustAudio/cpal#597. I confirmed the drag and drop example working on windows 10 with this change.
- ~~It also bumps the rodio version, though this is not strictly necessary.~~
Subserial pushed a commit to Subserial/bevy_winit_hook that referenced this pull request Jan 24, 2024
# Objective

- Fixes bevyengine/bevy#2096

## Solution

- This PR enables the drag-and-drop feature for winit on windows again, as the collision issue between cpal and winit has been fixed in RustAudio/cpal#597. I confirmed the drag and drop example working on windows 10 with this change.
- ~~It also bumps the rodio version, though this is not strictly necessary.~~
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants