From 0b9b1330ad9aabc238f7d3c335d1485a74b36606 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 23 May 2021 17:37:09 +0200 Subject: [PATCH] Make sure effProcessEvents() also never allocates This basically changes the default small vectors during VST2 event processing from 256 bytes to the size of a `DynamicVstEvents` object (which also includes a small_vector to hold MIDI events without allocating) and makes them thread local. We already have a similar optimization for VST3. There it's a bit neater since we already had to separate audio processing functions from non-time critical functions. Here we don't have that separation, so we just made these buffers thread local, large enough to hold our predefined number of events, and we then just shrink them to fit if these buffers grow even more (which can only happen after reading or writing chunk data). The change doesn't specifically target `effProcessEvents()`, but that's where you would see the differences. This is also relevant for `audioMasterProcessEvents()`. --- CHANGELOG.md | 4 +-- src/common/communication/vst2.cpp | 7 ++--- src/common/communication/vst2.h | 44 ++++++++++++++++++++++++++----- src/wine-host/bridges/vst2.cpp | 7 ++--- 4 files changed, 48 insertions(+), 14 deletions(-) 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); } }