diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b3a2d4..3f1774ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,8 +49,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). - VST2 audio processing also received the same small vector optimization to get rid of any last potential allocations during audio processing. - The same small vector optimization has been applied across yabridge's entire - communication architecture, meaning that most function calls should no longer - produce any allocations for both VST2 and VST3 plugins. + communication and event handling architecture, meaning that most function + calls should no longer produce any allocations for both VST2 and VST3 plugins. - Changed the way mutual recursion in VST3 plugins on the plugin side works to counter any potential GUI related timing issues with VST3 plugins when using multiple instances of a plugin. diff --git a/src/common/communication/vst2.cpp b/src/common/communication/vst2.cpp index f05dc898..212861d7 100644 --- a/src/common/communication/vst2.cpp +++ b/src/common/communication/vst2.cpp @@ -74,7 +74,8 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/, Vst2EventResult DefaultDataConverter::send_event( boost::asio::local::stream_protocol::socket& socket, - const Vst2Event& event) const { - write_object(socket, event); - return read_object(socket); + const Vst2Event& event, + SerializationBufferBase& buffer) const { + write_object(socket, event, buffer); + return read_object(socket, buffer); } diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index d78e3307..c442c973 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -87,7 +87,8 @@ class DefaultDataConverter { */ virtual Vst2EventResult send_event( boost::asio::local::stream_protocol::socket& socket, - const Vst2Event& event) const; + const Vst2Event& event, + SerializationBufferBase& buffer) const; }; /** @@ -193,8 +194,8 @@ class Vst2EventHandler : public AdHocSocketHandler { .index = index, .value = value, .option = option, - .payload = payload, - .value_payload = value_payload}; + .payload = std::move(payload), + .value_payload = std::move(value_payload)}; // A socket only handles a single request at a time as to prevent // messages from arriving out of order. `AdHocSocketHandler::send()` @@ -206,7 +207,8 @@ class Vst2EventHandler : public AdHocSocketHandler { // calling thread (i.e. mutual recursion). const Vst2EventResult response = this->send( [&](boost::asio::local::stream_protocol::socket& socket) { - return data_converter.send_event(socket, event); + return data_converter.send_event(socket, event, + serialization_buffer()); }); if (logging) { @@ -249,7 +251,9 @@ class Vst2EventHandler : public AdHocSocketHandler { const auto process_event = [&](boost::asio::local::stream_protocol::socket& socket, bool on_main_thread) { - auto event = read_object(socket); + SerializationBufferBase& buffer = serialization_buffer(); + + auto event = read_object(socket, buffer); if (logging) { auto [logger, is_dispatch] = *logging; logger.log_event(is_dispatch, event.opcode, event.index, @@ -265,7 +269,7 @@ class Vst2EventHandler : public AdHocSocketHandler { response.payload, response.value_payload); } - write_object(socket, response); + write_object(socket, response, buffer); }; this->receive_multi( @@ -278,6 +282,34 @@ class Vst2EventHandler : public AdHocSocketHandler { process_event(socket, false); }); } + + private: + /** + * Unlike our VST3 implementation, in the VST2 implementation there's no + * separation between potentially real time critical events that will be + * called on the audio thread an all other events. This is absolutely fine, + * except for sending and receiving MIDI events. Those objects can get + * rather large, and because we want to avoid allocations on the audio + * thread at all cost we'll just predefine a large buffer for every thread. + */ + SerializationBufferBase& serialization_buffer() { + // This object also contains a `boost::container::small_vector` that has + // capacity for a large-ish number of events so we don't have to + // allocate under normal circumstances. + constexpr size_t initial_events_size = sizeof(DynamicVstEvents); + thread_local SerializationBuffer buffer{}; + + // This buffer is already pretty large, but it can still grow immensely + // when sending and receiving preset data. In such cases we do want to + // reallocate the buffer on the next event to free up memory again. This + // won't happen during audio processing. + if (buffer.size() > initial_events_size) { + buffer.resize(initial_events_size); + buffer.shrink_to_fit(); + } + + return buffer; + } }; /** diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index d8bc6589..03451f93 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -621,13 +621,14 @@ class HostCallbackDataConverter : public DefaultDataConverter { Vst2EventResult send_event( boost::asio::local::stream_protocol::socket& socket, - const Vst2Event& event) const override { + const Vst2Event& event, + SerializationBufferBase& buffer) const override { if (mutually_recursive_callbacks.contains(event.opcode)) { return mutual_recursion.fork([&]() { - return DefaultDataConverter::send_event(socket, event); + return DefaultDataConverter::send_event(socket, event, buffer); }); } else { - return DefaultDataConverter::send_event(socket, event); + return DefaultDataConverter::send_event(socket, event, buffer); } }