From a702242665e88441a93f9185f52b9b046884409b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 26 Apr 2026 18:38:07 +0200 Subject: [PATCH] Call more VST3 functions from the UI thread These functions have had a UI thread requirement added after VST 3.7.7. This change may cause some breakage because of the mutual recursion requirements. I decided to not do this for most of the simpler getter-style functions that the VST3 specification now for some reason requires to be called from the UI thread, as this could significantly hurt performance and changing this is more likely to cause problems than leaving it alone is. --- src/wine-host/bridges/vst3.cpp | 401 +++++++++++++++++++++------------ 1 file changed, 261 insertions(+), 140 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 49c33a94..0578c25f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -273,23 +273,33 @@ void Vst3Bridge::run() { .result = result, .state = std::move(request.state)}; }, [&](YaAudioPresentationLatency::SetAudioPresentationLatencySamples& - request) -> YaAudioPresentationLatency:: - SetAudioPresentationLatencySamples::Response { - const auto& [instance, _] = - get_instance(request.instance_id); + request) + -> YaAudioPresentationLatency:: + SetAudioPresentationLatencySamples::Response { + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces - .audio_presentation_latency - ->setAudioPresentationLatencySamples( - request.dir, request.bus_index, - request.latency_in_samples); - }, + return instance.interfaces + .audio_presentation_latency + ->setAudioPresentationLatencySamples( + request.dir, request.bus_index, + request.latency_in_samples); + }) + .get(); + }, [&](YaAutomationState::SetAutomationState& request) -> YaAutomationState::SetAutomationState::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.automation_state->setAutomationState( - request.state); + return instance.interfaces.automation_state + ->setAutomationState(request.state); + }) + .get(); }, [&](YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { @@ -303,56 +313,70 @@ void Vst3Bridge::run() { // on the Wine side. If we cannot do that, then we'll still go // through the host's connection proxy connection proxy (and // we'll end up proxying the host's connection proxy). - return std::visit( - overload{ - [&](const native_size_t& other_instance_id) -> tresult { - const auto& [this_instance, _] = - get_instance(request.instance_id); - const auto& [other_instance, _2] = - get_instance(other_instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + return std::visit( + overload{ + [&](const native_size_t& other_instance_id) + -> tresult { + const auto& [this_instance, _] = + get_instance(request.instance_id); + const auto& [other_instance, _2] = + get_instance(other_instance_id); - return this_instance.interfaces.connection_point - ->connect( - other_instance.interfaces.connection_point); - }, - [&](Vst3ConnectionPointProxy::ConstructArgs& args) - -> tresult { - const auto& [this_instance, _] = - get_instance(request.instance_id); + return this_instance.interfaces + .connection_point->connect( + other_instance.interfaces + .connection_point); + }, + [&](Vst3ConnectionPointProxy::ConstructArgs& + args) -> tresult { + const auto& [this_instance, _] = + get_instance(request.instance_id); - this_instance.connection_point_proxy = - Steinberg::owned( - new Vst3ConnectionPointProxyImpl( - *this, std::move(args))); + this_instance.connection_point_proxy = + Steinberg::owned( + new Vst3ConnectionPointProxyImpl( + *this, std::move(args))); - return this_instance.interfaces.connection_point - ->connect(this_instance.connection_point_proxy); - }}, - request.other); + return this_instance.interfaces + .connection_point->connect( + this_instance + .connection_point_proxy); + }}, + request.other); + }) + .get(); }, [&](const YaConnectionPoint::Disconnect& request) -> YaConnectionPoint::Disconnect::Response { - const auto& [this_instance, _] = - get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [this_instance, _] = + get_instance(request.instance_id); - // If the objects were connected directly we can also disconnect - // them directly. Otherwise we'll disconnect them from our proxy - // object and then destroy that proxy object. - if (request.other_instance_id) { - const auto& [other_instance, _2] = - get_instance(*request.other_instance_id); + // If the objects were connected directly we can also + // disconnect them directly. Otherwise we'll disconnect + // them from our proxy object and then destroy that + // proxy object. + if (request.other_instance_id) { + const auto& [other_instance, _2] = + get_instance(*request.other_instance_id); - return this_instance.interfaces.connection_point - ->disconnect( - other_instance.interfaces.connection_point); - } else { - const tresult result = - this_instance.interfaces.connection_point->disconnect( - this_instance.connection_point_proxy); - this_instance.connection_point_proxy.reset(); + return this_instance.interfaces.connection_point + ->disconnect( + other_instance.interfaces.connection_point); + } else { + const tresult result = + this_instance.interfaces.connection_point + ->disconnect( + this_instance.connection_point_proxy); + this_instance.connection_point_proxy.reset(); - return result; - } + return result; + } + }) + .get(); }, [&](const YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { @@ -395,10 +419,15 @@ void Vst3Bridge::run() { }, [&](YaEditController::SetComponentState& request) -> YaEditController::SetComponentState::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.edit_controller->setComponentState( - &request.state); + return instance.interfaces.edit_controller + ->setComponentState(&request.state); + }) + .get(); }, [&](const YaEditController::GetParameterInfos& request) -> YaEditController::GetParameterInfos::Response { @@ -496,23 +525,33 @@ void Vst3Bridge::run() { }, [&](YaEditController::SetComponentHandler& request) -> YaEditController::SetComponentHandler::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - // If the host passed a valid component handler, then we'll - // create a proxy object for the component handler and pass that - // to the initialize function. The lifetime of this object is - // tied to that of the actual plugin object we're proxying for. - // Otherwise we'll also pass a null pointer. This often happens - // just before the host terminates the plugin. - instance.component_handler_proxy = - request.component_handler_proxy_args - ? Steinberg::owned(new Vst3ComponentHandlerProxyImpl( - *this, - std::move(*request.component_handler_proxy_args))) - : nullptr; + // If the host passed a valid component handler, then + // we'll create a proxy object for the component handler + // and pass that to the initialize function. The + // lifetime of this object is tied to that of the actual + // plugin object we're proxying for. Otherwise we'll + // also pass a null pointer. This often happens just + // before the host terminates the plugin. + instance.component_handler_proxy = + request.component_handler_proxy_args + ? Steinberg::owned( + new Vst3ComponentHandlerProxyImpl( + *this, + std::move( + *request + .component_handler_proxy_args))) + : nullptr; - return instance.interfaces.edit_controller->setComponentHandler( - instance.component_handler_proxy); + return instance.interfaces.edit_controller + ->setComponentHandler( + instance.component_handler_proxy); + }) + .get(); }, [&](const YaEditController::CreateView& request) -> YaEditController::CreateView::Response { @@ -554,6 +593,8 @@ void Vst3Bridge::run() { }, [&](const YaEditController2::SetKnobMode& request) -> YaEditController2::SetKnobMode::Response { + // We'll ignore the UI thread requirement for the parameter + // functions const auto& [instance, _] = get_instance(request.instance_id); return instance.interfaces.edit_controller_2->setKnobMode( @@ -561,20 +602,32 @@ void Vst3Bridge::run() { }, [&](const YaEditController2::OpenHelp& request) -> YaEditController2::OpenHelp::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.edit_controller_2->openHelp( - request.only_check); + return instance.interfaces.edit_controller_2->openHelp( + request.only_check); + }) + .get(); }, [&](const YaEditController2::OpenAboutBox& request) -> YaEditController2::OpenAboutBox::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.edit_controller_2->openAboutBox( - request.only_check); + return instance.interfaces.edit_controller_2 + ->openAboutBox(request.only_check); + }) + .get(); }, [&](const YaEditControllerHostEditing::BeginEditFromHost& request) -> YaEditControllerHostEditing::BeginEditFromHost::Response { + // We'll ignore the UI thread requirement for the parameter + // functions const auto& [instance, _] = get_instance(request.instance_id); return instance.interfaces.edit_controller_host_editing @@ -582,6 +635,8 @@ void Vst3Bridge::run() { }, [&](const YaEditControllerHostEditing::EndEditFromHost& request) -> YaEditControllerHostEditing::EndEditFromHost::Response { + // We'll ignore the UI thread requirement for the parameter + // functions const auto& [instance, _] = get_instance(request.instance_id); return instance.interfaces.edit_controller_host_editing @@ -624,11 +679,17 @@ void Vst3Bridge::run() { }, [&](const YaMidiLearn::OnLiveMIDIControllerInput& request) -> YaMidiLearn::OnLiveMIDIControllerInput::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.midi_learn - ->onLiveMIDIControllerInput( - request.bus_index, request.channel, request.midi_cc); + return instance.interfaces.midi_learn + ->onLiveMIDIControllerInput(request.bus_index, + request.channel, + request.midi_cc); + }) + .get(); }, [&](const YaMidiMapping::GetMidiControllerAssignment& request) -> YaMidiMapping::GetMidiControllerAssignment::Response { @@ -1181,21 +1242,37 @@ void Vst3Bridge::run() { }, [&](YaProgramListData::GetProgramData& request) -> YaProgramListData::GetProgramData::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context( + [&]() -> YaProgramListData::GetProgramDataResponse { + const auto& [instance, _] = + get_instance(request.instance_id); - const tresult result = - instance.interfaces.program_list_data->getProgramData( - request.list_id, request.program_index, &request.data); + const tresult result = + instance.interfaces.program_list_data + ->getProgramData(request.list_id, + request.program_index, + &request.data); - return YaProgramListData::GetProgramDataResponse{ - .result = result, .data = std::move(request.data)}; + return YaProgramListData::GetProgramDataResponse{ + .result = result, + .data = std::move(request.data)}; + }) + .get(); }, [&](YaProgramListData::SetProgramData& request) -> YaProgramListData::SetProgramData::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.program_list_data->setProgramData( - request.list_id, request.program_index, &request.data); + return instance.interfaces.program_list_data + ->setProgramData(request.list_id, + request.program_index, + &request.data); + }) + .get(); }, [&](const YaUnitData::UnitDataSupported& request) -> YaUnitData::UnitDataSupported::Response { @@ -1206,21 +1283,31 @@ void Vst3Bridge::run() { }, [&](YaUnitData::GetUnitData& request) -> YaUnitData::GetUnitData::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> YaUnitData::GetUnitDataResponse { + const auto& [instance, _] = + get_instance(request.instance_id); - const tresult result = - instance.interfaces.unit_data->getUnitData(request.unit_id, - &request.data); + const tresult result = + instance.interfaces.unit_data->getUnitData( + request.unit_id, &request.data); - return YaUnitData::GetUnitDataResponse{ - .result = result, .data = std::move(request.data)}; + return YaUnitData::GetUnitDataResponse{ + .result = result, .data = std::move(request.data)}; + }) + .get(); }, [&](YaUnitData::SetUnitData& request) -> YaUnitData::SetUnitData::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.unit_data->setUnitData( - request.unit_id, &request.data); + return instance.interfaces.unit_data->setUnitData( + request.unit_id, &request.data); + }) + .get(); }, [&](YaPluginFactory3::SetHostContext& request) -> YaPluginFactory3::SetHostContext::Response { @@ -1335,10 +1422,15 @@ void Vst3Bridge::run() { }, [&](const YaUnitInfo::SelectUnit& request) -> YaUnitInfo::SelectUnit::Response { - const auto& [instance, _] = get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.unit_info->selectUnit( - request.unit_id); + return instance.interfaces.unit_info->selectUnit( + request.unit_id); + }) + .get(); }, [&](const YaUnitInfo::GetUnitByBus& request) -> YaUnitInfo::GetUnitByBus::Response { @@ -1365,18 +1457,29 @@ void Vst3Bridge::run() { request) -> YaXmlRepresentationController::GetXmlRepresentationStream:: Response { - const auto& [instance, _] = - get_instance(request.instance_id); + return main_context_ + .run_in_context( + [&]() + -> YaXmlRepresentationController:: + GetXmlRepresentationStreamResponse { + const auto& [instance, _] = + get_instance( + request.instance_id); - const tresult result = - instance.interfaces.xml_representation_controller - ->getXmlRepresentationStream(request.info, - &request.stream); + const tresult result = + instance.interfaces + .xml_representation_controller + ->getXmlRepresentationStream( + request.info, + &request.stream); - return YaXmlRepresentationController:: - GetXmlRepresentationStreamResponse{ - .result = result, - .stream = std::move(request.stream)}; + return YaXmlRepresentationController:: + GetXmlRepresentationStreamResponse{ + .result = result, + .stream = std::move( + request.stream)}; + }) + .get(); }, }); } @@ -1636,23 +1739,29 @@ size_t Vst3Bridge::register_object_instance( overload{ [&](YaAudioProcessor::SetBusArrangements& request) -> YaAudioProcessor::SetBusArrangements::Response { - const auto& [instance, _] = - get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - // HACK: WA Production Imperfect VST3 somehow requires - // `inputs` to be a valid pointer, even if there - // are no inputs. - Steinberg::Vst::SpeakerArrangement empty_arrangement = - 0b00000000; + // HACK: WA Production Imperfect VST3 somehow + // requires `inputs` to be a valid + // pointer, even if there are no inputs. + Steinberg::Vst::SpeakerArrangement + empty_arrangement = 0b00000000; - return instance.interfaces.audio_processor - ->setBusArrangements( - request.num_ins > 0 ? request.inputs.data() - : &empty_arrangement, - request.num_ins, - request.num_outs > 0 ? request.outputs.data() - : &empty_arrangement, - request.num_outs); + return instance.interfaces.audio_processor + ->setBusArrangements( + request.num_ins > 0 + ? request.inputs.data() + : &empty_arrangement, + request.num_ins, + request.num_outs > 0 + ? request.outputs.data() + : &empty_arrangement, + request.num_outs); + }) + .get(); }, [&](YaAudioProcessor::GetBusArrangement& request) -> YaAudioProcessor::GetBusArrangement::Response { @@ -1697,8 +1806,12 @@ size_t Vst3Bridge::register_object_instance( // buffers. instance.process_setup = request.setup; - return instance.interfaces.audio_processor - ->setupProcessing(request.setup); + return main_context_ + .run_in_context([&]() -> tresult { + return instance.interfaces.audio_processor + ->setupProcessing(request.setup); + }) + .get(); }, [&](const YaAudioProcessor::SetProcessing& request) -> YaAudioProcessor::SetProcessing::Response { @@ -1797,11 +1910,15 @@ size_t Vst3Bridge::register_object_instance( }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { - const auto& [instance, _] = - get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.component->setIoMode( - request.mode); + return instance.interfaces.component->setIoMode( + request.mode); + }) + .get(); }, [&](const YaComponent::GetBusCount& request) -> YaComponent::GetBusCount::Response { @@ -1839,12 +1956,16 @@ size_t Vst3Bridge::register_object_instance( }, [&](const YaComponent::ActivateBus& request) -> YaComponent::ActivateBus::Response { - const auto& [instance, _] = - get_instance(request.instance_id); + return main_context_ + .run_in_context([&]() -> tresult { + const auto& [instance, _] = + get_instance(request.instance_id); - return instance.interfaces.component->activateBus( - request.type, request.dir, request.index, - request.state); + return instance.interfaces.component + ->activateBus(request.type, request.dir, + request.index, request.state); + }) + .get(); }, [&](const YaComponent::SetActive& request) -> YaComponent::SetActive::Response { @@ -1856,7 +1977,7 @@ size_t Vst3Bridge::register_object_instance( // handled from the same thread to prevent // deadlocks caused by mutually recursive function // calls. - return do_mutual_recursion_on_off_thread( + return do_mutual_recursion_on_gui_thread( [&]() -> YaComponent::SetActive::Response { const auto& [instance, _] = get_instance(request.instance_id);