Avoid allocations when reconstructing process data

This commit is contained in:
Robbert van der Helm
2021-05-07 18:21:30 +02:00
parent 814d3c688c
commit 93b8643cba
4 changed files with 85 additions and 76 deletions
@@ -236,7 +236,7 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor {
* Wine plugin host. This `YaProcessData` object wraps around all input * Wine plugin host. This `YaProcessData` object wraps around all input
* audio buffers, parameter changes and events along with all context data * 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 * 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 * reconstruct the original `ProcessData` object, and we then finally use
* `YaProcessData::move_outputs_to_response()` to create a response object * `YaProcessData::move_outputs_to_response()` to create a response object
* that we can write back to the `ProcessData` object provided by the host. * that we can write back to the `ProcessData` object provided by the host.
+71 -56
View File
@@ -20,16 +20,34 @@
YaAudioBusBuffers::YaAudioBusBuffers() {} YaAudioBusBuffers::YaAudioBusBuffers() {}
YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size, void YaAudioBusBuffers::clear(int32 sample_size,
size_t num_samples, size_t num_samples,
size_t num_channels) size_t num_channels) {
: buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64 if (sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64) {
? decltype(buffers)(std::vector<std::vector<double>>( if (!std::holds_alternative<std::vector<std::vector<double>>>(
num_channels, buffers)) {
std::vector<double>(num_samples, 0.0))) buffers.emplace<std::vector<std::vector<double>>>();
: decltype(buffers)(std::vector<std::vector<float>>( }
num_channels,
std::vector<float>(num_samples, 0.0)))) {} std::vector<std::vector<double>>& vector_buffers =
std::get<std::vector<std::vector<double>>>(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<std::vector<std::vector<float>>>(buffers)) {
buffers.emplace<std::vector<std::vector<float>>>();
}
std::vector<std::vector<float>>& vector_buffers =
std::get<std::vector<std::vector<float>>>(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( void YaAudioBusBuffers::repopulate(
int32 sample_size, int32 sample_size,
@@ -73,14 +91,16 @@ void YaAudioBusBuffers::repopulate(
} }
} }
Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { void YaAudioBusBuffers::reconstruct(
Steinberg::Vst::AudioBusBuffers reconstructed_buffers; Steinberg::Vst::AudioBusBuffers& reconstructed_buffers) {
// We'll update the `AudioBusBuffers` object in place to point to our new
// data
reconstructed_buffers.silenceFlags = silence_flags; reconstructed_buffers.silenceFlags = silence_flags;
std::visit(overload{ std::visit(overload{
[&](std::vector<std::vector<double>>& buffers) { [&](std::vector<std::vector<double>>& buffers) {
buffer_pointers.clear(); buffer_pointers.resize(buffers.size());
for (auto& buffer : buffers) { for (size_t i = 0; i < buffers.size(); i++) {
buffer_pointers.push_back(buffer.data()); buffer_pointers[i] = buffers[i].data();
} }
reconstructed_buffers.numChannels = reconstructed_buffers.numChannels =
@@ -89,9 +109,9 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() {
reinterpret_cast<double**>(buffer_pointers.data()); reinterpret_cast<double**>(buffer_pointers.data());
}, },
[&](std::vector<std::vector<float>>& buffers) { [&](std::vector<std::vector<float>>& buffers) {
buffer_pointers.clear(); buffer_pointers.resize(buffers.size());
for (auto& buffer : buffers) { for (size_t i = 0; i < buffers.size(); i++) {
buffer_pointers.push_back(buffer.data()); buffer_pointers[i] = buffers[i].data();
} }
reconstructed_buffers.numChannels = reconstructed_buffers.numChannels =
@@ -101,8 +121,6 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() {
}, },
}, },
buffers); buffers);
return reconstructed_buffers;
} }
size_t YaAudioBusBuffers::num_channels() const { size_t YaAudioBusBuffers::num_channels() const {
@@ -204,52 +222,47 @@ void YaProcessData::repopulate(
} }
} }
Steinberg::Vst::ProcessData& YaProcessData::get() { Steinberg::Vst::ProcessData& YaProcessData::reconstruct() {
// 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();
}
reconstructed_process_data.processMode = process_mode; reconstructed_process_data.processMode = process_mode;
reconstructed_process_data.symbolicSampleSize = symbolic_sample_size; reconstructed_process_data.symbolicSampleSize = symbolic_sample_size;
reconstructed_process_data.numSamples = num_samples; reconstructed_process_data.numSamples = num_samples;
reconstructed_process_data.numInputs = static_cast<int32>(inputs.size()); reconstructed_process_data.numInputs = static_cast<int32>(inputs.size());
reconstructed_process_data.numOutputs = reconstructed_process_data.numOutputs =
static_cast<int32>(outputs_num_channels.size()); static_cast<int32>(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; reconstructed_process_data.inputParameterChanges = &input_parameter_changes;
if (output_parameter_changes_supported) { 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 = reconstructed_process_data.outputParameterChanges =
&*output_parameter_changes; &*output_parameter_changes;
} else { } else {
output_parameter_changes.reset();
reconstructed_process_data.outputParameterChanges = nullptr; reconstructed_process_data.outputParameterChanges = nullptr;
} }
@@ -260,10 +273,12 @@ Steinberg::Vst::ProcessData& YaProcessData::get() {
} }
if (output_events_supported) { if (output_events_supported) {
output_events.emplace(); if (!output_events) {
output_events.emplace();
}
output_events->clear();
reconstructed_process_data.outputEvents = &*output_events; reconstructed_process_data.outputEvents = &*output_events;
} else { } else {
output_events.reset();
reconstructed_process_data.outputEvents = nullptr; reconstructed_process_data.outputEvents = nullptr;
} }
+11 -15
View File
@@ -47,14 +47,9 @@ class YaAudioBusBuffers {
/** /**
* Create a new, zero initialize audio bus buffers object. Used to * Create a new, zero initialize audio bus buffers object. Used to
* reconstruct the output buffers during `YaProcessData::get()`. * reconstruct the output buffers during `YaProcessData::reconstruct()`.
*
* TODO: Replace with a function similar to `repopulate` that just reassigns
* the existing buffers for the outputs created on the Wine side.
*/ */
YaAudioBusBuffers(int32 sample_size, void clear(int32 sample_size, size_t num_samples, size_t num_channels);
size_t num_samples,
size_t num_channels);
/** /**
* Copy data from a host provided `AudioBusBuffers` object during a process * 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 * Reconstruct the original `AudioBusBuffers` object passed to the
* constructor and return it. This is used as part of * constructor and return it. This is used as part of
* `YaProcessData::get()`. The object contains pointers to `buffers`, so it * `YaProcessData::reconstruct()`. The object contains pointers to
* may not outlive this object. * `buffers`, so it may not outlive this object.
* *
* NOTE: The `silenceFlags` field is of course not a reference, so writing * NOTE: The `silenceFlags` field is of course not a reference, so writing
* to that will not modify `silence_flags`. * 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. * 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 * Copy data from a host provided `ProcessData` object during a process
* call. This struct can then be serialized, and `YaProcessData::get()` can * call. This struct can then be serialized, and
* then be used again to recreate the original `ProcessData` object. This * `YaProcessData::reconstruct()` can then be used again to recreate the
* will avoid allocating unless it's absolutely necessary (e.g. when we * original `ProcessData` object. This will avoid allocating unless it's
* receive more parameter changes than we've received in previous calls). * 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); 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 * and return it. This is used in the Wine plugin host when processing an
* `IAudioProcessor::process()` call. * `IAudioProcessor::process()` call.
*/ */
Steinberg::Vst::ProcessData& get(); Steinberg::Vst::ProcessData& reconstruct();
/** /**
* **Move** all output written by the Windows VST3 plugin to a response * **Move** all output written by the Windows VST3 plugin to a response
+2 -4
View File
@@ -1261,12 +1261,10 @@ size_t Vst3Bridge::register_object_instance(
true, *request.new_realtime_priority); 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 = const tresult result =
object_instances[request.instance_id] object_instances[request.instance_id]
.audio_processor->process(request.data.get()); .audio_processor->process(
request.data.reconstruct());
return YaAudioProcessor::ProcessResponse{ return YaAudioProcessor::ProcessResponse{
.result = result, .result = result,