Reuse buffers in VST3 audio processing

This commit is contained in:
Robbert van der Helm
2020-12-25 16:22:53 +01:00
parent 8a56b67cb3
commit 7da5ec113c
2 changed files with 81 additions and 12 deletions
+3
View File
@@ -101,6 +101,9 @@ inline void write_object(Socket& socket, const T& object) {
* @throw boost::system::system_error If the socket is closed or gets closed * @throw boost::system::system_error If the socket is closed or gets closed
* while reading. * while reading.
* *
* TODO: Swap these arguments around so they match `write_object`'s argument
* order
*
* @relates write_object * @relates write_object
*/ */
template <typename T, typename Socket> template <typename T, typename Socket>
+77 -11
View File
@@ -84,10 +84,29 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
* then this indicates that this `Vst3MessageHandler` is handling plugin * then this indicates that this `Vst3MessageHandler` is handling plugin
* -> host callbacks isntead. Optional since it only has to be set on the * -> host callbacks isntead. Optional since it only has to be set on the
* plugin's side. * plugin's side.
* @param buffer The serialization and receiving buffer to reuse. This is
* optional, but it's useful for minimizing allocations in the audio
* processing loop.
* *
* @relates Vst3MessageHandler::receive_messages * @relates Vst3MessageHandler::receive_messages
*/ */
template <typename T> template <typename T>
typename T::Response send_message(
const T& object,
std::optional<std::pair<Vst3Logger&, bool>> logging,
std::vector<uint8_t>& buffer) {
typename T::Response response_object;
receive_into(object, response_object, logging, buffer);
return response_object;
}
/**
* The same as the above, but with a small default buffer.
*
* @overload
*/
template <typename T>
typename T::Response send_message( typename T::Response send_message(
const T& object, const T& object,
std::optional<std::pair<Vst3Logger&, bool>> logging) { std::optional<std::pair<Vst3Logger&, bool>> logging) {
@@ -101,8 +120,6 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
* `Vst3MessageHandler::send_message()`, but deserializing the response into * `Vst3MessageHandler::send_message()`, but deserializing the response into
* an existing object. * an existing object.
* *
* TODO: We might also need overloads that reuse buffers
*
* @param response_object The object to deserialize into. * @param response_object The object to deserialize into.
* *
* @overload Vst3MessageHandler::send_message * @overload Vst3MessageHandler::send_message
@@ -111,7 +128,8 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
typename T::Response& receive_into( typename T::Response& receive_into(
const T& object, const T& object,
typename T::Response& response_object, typename T::Response& response_object,
std::optional<std::pair<Vst3Logger&, bool>> logging) { std::optional<std::pair<Vst3Logger&, bool>> logging,
std::vector<uint8_t>& buffer) {
using TResponse = typename T::Response; using TResponse = typename T::Response;
// Since a lot of messages just return a `tresult`, we can't filter out // Since a lot of messages just return a `tresult`, we can't filter out
@@ -129,8 +147,8 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
// in use it will spawn a new socket for us. // in use it will spawn a new socket for us.
this->template send<std::monostate>( this->template send<std::monostate>(
[&](boost::asio::local::stream_protocol::socket& socket) { [&](boost::asio::local::stream_protocol::socket& socket) {
write_object(socket, Request(object)); write_object(socket, Request(object), buffer);
read_object<TResponse>(socket, response_object); read_object<TResponse>(socket, buffer, response_object);
// FIXME: We have to return something here, and ML was not yet // FIXME: We have to return something here, and ML was not yet
// invented when they came up with C++ so void is not // invented when they came up with C++ so void is not
// valid here // valid here
@@ -145,6 +163,21 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
return response_object; return response_object;
} }
/**
* The same function as above, but with a small default buffer.
*
* @overload
*/
template <typename T>
typename T::Response& receive_into(
const T& object,
typename T::Response& response_object,
std::optional<std::pair<Vst3Logger&, bool>> logging) {
std::vector<uint8_t> buffer(64);
return receive_into(object, response_object, std::move(logging),
buffer);
}
/** /**
* Spawn a new thread to listen for extra connections to `endpoint`, and * Spawn a new thread to listen for extra connections to `endpoint`, and
* then start a blocking loop that handles messages from the primary * then start a blocking loop that handles messages from the primary
@@ -165,18 +198,32 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
* @tparam F A function type in the form of `T::Response(T)` for every `T` * @tparam F A function type in the form of `T::Response(T)` for every `T`
* in `Request`. This way we can directly deserialize into a `T::Response` * in `Request`. This way we can directly deserialize into a `T::Response`
* on the side that called `receive_into(T, T::Response&)`. * on the side that called `receive_into(T, T::Response&)`.
* @tparam keep_buffers Whether processing buffers should be kept around and
* reused. This is used to minimize allocations in the audio processing
* loop. This can only be used when `ad_hoc_sockets` is set to false,
* since we can of course only reuse buffers within a single thread. These
* buffers will also never shrink, but that should not be an issue here.
* *
* @relates Vst3MessageHandler::send_event * @relates Vst3MessageHandler::send_event
*/ */
template <typename F> template <bool keep_buffers = false, typename F>
void receive_messages(std::optional<std::pair<Vst3Logger&, bool>> logging, void receive_messages(std::optional<std::pair<Vst3Logger&, bool>> logging,
F callback) { F callback) {
std::vector<uint8_t> persistent_buffer{};
if constexpr (keep_buffers) {
static_assert(!ad_hoc_sockets,
"Buffers can only be reused when ad-hoc socket "
"spawning has been disabled.");
}
// Reading, processing, and writing back the response for the requests // Reading, processing, and writing back the response for the requests
// we receive works in the same way regardless of which socket we're // we receive works in the same way regardless of which socket we're
// using // using
const auto process_message = const auto process_message =
[&](boost::asio::local::stream_protocol::socket& socket) { [&](boost::asio::local::stream_protocol::socket& socket) {
auto request = read_object<Request>(socket); auto request = keep_buffers ? read_object<Request>(
socket, persistent_buffer)
: read_object<Request>(socket);
// See the comment in `receive_into()` for more information // See the comment in `receive_into()` for more information
bool should_log_response = false; bool should_log_response = false;
@@ -201,7 +248,11 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread, ad_hoc_sockets> {
logger.log_response(!is_host_vst, response); logger.log_response(!is_host_vst, response);
} }
if constexpr (keep_buffers) {
write_object(socket, response, persistent_buffer);
} else {
write_object(socket, response); write_object(socket, response);
}
}, },
request); request);
}; };
@@ -299,6 +350,8 @@ class Vst3Sockets : public Sockets {
std::to_string(instance_id) + ".sock")) std::to_string(instance_id) + ".sock"))
.string(), .string(),
false); false);
audio_processor_buffers.try_emplace(instance_id);
audio_processor_sockets.at(instance_id).connect(); audio_processor_sockets.at(instance_id).connect();
} }
@@ -329,12 +382,17 @@ class Vst3Sockets : public Sockets {
std::to_string(instance_id) + ".sock")) std::to_string(instance_id) + ".sock"))
.string(), .string(),
true); true);
audio_processor_buffers.try_emplace(instance_id);
} }
socket_listening_latch.set_value(); socket_listening_latch.set_value();
audio_processor_sockets.at(instance_id).connect(); audio_processor_sockets.at(instance_id).connect();
// This `true` indicates that we want to reuse our serialization and
// receiving buffers for all calls. This slightly reduces the amount of
// allocations in the audio processing loop.
audio_processor_sockets.at(instance_id) audio_processor_sockets.at(instance_id)
.receive_messages(std::nullopt, cb); .template receive_messages<true>(std::nullopt, cb);
} }
/** /**
@@ -353,6 +411,7 @@ class Vst3Sockets : public Sockets {
if (audio_processor_sockets.contains(instance_id)) { if (audio_processor_sockets.contains(instance_id)) {
audio_processor_sockets.at(instance_id).close(); audio_processor_sockets.at(instance_id).close();
audio_processor_sockets.erase(instance_id); audio_processor_sockets.erase(instance_id);
audio_processor_buffers.erase(instance_id);
return true; return true;
} else { } else {
@@ -364,7 +423,8 @@ class Vst3Sockets : public Sockets {
* Send a message from the native plugin to the Wine plugin host to handle * Send a message from the native plugin to the Wine plugin host to handle
* an `IAudioProcessor` or `IComponent` call. Since those functions are * an `IAudioProcessor` or `IComponent` call. Since those functions are
* called from a hot loop we want every instance to have a dedicated socket * called from a hot loop we want every instance to have a dedicated socket
* and thread for handling those. * and thread for handling those. These calls also always reuse buffers to
* minimize allocations.
* *
* @tparam T Some object in the `AudioProcessorRequest` variant. * @tparam T Some object in the `AudioProcessorRequest` variant.
*/ */
@@ -372,9 +432,9 @@ class Vst3Sockets : public Sockets {
typename T::Response send_audio_processor_message( typename T::Response send_audio_processor_message(
const T& object, const T& object,
std::optional<std::pair<Vst3Logger&, bool>> logging) { std::optional<std::pair<Vst3Logger&, bool>> logging) {
// TODO: These calls should reuse buffers
return audio_processor_sockets.at(object.instance_id) return audio_processor_sockets.at(object.instance_id)
.send_message(object, logging); .send_message(object, logging,
audio_processor_buffers.at(object.instance_id));
} }
/** /**
@@ -412,5 +472,11 @@ class Vst3Sockets : public Sockets {
*/ */
std::map<size_t, Vst3MessageHandler<Thread, AudioProcessorRequest, false>> std::map<size_t, Vst3MessageHandler<Thread, AudioProcessorRequest, false>>
audio_processor_sockets; audio_processor_sockets;
/**
* Binary buffers used for serializing objects and receiving messages into
* during `send_audio_processor_message()`. This is used to minimize the
* amount of allocations in the audio processing loop.
*/
std::map<size_t, std::vector<uint8_t>> audio_processor_buffers;
std::mutex audio_processor_sockets_mutex; std::mutex audio_processor_sockets_mutex;
}; };