-
Notifications
You must be signed in to change notification settings - Fork 391
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
Conversation
a08b4c9
to
494ef00
Compare
Thanks for doing all that research and sorting this out, this is awesome! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/host/wasapi/com.rs
Outdated
struct ComInitialized(*mut ()); | ||
struct ComInitialized { | ||
result: HRESULT, | ||
_ptr: *mut (), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ptr: *mut (), | |
_marker: PhantomData<*mut ()>, |
Using PhantomData<*mut T>
avoids the need to store a pointer while still removing Send
/Sync
.
src/host/wasapi/com.rs
Outdated
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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
Thanks @maroider for the suggested changes! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
This is a big one. i think it warrants a new release, don't you think? |
Yeah, let's have a release! |
@est31 I don't have publish perms on crates.io, would you mind doing the release? (just a friendly reminder) |
@ishitatsuyuki I think it should be enough to file a PR that increases the version. That will trigger the auto publish machinery. |
Got it, will do that tomorrow unless someone beat me to it. |
0.13.4 is out: #598 |
@ishitatsuyuki Thanks for thoroughly researching this issue! |
Thanks @ishitatsuyuki for doing this. Very much appreciated! |
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.
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.
# 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.~~
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
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
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
# 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.~~
# 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.~~
# 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.~~
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