Update the VST3 implementation documentation

This commit is contained in:
Robbert van der Helm
2020-12-17 15:43:13 +01:00
parent 1ce12227fb
commit 7a5de5d35e
+36 -48
View File
@@ -1,14 +1,9 @@
# VST3 serialization # VST3 serialization
TODO: Flesh this out further TODO: Flesh this out further, update the instantiation part, make the proxying part clearer
TODO: Link to `src/common/serialization/vst3/README.md` TODO: Link to `src/common/serialization/vst3/README.md`
TODO: Mention the new `Ya<Base>::supports()` mechanism for the monolithic proxy
objects through multiple inheritance
TODO: Explain the monolith
The VST3 SDK uses an architecture where every concrete object inherits from an The VST3 SDK uses an architecture where every concrete object inherits from an
interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a
dynamic casting interface through `queryInterface()` and a reference counting dynamic casting interface through `queryInterface()` and a reference counting
@@ -34,52 +29,45 @@ Yabridge's serialization and communication model for VST3 is thus a lot more
complicated than for VST2 since all of these objects are loosely coupled and are complicated than for VST2 since all of these objects are loosely coupled and are
instantiated and managed by the host. The basic model works as follows: instantiated and managed by the host. The basic model works as follows:
1. For an interface `IFoo`, we provide a possibly abstract implementation called 1. The main idea behind yabridge's VST3 implementation is that we define
`YaFoo`. monolithic proxy objects that can proxy any object created by the Windows
2. When we want to _proxy_ an interface from one side to the other (let's assume VST3 plugin. These proxy objects indirectly inherit from all applicable
we want to allow the native VST3 host to call functions on the `IFoo` interfaces defiend in the VST3 SDK. `Vst3PluginProxy` implements all
provided by the Windows VST3 plugin), we need to provide a `YaFoo` interfaces that can be implemented by plugins, and `Vst3HostProxy` implements
implementation on the native plugin side that can do callbacks to the all interfaces that are to be implemented by the host.
corresponding `IFoo` object in the Wine plugin host. For most objects, this 2. For every interface `IFoo`, we provide an abstract implementation called
works by first generating a unique identifier to be able to refer to this `YaFoo`. This implementation mostly contain message object we use to make
specific `IFoo` instance, and then serializing that identifier together with specific function calls on the actual objects we are proxying. The
any static payload data into a `YaFoo::ConstructArgs` object. This implementation also comes with a function that takes an `FUnknown` pointer,
`YaFoo::ConstructArgs` copies this data through a `IPtr<IFoo>` smart pointer checks whether the object behind that pointer supports `IFoo`, and then
to the original object we're proxying. This object can be serialized and stores the result along with any potential static payload data as a
transmitted to the other side using bitsery. `YaFoo::ConstrctArgs` object.
3. The original `IFoo` we are proxying gets added to an 3. Proxy object are instantiated while handling
`std::map<size_t, IPtr<IFoo>>` (in our assumed scenario, this happens on the `IPluginFactory::createInstance()` for `Vst3PluginProxy`, and during
Wine plugin host's side) with the key being that unique instance identifier `IPluginBase::initialize()` and `IPluginFactory::setHostContext()` for
we generated so we can refer to it later on. `Vst3HostProxy`. On the receiving side of those functions (where we call the
4. `YaFoo` implements all the boilerplate required for `FUnknown`. This includes actual function implemented by the plugin or the host), we receive an
the constructor, destructor and methods required for reference counting, as `IPtr<T>` smart pointer to an object provided by the host or the plugin. We
well as the query interface. It also implements any static lookup functions use this object to iterate over every applicable `YaFoo` as mentioend above.
that can be performed using the data contained in a `YaFoo::ConstructArgs` All of these `YaFoo::ConstructArgs` objects along with a unique identifier
object. Any functions that perform side effects or return dynamic data and for this specific object are then serialized and transmitted to the other
thus require a callback or control message are marked as pure virtual. These side. With this information we can create a proxy object that supports all
callbacks can be performed through yabridge's `Vst3MessageHandler` message the same interfaces (and thus allows calls to the functions in those
handling interface. For the sake of clarity, we use the term _callback_ for interfaces) as the original object we are proxying.
`plugin -> host` function calls and _control message_ for `host -> plugin` 4. As mentioend, every object we instantiate gets assigned a unique identifier.
function calls. When dealign with objects created by the Windows VST3 plugin, the object's
5. The side that requested the object (which we assume to be the native plugin `FUnknown` pointer will be stored in an `std::map<size_t, PluginObject>` map.
here), creates a _proxy object_ called `YaFoo{Plugin,Host}Impl`, so This way we can refer to it later on when we receive a request to call a
`YaFooPluginImpl` in this case. This is an instance of `YaFoo` and thus specific function on the plugin.
`IFoo`, so we can pass it as an `IFoo` pointer to the host. This object takes 5. If `IFoo` is a versioned interface such as `IPluginFactory{,2,3}`, the
those `YaFoo::ConstructArgs` and a reference to the bridge instance so it can
do callbacks or send control messages.
6. If `IFoo` is a versioned interface such as `IPluginFactory{,2,3}`, the
creation of `YaFoo::ConstrctArgs` and the definition of `YaFoo`'s query creation of `YaFoo::ConstrctArgs` and the definition of `YaFoo`'s query
interface work slightly differently. When copying the data for a plugin interface work slightly differently. When copying the data for a plugin
factory, we'll start copying from `IPluginFactory`, and we'll copy data from factory, we'll start copying from `IPluginFactory`, and we'll copy data from
each newer version of the interface that the `IPtr<IPluginFactory>` supports. each newer version of the interface that the `IPtr<IPluginFactory>` supports.
During this process we keep track of which interfaces were supported by the During this process we keep track of which interfaces were supported by the
native plugin in a `known_iids` set. In our query interface method we then native plugin. In our query interface method we then only report support for
only report support for the same interfaces that were supported by the the same interface versions that were supported by the original
original `IPtr<IPluginFactory` we're proxying. `IPtr<IPluginFactory>` we are proxying.
7. The same mechanism that we use for versioning is also used for objects that
commonly implement multiple interfaces. A common example of this is an
`IComponent` (which inherits from `IPluginBase`) also implementing
`IAudioProcessor` and `IConnectionPoint`.
## Interface Instantiation ## Interface Instantiation
@@ -87,7 +75,7 @@ Creating a new instance of an interface using the plugin factory wroks as
follows. This describes the object lifecycle. The actual serialization and follows. This describes the object lifecycle. The actual serialization and
proxying is described in the section above. proxying is described in the section above.
1. The host calls `createInterface(cid, _iid, obj)` on an IPluginFactory 1. The host calls `createInterface(cid, _iid, obj)` on an `IPluginFactory`
implementation exposed to the host as described above. implementation exposed to the host as described above.
2. We check which interface we support matches the `_iid`. If we don't support 2. We check which interface we support matches the `_iid`. If we don't support
the interface, we'll log a message about it and return that we do not support the interface, we'll log a message about it and return that we do not support