From 79c6f02d919d69c77819ffe8f34463c92b6e3a44 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:07:21 +0100 Subject: [PATCH] Request and deserialize plugin factory from plugin --- src/common/bitsery/ext/boost-path.h | 2 - src/common/logging/vst3.cpp | 23 ++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 12 +- .../serialization/vst3/plugin-factory.cpp | 2 - .../serialization/vst3/plugin-factory.h | 45 +++---- src/plugin/bridges/vst3.cpp | 43 +++++++ src/plugin/bridges/vst3.h | 14 +++ src/plugin/vst3-plugin.cpp | 111 ++---------------- 9 files changed, 127 insertions(+), 127 deletions(-) diff --git a/src/common/bitsery/ext/boost-path.h b/src/common/bitsery/ext/boost-path.h index fe149bc2..ba103324 100644 --- a/src/common/bitsery/ext/boost-path.h +++ b/src/common/bitsery/ext/boost-path.h @@ -27,8 +27,6 @@ namespace bitsery { namespace ext { -// TODO: There's probably a better way to do all of this - /** * An adapter for serializing and deserializing filesystem paths since they're * not mutable. diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f3c2f029..608b71a9 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -17,6 +17,7 @@ #include "vst3.h" #include +#include "src/common/serialization/vst3.h" // TODO: Reconsider the output format // TODO: Maybe think of an alterantive that's a little less boilerplaty @@ -33,6 +34,16 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { } } +void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) + << " >> Requesting "; + + log(message.str()); + } +} + void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; @@ -41,3 +52,15 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log(message.str()); } } + +void Vst3Logger::log_response(bool is_host_vst, + const YaPluginFactory& factory) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) << " with " + << const_cast(factory).countClasses() + << " registered classes"; + + log(message.str()); + } +} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index adc493eb..8eccac19 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -44,8 +44,10 @@ class Vst3Logger { // (what we'll call a control message). void log_request(bool is_host_vst, const WantsConfiguration&); + void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3dacf9ab..1654d6e9 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -45,16 +45,24 @@ struct WantsConfiguration { using Response = Configuration; }; +/** + * Marker struct to indicate the other side (the Wine plugin host) should send a + * copy of the hosted Windows VST3 plugin's `IPluginFactory{,2,3}` interface. + */ +struct WantsPluginFactory { + using Response = YaPluginFactory; +}; + /** * When we send a control message from the plugin to the Wine VST host, this * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Reponse`. */ -using ControlRequest = std::variant<>; +using ControlRequest = std::variant; template void serialize(S& s, ControlRequest& payload) { - s.ext(payload, bitsery::ext::StdVariant{}); + s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsPluginFactory&) {}}); } /** diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 08a21ac3..f21e812b 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -53,8 +53,6 @@ YaPluginFactory::YaPluginFactory( // `IPluginFactory::countClasses` num_classes = factory->countClasses(); // `IPluginFactory::getClassInfo` - // TODO: At this point we don't know what this class is and thus we can't - // filter unsupported classes, right? class_infos_1.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo info; diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 011258db..7fe9d555 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -86,6 +86,29 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; + template + void serialize(S& s) { + s.ext(known_iids, bitsery::ext::StdSet{32}, + [](S& s, Steinberg::FUID& iid) { + s.ext(iid, bitsery::ext::FUID{}); + }); + s.ext(factory_info, bitsery::ext::StdOptional{}); + s.value4b(num_classes); + s.container(class_infos_1, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_2, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_unicode, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + } + + private: /** * The IIDs that the interface we serialized supports. */ @@ -115,28 +138,6 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * above. */ std::vector> class_infos_unicode; - - template - void serialize(S& s) { - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); - s.ext(factory_info, bitsery::ext::StdOptional{}); - s.value4b(num_classes); - s.container(class_infos_1, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_2, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_unicode, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - } }; #pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 0d6165bb..1ac88ffb 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -16,6 +16,42 @@ #include "vst3.h" +#include "src/common/serialization/vst3.h" +#include "vst3-impls.h" + +// There are still some design decisions that need some more thought +// TODO: Check whether `IPlugView::isPlatformTypeSupported` needs special +// handling. +// TODO: The documentation mentions that private communication through VST3's +// message system should be handled on a separate timer thread. Do we +// need special handling for this on the Wine side (e.g. during the event +// handling loop)? Probably not, since the actual host should manage all +// messaging. +// TODO: The docs very explicitly mention that +// the`IComponentHandler::{begin,perform,end}Edit()` functions have to be +// called from the UI thread. Should we have special handling for this or +// does everything just magically work out? +// TODO: Something that's not relevant here but that will require some thinking +// is that VST3 requires all plugins to be installed in ~/.vst3. I can +// think of two options and I"m not sure what's the best one: +// +// 1. We can add the required files for the Linux VST3 plugin to the +// location of the Windows VST3 plugin (by adding some files to the +// bundle or creating a bundle next to it) and then symlink that bundle +// to ~/.vst3. +// 2. We can create the bundle in ~/.vst3 and symlink the Windows plugin +// and all of its resources into bundle as if they were also installed +// there. +// +// The second one sounds much better, but it will still need some more +// consideration. Aside from that VST3 plugins also have a centralized +// preset location, even though barely anyone uses it, yabridgectl will +// also have to make a symlink of that. Also, yabridgectl will need to do +// some extra work there to detect removed plugins. +// TODO: Also symlink presets, and allow pruning broken symlinks there as well +// TODO: And how do we choose between 32-bit and 64-bit versions of a VST3 +// plugin if they exist? Config files? + Vst3PluginBridge::Vst3PluginBridge() : PluginBridge( PluginType::vst3, @@ -34,6 +70,13 @@ Vst3PluginBridge::Vst3PluginBridge() // host connect_sockets_guarded(); + // Set up the plugin factory, since this is the first thing the host will + // request after loading the module + plugin_factory = std::make_unique(*this); + sockets.host_vst_control.receive_into( + WantsPluginFactory{}, *plugin_factory, + std::pair(logger, true)); + // Now that communication is set up the Wine host can send callbacks to this // bridge class, and we can send control messages to the Wine host. This // messaging mechanism is how we relay the VST3 communication protocol. As a diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index f997a11d..93fc0a7e 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -18,6 +18,7 @@ #include +#include "../..//common/serialization/vst3/plugin-factory.h" #include "../../common/communication/vst3.h" #include "../../common/logging/vst3.h" #include "common.h" @@ -63,4 +64,17 @@ class Vst3PluginBridge : PluginBridge> { * `PluginBridge::generic_logger`. */ Vst3Logger logger; + + public: + /** + * Our plugin factory. This will be set up directly after initialization. + * All information about the plugin and its supported classes are copied + * directly from the Windows VST3 plugin's factory on the Wine side, and + * we'll provide an implementation that can send control messages to the + * Wine plugin host. + * + * A pointer to this implementation will be returned to the host in + * GetPluginFactory(). + */ + std::unique_ptr plugin_factory; }; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 4052cd57..c2a26050 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -17,9 +17,6 @@ #include #include "bridges/vst3.h" -// TODO: Remove include, instantiating and returning the `YaPluginFactory` -// should be done in `Vst3PluginBridge` -#include "src/plugin/bridges/vst3-impls.h" #include @@ -76,101 +73,17 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // The host should have called `InitModule()` first assert(bridge); - // TODO: Instead of using gPluginFactory we'll use a field in - // `Vst3PluginBridge` - // TODO: First thing we should do is query the factory on the Wine side and - // preset a copy of it to the host. The important bits there are that - // we use the same interface version as the one presented the plugin. - // TODO: We have two options for the implementation: - // 1. We can query the interface version, and then have three - // different implementations for the interface version. - // 2. We can implement version 3, but copy the iid from the plugin so - // it always uses the correct version. + return bridge->plugin_factory.get(); - // TODO: Remove, this is just for type checking - if (false) { - boost::asio::local::stream_protocol::socket* socket; - YaPluginFactoryPluginImpl object(*bridge); - write_object(*socket, object); - } - - if (!gPluginFactory) { - // TODO: Here we want to: - // 1) Load the plugin on the Wine host - // 2) Create a factory using the plugins PFactoryInfo - // 3) Get all PClassInfo{,2,W} objects from the plugin, register - // those classes. - // - // We should wrap this in our `Vst3PluginBridge` - // TODO: We should also create a list of which extensions we have - // already implemented and which are left - // TODO: And when we get a query for some interface that we do not (yet) - // support, we should print some easy to spot warning message - // TODO: Check whether `IPlugView::isPlatformTypeSupported` needs - // special handling. - // TODO: Should we always use plugin groups or for VST3 plugins? Since - // they seem to be very keen on sharing resources and leaving - // modules loaded. - // TODO: The documentation mentions that private communication through - // VST3's message system should be handled on a separate timer - // thread. Do we need special handling for this on the Wine side - // (e.g. during the event handling loop)? Probably not, since the - // actual host should manage all messaging. - // TODO: The docs very explicitly mention that - // the`IComponentHandler::{begin,perform,end}Edit()` functions - // have to be called from the UI thread. Should we have special - // handling for this or does everything just magically work out? - // TODO: Something that's not relevant here but that will require some - // thinking is that VST3 requires all plugins to be installed in - // ~/.vst3. I can think of two options and I"m not sure what's the - // best one: - // - // 1. We can add the required files for the Linux VST3 plugin to - // the location of the Windows VST3 plugin (by adding some - // files to the bundle or creating a bundle next to it) and - // then symlink that bundle to ~/.vst3. - // 2. We can create the bundle in ~/.vst3 and symlink the Windows - // plugin and all of its resources into bundle as if they were - // also installed there. - // - // The second one sounds much better, but it will still need some - // more consideration. Aside from that VST3 plugins also have a - // centralized preset location, even though barely anyone uses it, - // yabridgectl will also have to make a symlink of that. Also, - // yabridgectl will need to do some extra work there to detect - // removed plugins. - // TODO: Also symlink presets, and allow pruning broken symlinks there - // as well - // TODO: And how do we choose between 32-bit and 64-bit versions of a - // VST3 plugin if they exist? Config files? - - // static Steinberg::PFactoryInfo factoryInfo(vendor, url, email, - // flags); gPluginFactory = new Steinberg::CPluginFactory(factoryInfo); - - // - // { - // Steinberg::TUID lcid = cid; - // static Steinberg::PClassInfo componentClass(lcid, cardinality, - // category, name); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - // { - // Steinberg::TUID lcid = cid; - // static Steinberg::PClassInfo2 componentClass( - // lcid, cardinality, category, name, classFlags, subCategories, - // 0, version, sdkVersion); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - // { - // TUID lcid = cid; - // static Steinberg::PClassInfoW componentClass( - // lcid, cardinality, category, name, classFlags, subCategories, - // 0, version, sdkVersion); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - } else { - gPluginFactory->addRef(); - } - - return gPluginFactory; + // TODO: In the normal implementation of this function they manually call + // part of the reference counting mechanism. Is this something we also + // have to? And how does the `delete self` in the `removeRef()` play + // with our `std::unique_ptr` (aka, should we also use IPtr here)? The + // normal implementation looks like this: + // if (!gPluginFactory) { + // // Instantiate the factory + // } else { + // gPluginFactory->addRef(); + // } + // return gPluginFactory; }