[APU] Manage XAudio 2.8 lifecycle in MTA thread + error handling cleanup
This commit is contained in:
parent
9606ff2a31
commit
0846cc026d
|
@ -50,8 +50,6 @@ XAudio2AudioDriver::XAudio2AudioDriver(Memory* memory,
|
||||||
XAudio2AudioDriver::~XAudio2AudioDriver() = default;
|
XAudio2AudioDriver::~XAudio2AudioDriver() = default;
|
||||||
|
|
||||||
bool XAudio2AudioDriver::Initialize() {
|
bool XAudio2AudioDriver::Initialize() {
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
voice_callback_ = new VoiceCallback(semaphore_);
|
voice_callback_ = new VoiceCallback(semaphore_);
|
||||||
|
|
||||||
// Load the XAudio2 DLL dynamically. Needed both for 2.7 and for
|
// Load the XAudio2 DLL dynamically. Needed both for 2.7 and for
|
||||||
|
@ -60,77 +58,63 @@ bool XAudio2AudioDriver::Initialize() {
|
||||||
// Windows 10 SDK references XAudio2_9.dll in it, which is only available in
|
// Windows 10 SDK references XAudio2_9.dll in it, which is only available in
|
||||||
// Windows 10, and XAudio2_8.dll is linked through a different .lib -
|
// Windows 10, and XAudio2_8.dll is linked through a different .lib -
|
||||||
// xaudio2_8.lib, so easier not to link the .lib at all.
|
// xaudio2_8.lib, so easier not to link the .lib at all.
|
||||||
xaudio2_module_ = reinterpret_cast<void*>(LoadLibraryW(L"XAudio2_8.dll"));
|
xaudio2_module_ = static_cast<void*>(LoadLibraryW(L"XAudio2_8.dll"));
|
||||||
if (xaudio2_module_) {
|
if (xaudio2_module_) {
|
||||||
api_minor_version_ = 8;
|
xaudio2_create_ = reinterpret_cast<decltype(xaudio2_create_)>(
|
||||||
} else {
|
GetProcAddress(static_cast<HMODULE>(xaudio2_module_), "XAudio2Create"));
|
||||||
xaudio2_module_ = reinterpret_cast<void*>(LoadLibraryW(L"XAudio2_7.dll"));
|
if (xaudio2_create_) {
|
||||||
|
api_minor_version_ = 8;
|
||||||
|
} else {
|
||||||
|
XELOGE("XAudio2Create not found in XAudio2_8.dll");
|
||||||
|
FreeLibrary(static_cast<HMODULE>(xaudio2_module_));
|
||||||
|
xaudio2_module_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!xaudio2_module_) {
|
||||||
|
xaudio2_module_ = static_cast<void*>(LoadLibraryW(L"XAudio2_7.dll"));
|
||||||
if (xaudio2_module_) {
|
if (xaudio2_module_) {
|
||||||
api_minor_version_ = 7;
|
api_minor_version_ = 7;
|
||||||
} else {
|
} else {
|
||||||
XELOGE("Failed to load XAudio 2.8 or 2.7 library DLL");
|
XELOGE("Failed to load XAudio 2.8 or 2.7 library DLL");
|
||||||
assert_always();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api_minor_version_ >= 8) {
|
// We need to be able to accept frames from any non-STA thread - primarily
|
||||||
union {
|
// from any guest thread, so MTA needs to be used. The AudioDriver, however,
|
||||||
// clang-format off
|
// may be initialized from the UI thread, which has the STA concurrency model,
|
||||||
HRESULT (__stdcall* xaudio2_create)(
|
// or from another thread regardless of its concurrency model. So, all
|
||||||
api::IXAudio2_8** xaudio2_out, UINT32 flags,
|
// management of the objects needs to be performed in MTA. Launch the
|
||||||
api::XAUDIO2_PROCESSOR xaudio2_processor);
|
// lifecycle management thread, which will handle initialization and shutdown,
|
||||||
// clang-format on
|
// and also provide a scope for implicit MTA in threads that have never
|
||||||
FARPROC xaudio2_create_ptr;
|
// initialized COM explicitly, which lasts until all threads that have
|
||||||
};
|
// initialized MTA explicitly have uninitialized it - the thread that holds
|
||||||
xaudio2_create_ptr = GetProcAddress(
|
// the MTA scope needs to be running while other threads are able to submit
|
||||||
reinterpret_cast<HMODULE>(xaudio2_module_), "XAudio2Create");
|
// frames as they might have not initialized MTA explicitly.
|
||||||
if (!xaudio2_create_ptr) {
|
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||||
XELOGE("XAudio2Create not found in XAudio2_8.dll");
|
// This is needed for both XAudio 2.7 (created via explicit CoCreateInstance)
|
||||||
assert_always();
|
// and 2.8 (using XAudio2Create, but still requiring CoInitializeEx).
|
||||||
return false;
|
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/how-to--initialize-xaudio2
|
||||||
}
|
assert_false(mta_thread_.joinable());
|
||||||
hr = xaudio2_create(&objects_.api_2_8.audio, 0, kProcessor);
|
mta_thread_initialization_attempt_completed_ = false;
|
||||||
if (FAILED(hr)) {
|
mta_thread_shutdown_requested_ = false;
|
||||||
XELOGE("XAudio2Create failed with {:08X}", hr);
|
mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this);
|
||||||
assert_always();
|
{
|
||||||
return false;
|
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||||
}
|
mta_thread_initialization_completion_mutex_);
|
||||||
return InitializeObjects(objects_.api_2_8);
|
while (true) {
|
||||||
} else {
|
if (mta_thread_initialization_attempt_completed_) {
|
||||||
// We need to be able to accept frames from any non-STA thread - primarily
|
break;
|
||||||
// from any guest thread, so MTA needs to be used. The AudioDriver, however,
|
|
||||||
// may be initialized from the UI thread, which has the STA concurrency
|
|
||||||
// model, or from another thread regardless of its concurrency model. So,
|
|
||||||
// all management of the objects needs to be performed in MTA. Launch the
|
|
||||||
// lifecycle management thread, which will handle initialization and
|
|
||||||
// shutdown, and also provide a scope for implicit MTA in threads that have
|
|
||||||
// never initialized COM explicitly, which lasts until all threads that have
|
|
||||||
// initialized MTA explicitly have uninitialized it - the thread that holds
|
|
||||||
// the MTA scope needs to be running while other threads are able to submit
|
|
||||||
// frames as they might have not initialized MTA explicitly.
|
|
||||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
|
||||||
assert_false(mta_thread_.joinable());
|
|
||||||
mta_thread_initialization_attempt_completed_ = false;
|
|
||||||
mta_thread_shutdown_requested_ = false;
|
|
||||||
mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this);
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
|
||||||
mta_thread_initialization_completion_mutex_);
|
|
||||||
while (true) {
|
|
||||||
if (mta_thread_initialization_attempt_completed_) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mta_thread_initialization_completion_cond_.wait(
|
|
||||||
mta_thread_initialization_completion_lock);
|
|
||||||
}
|
}
|
||||||
|
mta_thread_initialization_completion_cond_.wait(
|
||||||
|
mta_thread_initialization_completion_lock);
|
||||||
}
|
}
|
||||||
if (!mta_thread_initialization_completion_result_) {
|
|
||||||
mta_thread_.join();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (!mta_thread_initialization_completion_result_) {
|
||||||
|
mta_thread_.join();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Objects>
|
template <typename Objects>
|
||||||
|
@ -148,8 +132,8 @@ bool XAudio2AudioDriver::InitializeObjects(Objects& objects) {
|
||||||
|
|
||||||
hr = objects.audio->CreateMasteringVoice(&objects.mastering_voice);
|
hr = objects.audio->CreateMasteringVoice(&objects.mastering_voice);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("IXAudio2::CreateMasteringVoice failed with {:08X}", hr);
|
XELOGE("IXAudio2::CreateMasteringVoice failed with 0x{:08X}", hr);
|
||||||
assert_always();
|
ShutdownObjects(objects);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,15 +170,15 @@ bool XAudio2AudioDriver::InitializeObjects(Objects& objects) {
|
||||||
0, // api::XE_XAUDIO2_VOICE_NOSRC | api::XE_XAUDIO2_VOICE_NOPITCH,
|
0, // api::XE_XAUDIO2_VOICE_NOSRC | api::XE_XAUDIO2_VOICE_NOPITCH,
|
||||||
api::XE_XAUDIO2_MAX_FREQ_RATIO, voice_callback_);
|
api::XE_XAUDIO2_MAX_FREQ_RATIO, voice_callback_);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("IXAudio2::CreateSourceVoice failed with {:08X}", hr);
|
XELOGE("IXAudio2::CreateSourceVoice failed with 0x{:08X}", hr);
|
||||||
assert_always();
|
ShutdownObjects(objects);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = objects.pcm_voice->Start();
|
hr = objects.pcm_voice->Start();
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("IXAudio2SourceVoice::Start failed with {:08X}", hr);
|
XELOGE("IXAudio2SourceVoice::Start failed with 0x{:08X}", hr);
|
||||||
assert_always();
|
ShutdownObjects(objects);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +226,7 @@ void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
|
||||||
hr = objects_.api_2_7.pcm_voice->SubmitSourceBuffer(&buffer);
|
hr = objects_.api_2_7.pcm_voice->SubmitSourceBuffer(&buffer);
|
||||||
}
|
}
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("SubmitSourceBuffer failed with {:08X}", hr);
|
XELOGE("SubmitSourceBuffer failed with 0x{:08X}", hr);
|
||||||
assert_always();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,23 +243,20 @@ void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void XAudio2AudioDriver::Shutdown() {
|
void XAudio2AudioDriver::Shutdown() {
|
||||||
if (api_minor_version_ >= 8) {
|
// XAudio2 lifecycle is managed by the MTA thread.
|
||||||
ShutdownObjects(objects_.api_2_8);
|
if (mta_thread_.joinable()) {
|
||||||
} else {
|
{
|
||||||
// XAudio 2.7 lifecycle is managed by the MTA thread.
|
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||||
if (mta_thread_.joinable()) {
|
mta_thread_shutdown_request_mutex_);
|
||||||
{
|
mta_thread_shutdown_requested_ = true;
|
||||||
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
|
||||||
mta_thread_shutdown_request_mutex_);
|
|
||||||
mta_thread_shutdown_requested_ = true;
|
|
||||||
}
|
|
||||||
mta_thread_shutdown_request_cond_.notify_all();
|
|
||||||
mta_thread_.join();
|
|
||||||
}
|
}
|
||||||
|
mta_thread_shutdown_request_cond_.notify_all();
|
||||||
|
mta_thread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xaudio2_create_ = nullptr;
|
||||||
if (xaudio2_module_) {
|
if (xaudio2_module_) {
|
||||||
FreeLibrary(reinterpret_cast<HMODULE>(xaudio2_module_));
|
FreeLibrary(static_cast<HMODULE>(xaudio2_module_));
|
||||||
xaudio2_module_ = nullptr;
|
xaudio2_module_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +287,7 @@ void XAudio2AudioDriver::ShutdownObjects(Objects& objects) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void XAudio2AudioDriver::MTAThread() {
|
void XAudio2AudioDriver::MTAThread() {
|
||||||
xe::threading::set_name("XAudio 2.7 MTA");
|
xe::threading::set_name("XAudio2 MTA");
|
||||||
|
|
||||||
assert_false(mta_thread_initialization_attempt_completed_);
|
assert_false(mta_thread_initialization_attempt_completed_);
|
||||||
|
|
||||||
|
@ -315,70 +295,72 @@ void XAudio2AudioDriver::MTAThread() {
|
||||||
|
|
||||||
// Initializing MTA COM in this thread, as well making other (guest) threads
|
// Initializing MTA COM in this thread, as well making other (guest) threads
|
||||||
// that don't explicitly call CoInitializeEx implicitly MTA for the period of
|
// that don't explicitly call CoInitializeEx implicitly MTA for the period of
|
||||||
// time when they can interact with XAudio 2.7 through the XAudio2AudioDriver,
|
// time when they can interact with XAudio through the XAudio2AudioDriver,
|
||||||
// until the CoUninitialize (to be more precise, the CoUninitialize for the
|
// until the CoUninitialize (to be more precise, the CoUninitialize for the
|
||||||
// last remaining MTA thread, but we need implicit MTA for the audio here).
|
// last remaining MTA thread, but we need implicit MTA for the audio here).
|
||||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
if (FAILED(hr)) {
|
bool com_initialized = SUCCEEDED(hr);
|
||||||
XELOGE("XAudio 2.7 MTA thread CoInitializeEx failed with {:08X}", hr);
|
if (!com_initialized) {
|
||||||
|
XELOGE("XAudio2 MTA thread CoInitializeEx failed with 0x{:08X}", hr);
|
||||||
} else {
|
} else {
|
||||||
hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr,
|
if (api_minor_version_ >= 8) {
|
||||||
CLSCTX_INPROC_SERVER,
|
hr = xaudio2_create_(&objects_.api_2_8.audio, 0, kProcessor);
|
||||||
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr);
|
|
||||||
} else {
|
|
||||||
hr = objects_.api_2_7.audio->Initialize(0, kProcessor);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("IXAudio2::Initialize failed with {:08X}", hr);
|
XELOGE("XAudio2Create failed with 0x{:08X}", hr);
|
||||||
} else {
|
} else {
|
||||||
if (InitializeObjects(objects_.api_2_7)) {
|
initialized = InitializeObjects(objects_.api_2_8);
|
||||||
initialized = true;
|
}
|
||||||
|
} else {
|
||||||
// Initialized successfully, await a shutdown request while keeping an
|
hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr,
|
||||||
// implicit COM MTA scope.
|
CLSCTX_INPROC_SERVER,
|
||||||
|
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
||||||
mta_thread_initialization_completion_result_ = true;
|
if (FAILED(hr)) {
|
||||||
{
|
XELOGE("CoCreateInstance for XAudio2 failed with 0x{:08X}", hr);
|
||||||
std::unique_lock<std::mutex>
|
} else {
|
||||||
mta_thread_initialization_completion_lock(
|
hr = objects_.api_2_7.audio->Initialize(0, kProcessor);
|
||||||
mta_thread_initialization_completion_mutex_);
|
if (FAILED(hr)) {
|
||||||
mta_thread_initialization_attempt_completed_ = true;
|
XELOGE("IXAudio2::Initialize failed with 0x{:08X}", hr);
|
||||||
}
|
} else {
|
||||||
mta_thread_initialization_completion_cond_.notify_all();
|
initialized = InitializeObjects(objects_.api_2_7);
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
|
||||||
mta_thread_shutdown_request_mutex_);
|
|
||||||
while (true) {
|
|
||||||
if (mta_thread_shutdown_requested_) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mta_thread_shutdown_request_cond_.wait(
|
|
||||||
mta_thread_shutdown_request_lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if InitializeObjects has failed, need to clean up with
|
|
||||||
// ShutdownObjects.
|
|
||||||
ShutdownObjects(objects_.api_2_7);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoUninitialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initialized) {
|
// Notify the threads waiting for the initialization of the result.
|
||||||
mta_thread_initialization_completion_result_ = false;
|
mta_thread_initialization_completion_result_ = initialized;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||||
|
mta_thread_initialization_completion_mutex_);
|
||||||
|
mta_thread_initialization_attempt_completed_ = true;
|
||||||
|
}
|
||||||
|
mta_thread_initialization_completion_cond_.notify_all();
|
||||||
|
|
||||||
|
if (initialized) {
|
||||||
|
// Initialized successfully, await a shutdown request while keeping an
|
||||||
|
// implicit COM MTA scope.
|
||||||
{
|
{
|
||||||
// Failed to initialize - notify the threads waiting for the
|
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||||
// initialization.
|
mta_thread_shutdown_request_mutex_);
|
||||||
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
while (true) {
|
||||||
mta_thread_initialization_completion_mutex_);
|
if (mta_thread_shutdown_requested_) {
|
||||||
mta_thread_initialization_attempt_completed_ = true;
|
break;
|
||||||
|
}
|
||||||
|
mta_thread_shutdown_request_cond_.wait(
|
||||||
|
mta_thread_shutdown_request_lock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mta_thread_initialization_completion_cond_.notify_all();
|
|
||||||
|
if (api_minor_version_ >= 8) {
|
||||||
|
ShutdownObjects(objects_.api_2_8);
|
||||||
|
} else {
|
||||||
|
ShutdownObjects(objects_.api_2_7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (com_initialized) {
|
||||||
|
CoUninitialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class XAudio2AudioDriver : public AudioDriver {
|
||||||
~XAudio2AudioDriver() override;
|
~XAudio2AudioDriver() override;
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
// Must not be called from COM STA threads as MTA XAudio 2.7 may be used. It's
|
// Must not be called from COM STA threads as MTA XAudio2 will be used. It's
|
||||||
// fine to call this from threads that have never initialized COM as
|
// fine to call this from threads that have never initialized COM as
|
||||||
// initializing MTA for any thread implicitly initializes it for all threads
|
// initializing MTA for any thread implicitly initializes it for all threads
|
||||||
// not explicitly requesting STA (until COM is uninitialized all threads that
|
// not explicitly requesting STA (until COM is uninitialized all threads that
|
||||||
|
@ -49,8 +49,8 @@ class XAudio2AudioDriver : public AudioDriver {
|
||||||
// even beyond the 6 guest cores.
|
// even beyond the 6 guest cores.
|
||||||
api::XAUDIO2_PROCESSOR kProcessor = 0x00000001;
|
api::XAUDIO2_PROCESSOR kProcessor = 0x00000001;
|
||||||
|
|
||||||
// For XAudio 2.7, InitializeObjects and ShutdownObjects must be called only
|
// InitializeObjects and ShutdownObjects must be called only in the lifecycle
|
||||||
// in the lifecycle management thread with COM MTA initialized.
|
// management thread with COM MTA initialized.
|
||||||
template <typename Objects>
|
template <typename Objects>
|
||||||
bool InitializeObjects(Objects& objects);
|
bool InitializeObjects(Objects& objects);
|
||||||
template <typename Objects>
|
template <typename Objects>
|
||||||
|
@ -59,6 +59,11 @@ class XAudio2AudioDriver : public AudioDriver {
|
||||||
void MTAThread();
|
void MTAThread();
|
||||||
|
|
||||||
void* xaudio2_module_ = nullptr;
|
void* xaudio2_module_ = nullptr;
|
||||||
|
// clang-format off
|
||||||
|
HRESULT (__stdcall* xaudio2_create_)(
|
||||||
|
api::IXAudio2_8** xaudio2_out, UINT32 flags,
|
||||||
|
api::XAUDIO2_PROCESSOR xaudio2_processor) = nullptr;
|
||||||
|
// clang-format on
|
||||||
uint32_t api_minor_version_ = 7;
|
uint32_t api_minor_version_ = 7;
|
||||||
|
|
||||||
bool mta_thread_initialization_completion_result_;
|
bool mta_thread_initialization_completion_result_;
|
||||||
|
|
Loading…
Reference in New Issue