mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
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()`.
This commit is contained in:
+2
-2
@@ -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
|
- VST2 audio processing also received the same small vector optimization to get
|
||||||
rid of any last potential allocations during audio processing.
|
rid of any last potential allocations during audio processing.
|
||||||
- The same small vector optimization has been applied across yabridge's entire
|
- The same small vector optimization has been applied across yabridge's entire
|
||||||
communication architecture, meaning that most function calls should no longer
|
communication and event handling architecture, meaning that most function
|
||||||
produce any allocations for both VST2 and VST3 plugins.
|
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
|
- 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
|
counter any potential GUI related timing issues with VST3 plugins when using
|
||||||
multiple instances of a plugin.
|
multiple instances of a plugin.
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/,
|
|||||||
|
|
||||||
Vst2EventResult DefaultDataConverter::send_event(
|
Vst2EventResult DefaultDataConverter::send_event(
|
||||||
boost::asio::local::stream_protocol::socket& socket,
|
boost::asio::local::stream_protocol::socket& socket,
|
||||||
const Vst2Event& event) const {
|
const Vst2Event& event,
|
||||||
write_object(socket, event);
|
SerializationBufferBase& buffer) const {
|
||||||
return read_object<Vst2EventResult>(socket);
|
write_object(socket, event, buffer);
|
||||||
|
return read_object<Vst2EventResult>(socket, buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ class DefaultDataConverter {
|
|||||||
*/
|
*/
|
||||||
virtual Vst2EventResult send_event(
|
virtual Vst2EventResult send_event(
|
||||||
boost::asio::local::stream_protocol::socket& socket,
|
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<Thread> {
|
|||||||
.index = index,
|
.index = index,
|
||||||
.value = value,
|
.value = value,
|
||||||
.option = option,
|
.option = option,
|
||||||
.payload = payload,
|
.payload = std::move(payload),
|
||||||
.value_payload = value_payload};
|
.value_payload = std::move(value_payload)};
|
||||||
|
|
||||||
// A socket only handles a single request at a time as to prevent
|
// A socket only handles a single request at a time as to prevent
|
||||||
// messages from arriving out of order. `AdHocSocketHandler::send()`
|
// messages from arriving out of order. `AdHocSocketHandler::send()`
|
||||||
@@ -206,7 +207,8 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
|
|||||||
// calling thread (i.e. mutual recursion).
|
// calling thread (i.e. mutual recursion).
|
||||||
const Vst2EventResult response = this->send(
|
const Vst2EventResult response = this->send(
|
||||||
[&](boost::asio::local::stream_protocol::socket& socket) {
|
[&](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) {
|
if (logging) {
|
||||||
@@ -249,7 +251,9 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
|
|||||||
const auto process_event =
|
const auto process_event =
|
||||||
[&](boost::asio::local::stream_protocol::socket& socket,
|
[&](boost::asio::local::stream_protocol::socket& socket,
|
||||||
bool on_main_thread) {
|
bool on_main_thread) {
|
||||||
auto event = read_object<Vst2Event>(socket);
|
SerializationBufferBase& buffer = serialization_buffer();
|
||||||
|
|
||||||
|
auto event = read_object<Vst2Event>(socket, buffer);
|
||||||
if (logging) {
|
if (logging) {
|
||||||
auto [logger, is_dispatch] = *logging;
|
auto [logger, is_dispatch] = *logging;
|
||||||
logger.log_event(is_dispatch, event.opcode, event.index,
|
logger.log_event(is_dispatch, event.opcode, event.index,
|
||||||
@@ -265,7 +269,7 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
|
|||||||
response.payload, response.value_payload);
|
response.payload, response.value_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
write_object(socket, response);
|
write_object(socket, response, buffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->receive_multi(
|
this->receive_multi(
|
||||||
@@ -278,6 +282,34 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
|
|||||||
process_event(socket, false);
|
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<initial_events_size> 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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -621,13 +621,14 @@ class HostCallbackDataConverter : public DefaultDataConverter {
|
|||||||
|
|
||||||
Vst2EventResult send_event(
|
Vst2EventResult send_event(
|
||||||
boost::asio::local::stream_protocol::socket& socket,
|
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)) {
|
if (mutually_recursive_callbacks.contains(event.opcode)) {
|
||||||
return mutual_recursion.fork([&]() {
|
return mutual_recursion.fork([&]() {
|
||||||
return DefaultDataConverter::send_event(socket, event);
|
return DefaultDataConverter::send_event(socket, event, buffer);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return DefaultDataConverter::send_event(socket, event);
|
return DefaultDataConverter::send_event(socket, event, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user