From 93b8643cba11c86f631011da207430d76b8eb1d1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 7 May 2021 18:21:30 +0200 Subject: [PATCH] Avoid allocations when reconstructing process data --- .../vst3/plugin/audio-processor.h | 2 +- .../serialization/vst3/process-data.cpp | 127 ++++++++++-------- src/common/serialization/vst3/process-data.h | 26 ++-- src/wine-host/bridges/vst3.cpp | 6 +- 4 files changed, 85 insertions(+), 76 deletions(-) diff --git a/src/common/serialization/vst3/plugin/audio-processor.h b/src/common/serialization/vst3/plugin/audio-processor.h index 2e16363a..f5c9f837 100644 --- a/src/common/serialization/vst3/plugin/audio-processor.h +++ b/src/common/serialization/vst3/plugin/audio-processor.h @@ -236,7 +236,7 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { * Wine plugin host. This `YaProcessData` object wraps around all input * audio buffers, parameter changes and events along with all context data * provided by the host so we can send it to the Wine plugin host. We can - * then use `YaProcessData::get()` on the Wine plugin host side to + * then use `YaProcessData::reconstruct()` on the Wine plugin host side to * reconstruct the original `ProcessData` object, and we then finally use * `YaProcessData::move_outputs_to_response()` to create a response object * that we can write back to the `ProcessData` object provided by the host. diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 72369367..e7db26a8 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -20,16 +20,34 @@ YaAudioBusBuffers::YaAudioBusBuffers() {} -YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size, - size_t num_samples, - size_t num_channels) - : buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64 - ? decltype(buffers)(std::vector>( - num_channels, - std::vector(num_samples, 0.0))) - : decltype(buffers)(std::vector>( - num_channels, - std::vector(num_samples, 0.0)))) {} +void YaAudioBusBuffers::clear(int32 sample_size, + size_t num_samples, + size_t num_channels) { + if (sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64) { + if (!std::holds_alternative>>( + buffers)) { + buffers.emplace>>(); + } + + std::vector>& vector_buffers = + std::get>>(buffers); + vector_buffers.resize(num_channels); + for (size_t i = 0; i < vector_buffers.size(); i++) { + vector_buffers[i].assign(num_samples, 0.0); + } + } else { + if (!std::holds_alternative>>(buffers)) { + buffers.emplace>>(); + } + + std::vector>& vector_buffers = + std::get>>(buffers); + vector_buffers.resize(num_channels); + for (size_t i = 0; i < vector_buffers.size(); i++) { + vector_buffers[i].assign(num_samples, 0.0f); + } + } +} void YaAudioBusBuffers::repopulate( int32 sample_size, @@ -73,14 +91,16 @@ void YaAudioBusBuffers::repopulate( } } -Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { - Steinberg::Vst::AudioBusBuffers reconstructed_buffers; +void YaAudioBusBuffers::reconstruct( + Steinberg::Vst::AudioBusBuffers& reconstructed_buffers) { + // We'll update the `AudioBusBuffers` object in place to point to our new + // data reconstructed_buffers.silenceFlags = silence_flags; std::visit(overload{ [&](std::vector>& buffers) { - buffer_pointers.clear(); - for (auto& buffer : buffers) { - buffer_pointers.push_back(buffer.data()); + buffer_pointers.resize(buffers.size()); + for (size_t i = 0; i < buffers.size(); i++) { + buffer_pointers[i] = buffers[i].data(); } reconstructed_buffers.numChannels = @@ -89,9 +109,9 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { reinterpret_cast(buffer_pointers.data()); }, [&](std::vector>& buffers) { - buffer_pointers.clear(); - for (auto& buffer : buffers) { - buffer_pointers.push_back(buffer.data()); + buffer_pointers.resize(buffers.size()); + for (size_t i = 0; i < buffers.size(); i++) { + buffer_pointers[i] = buffers[i].data(); } reconstructed_buffers.numChannels = @@ -101,8 +121,6 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { }, }, buffers); - - return reconstructed_buffers; } size_t YaAudioBusBuffers::num_channels() const { @@ -204,52 +222,47 @@ void YaProcessData::repopulate( } } -Steinberg::Vst::ProcessData& YaProcessData::get() { - // TODO: Also optimize this to make use of the reused objects - - // We'll have to transform out `YaAudioBusBuffers` objects into an array of - // `AudioBusBuffers` object so the plugin can deal with them. These objects - // contain pointers to those original objects and thus don't store any - // buffer data themselves. - inputs_audio_bus_buffers.clear(); - for (auto& buffers : inputs) { - inputs_audio_bus_buffers.push_back(buffers.get()); - } - - // We'll do the same with with the outputs, but we'll first have to - // initialize zeroed out buffers for the plugin to work with since we didn't - // serialize those directly - outputs.clear(); - outputs_audio_bus_buffers.clear(); - for (auto& num_channels : outputs_num_channels) { - YaAudioBusBuffers& buffers = outputs.emplace_back( - symbolic_sample_size, num_samples, num_channels); - outputs_audio_bus_buffers.push_back(buffers.get()); - } - - if (output_parameter_changes) { - output_parameter_changes->clear(); - } - if (output_events) { - output_events->clear(); - } - +Steinberg::Vst::ProcessData& YaProcessData::reconstruct() { reconstructed_process_data.processMode = process_mode; reconstructed_process_data.symbolicSampleSize = symbolic_sample_size; reconstructed_process_data.numSamples = num_samples; reconstructed_process_data.numInputs = static_cast(inputs.size()); reconstructed_process_data.numOutputs = static_cast(outputs_num_channels.size()); - reconstructed_process_data.inputs = inputs_audio_bus_buffers.data(); - reconstructed_process_data.outputs = outputs_audio_bus_buffers.data(); + // We'll have to transform our `YaAudioBusBuffers` objects into an array of + // `AudioBusBuffers` object so the plugin can deal with them. These objects + // contain pointers to those original objects and thus don't store any + // buffer data themselves. + inputs_audio_bus_buffers.resize(inputs.size()); + for (size_t i = 0; i < inputs.size(); i++) { + inputs[i].reconstruct(inputs_audio_bus_buffers[i]); + } + + reconstructed_process_data.inputs = inputs_audio_bus_buffers.data(); + + // We'll do the same with with the outputs, but we'll first have to + // initialize zeroed out buffers for the plugin to work with since we didn't + // serialize those directly + outputs.resize(outputs_num_channels.size()); + outputs_audio_bus_buffers.resize(outputs_num_channels.size()); + for (size_t i = 0; i < outputs_num_channels.size(); i++) { + outputs[i].clear(symbolic_sample_size, num_samples, + outputs_num_channels[i]); + outputs[i].reconstruct(outputs_audio_bus_buffers[i]); + } + + reconstructed_process_data.outputs = outputs_audio_bus_buffers.data(); reconstructed_process_data.inputParameterChanges = &input_parameter_changes; + if (output_parameter_changes_supported) { - output_parameter_changes.emplace(); + if (!output_parameter_changes) { + output_parameter_changes.emplace(); + } + output_parameter_changes->clear(); reconstructed_process_data.outputParameterChanges = &*output_parameter_changes; } else { - output_parameter_changes.reset(); reconstructed_process_data.outputParameterChanges = nullptr; } @@ -260,10 +273,12 @@ Steinberg::Vst::ProcessData& YaProcessData::get() { } if (output_events_supported) { - output_events.emplace(); + if (!output_events) { + output_events.emplace(); + } + output_events->clear(); reconstructed_process_data.outputEvents = &*output_events; } else { - output_events.reset(); reconstructed_process_data.outputEvents = nullptr; } diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 8e0a83c0..abf3228c 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -47,14 +47,9 @@ class YaAudioBusBuffers { /** * Create a new, zero initialize audio bus buffers object. Used to - * reconstruct the output buffers during `YaProcessData::get()`. - * - * TODO: Replace with a function similar to `repopulate` that just reassigns - * the existing buffers for the outputs created on the Wine side. + * reconstruct the output buffers during `YaProcessData::reconstruct()`. */ - YaAudioBusBuffers(int32 sample_size, - size_t num_samples, - size_t num_channels); + void clear(int32 sample_size, size_t num_samples, size_t num_channels); /** * Copy data from a host provided `AudioBusBuffers` object during a process @@ -71,13 +66,13 @@ class YaAudioBusBuffers { /** * Reconstruct the original `AudioBusBuffers` object passed to the * constructor and return it. This is used as part of - * `YaProcessData::get()`. The object contains pointers to `buffers`, so it - * may not outlive this object. + * `YaProcessData::reconstruct()`. The object contains pointers to + * `buffers`, so it may not outlive this object. * * NOTE: The `silenceFlags` field is of course not a reference, so writing * to that will not modify `silence_flags`. */ - Steinberg::Vst::AudioBusBuffers get(); + void reconstruct(Steinberg::Vst::AudioBusBuffers& reconstructed_buffers); /** * Return the number of channels in `buffers`. Only used for debug logs. @@ -183,10 +178,11 @@ class YaProcessData { /** * Copy data from a host provided `ProcessData` object during a process - * call. This struct can then be serialized, and `YaProcessData::get()` can - * then be used again to recreate the original `ProcessData` object. This - * will avoid allocating unless it's absolutely necessary (e.g. when we - * receive more parameter changes than we've received in previous calls). + * call. This struct can then be serialized, and + * `YaProcessData::reconstruct()` can then be used again to recreate the + * original `ProcessData` object. This will avoid allocating unless it's + * absolutely necessary (e.g. when we receive more parameter changes than + * we've received in previous calls). */ void repopulate(const Steinberg::Vst::ProcessData& process_data); @@ -195,7 +191,7 @@ class YaProcessData { * and return it. This is used in the Wine plugin host when processing an * `IAudioProcessor::process()` call. */ - Steinberg::Vst::ProcessData& get(); + Steinberg::Vst::ProcessData& reconstruct(); /** * **Move** all output written by the Windows VST3 plugin to a response diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 6f4147d0..7c58daf5 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -1261,12 +1261,10 @@ size_t Vst3Bridge::register_object_instance( true, *request.new_realtime_priority); } - // TODO: This `get()` now moves data. We should avoid - // that, since that would require reallocating the - // process data next iteration. const tresult result = object_instances[request.instance_id] - .audio_processor->process(request.data.get()); + .audio_processor->process( + request.data.reconstruct()); return YaAudioProcessor::ProcessResponse{ .result = result,