From 0143d43c7e33303f6b1403118f32a4dcac51baa5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 10 Oct 2022 16:08:26 +0200 Subject: [PATCH] Implement the CLAP render extension --- src/common/serialization/clap.h | 2 + src/common/serialization/clap/README.md | 2 +- src/common/serialization/clap/plugin.cpp | 4 +- src/common/serialization/clap/plugin.h | 4 +- .../bridges/clap-impls/plugin-proxy.cpp | 28 +++++++++ src/plugin/bridges/clap-impls/plugin-proxy.h | 7 +++ src/wine-host/bridges/clap.cpp | 60 +++++++++++++++++-- src/wine-host/bridges/clap.h | 9 +++ 8 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/common/serialization/clap.h b/src/common/serialization/clap.h index 981a988b..f2b2f74d 100644 --- a/src/common/serialization/clap.h +++ b/src/common/serialization/clap.h @@ -81,6 +81,8 @@ using ClapMainThreadControlRequest = clap::ext::params::plugin::GetValue, clap::ext::params::plugin::ValueToText, clap::ext::params::plugin::TextToValue, + clap::ext::render::plugin::HasHardRealtimeRequirement, + clap::ext::render::plugin::Set, clap::ext::state::plugin::Save, clap::ext::state::plugin::Load, clap::ext::voice_info::plugin::Get>; diff --git a/src/common/serialization/clap/README.md b/src/common/serialization/clap/README.md index ec015030..a0acafcc 100644 --- a/src/common/serialization/clap/README.md +++ b/src/common/serialization/clap/README.md @@ -27,7 +27,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core | `clap.note-ports` | :heavy_check_mark: | | `clap.params` | :heavy_check_mark: | | `clap.posix-fd-support` | :x: Not supported yet | -| `clap.render` | :x: Not supported yet | +| `clap.render` | :heavy_check_mark: | | `clap.state` | :heavy_check_mark: | | `clap.tail` | :heavy_check_mark: | | `clap.thread-check` | :heavy_check_mark: No bridging involved | diff --git a/src/common/serialization/clap/plugin.cpp b/src/common/serialization/clap/plugin.cpp index f037fa26..79be7775 100644 --- a/src/common/serialization/clap/plugin.cpp +++ b/src/common/serialization/clap/plugin.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -83,13 +84,14 @@ const clap_plugin_descriptor_t* Descriptor::get() const { return &clap_descriptor; } -std::array, 8> SupportedPluginExtensions::list() +std::array, 9> SupportedPluginExtensions::list() const noexcept { return {std::pair(supports_audio_ports, CLAP_EXT_AUDIO_PORTS), std::pair(supports_gui, CLAP_EXT_GUI), std::pair(supports_latency, CLAP_EXT_LATENCY), std::pair(supports_note_ports, CLAP_EXT_NOTE_PORTS), std::pair(supports_params, CLAP_EXT_PARAMS), + std::pair(supports_render, CLAP_EXT_RENDER), std::pair(supports_state, CLAP_EXT_STATE), std::pair(supports_tail, CLAP_EXT_TAIL), std::pair(supports_voice_info, CLAP_EXT_VOICE_INFO)}; diff --git a/src/common/serialization/clap/plugin.h b/src/common/serialization/clap/plugin.h index 594aa202..b9663bab 100644 --- a/src/common/serialization/clap/plugin.h +++ b/src/common/serialization/clap/plugin.h @@ -120,6 +120,7 @@ struct SupportedPluginExtensions { bool supports_latency = false; bool supports_note_ports = false; bool supports_params = false; + bool supports_render = false; bool supports_state = false; bool supports_tail = false; bool supports_voice_info = false; @@ -128,7 +129,7 @@ struct SupportedPluginExtensions { * Get a list of `` tuples for the supported * extensions. Used during logging. */ - std::array, 8> list() const noexcept; + std::array, 9> list() const noexcept; template void serialize(S& s) { @@ -137,6 +138,7 @@ struct SupportedPluginExtensions { s.value1b(supports_latency); s.value1b(supports_note_ports); s.value1b(supports_params); + s.value1b(supports_render); s.value1b(supports_state); s.value1b(supports_tail); s.value1b(supports_voice_info); diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.cpp b/src/plugin/bridges/clap-impls/plugin-proxy.cpp index c3000927..9d862e29 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/clap-impls/plugin-proxy.cpp @@ -112,6 +112,11 @@ clap_plugin_proxy::clap_plugin_proxy(ClapPluginBridge& bridge, .text_to_value = ext_params_text_to_value, .flush = ext_params_flush, }), + ext_render_vtable(clap_plugin_render_t{ + .has_hard_realtime_requirement = + ext_render_has_hard_realtime_requirement, + .set = ext_render_set, + }), ext_state_vtable(clap_plugin_state_t{ .save = ext_state_save, .load = ext_state_load, @@ -309,6 +314,9 @@ clap_plugin_proxy::plugin_get_extension(const struct clap_plugin* plugin, } else if (self->supported_extensions_.supports_params && strcmp(id, CLAP_EXT_PARAMS) == 0) { extension_ptr = &self->ext_params_vtable; + } else if (self->supported_extensions_.supports_render && + strcmp(id, CLAP_EXT_RENDER) == 0) { + extension_ptr = &self->ext_render_vtable; } else if (self->supported_extensions_.supports_state && strcmp(id, CLAP_EXT_STATE) == 0) { extension_ptr = &self->ext_state_vtable; @@ -732,6 +740,26 @@ clap_plugin_proxy::ext_params_flush(const clap_plugin_t* plugin, response.out.write_back_outputs(*out); } +bool CLAP_ABI clap_plugin_proxy::ext_render_has_hard_realtime_requirement( + const clap_plugin_t* plugin) { + assert(plugin && plugin->plugin_data); + auto self = static_cast(plugin->plugin_data); + + return self->bridge_.send_main_thread_message( + clap::ext::render::plugin::HasHardRealtimeRequirement{ + .instance_id = self->instance_id()}); +} + +bool CLAP_ABI clap_plugin_proxy::ext_render_set(const clap_plugin_t* plugin, + clap_plugin_render_mode mode) { + assert(plugin && plugin->plugin_data); + auto self = static_cast(plugin->plugin_data); + + return self->bridge_.send_main_thread_message( + clap::ext::render::plugin::Set{.instance_id = self->instance_id(), + .mode = mode}); +} + bool CLAP_ABI clap_plugin_proxy::ext_state_save(const clap_plugin_t* plugin, const clap_ostream_t* stream) { assert(plugin && plugin->plugin_data && stream); diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.h b/src/plugin/bridges/clap-impls/plugin-proxy.h index 4fc8b1db..066ba273 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.h +++ b/src/plugin/bridges/clap-impls/plugin-proxy.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -258,6 +259,11 @@ class clap_plugin_proxy { const clap_input_events_t* in, const clap_output_events_t* out); + static bool CLAP_ABI + ext_render_has_hard_realtime_requirement(const clap_plugin_t* plugin); + static bool CLAP_ABI ext_render_set(const clap_plugin_t* plugin, + clap_plugin_render_mode mode); + static bool CLAP_ABI ext_state_save(const clap_plugin_t* plugin, const clap_ostream_t* stream); static bool CLAP_ABI ext_state_load(const clap_plugin_t* plugin, @@ -329,6 +335,7 @@ class clap_plugin_proxy { const clap_plugin_latency_t ext_latency_vtable; const clap_plugin_note_ports_t ext_note_ports_vtable; const clap_plugin_params_t ext_params_vtable; + const clap_plugin_render_t ext_render_vtable; const clap_plugin_state_t ext_state_vtable; const clap_plugin_tail_t ext_tail_vtable; const clap_plugin_voice_info_t ext_voice_info_vtable; diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp index e51f3ffe..abf41e4b 100644 --- a/src/wine-host/bridges/clap.cpp +++ b/src/wine-host/bridges/clap.cpp @@ -37,6 +37,8 @@ ClapPluginExtensions::ClapPluginExtensions(const clap_plugin& plugin) noexcept plugin.get_extension(&plugin, CLAP_EXT_NOTE_PORTS))), params(static_cast( plugin.get_extension(&plugin, CLAP_EXT_PARAMS))), + render(static_cast( + plugin.get_extension(&plugin, CLAP_EXT_RENDER))), state(static_cast( plugin.get_extension(&plugin, CLAP_EXT_STATE))), tail(static_cast( @@ -54,6 +56,7 @@ clap::plugin::SupportedPluginExtensions ClapPluginExtensions::supported() .supports_latency = latency != nullptr, .supports_note_ports = note_ports != nullptr, .supports_params = params != nullptr, + .supports_render = render != nullptr, .supports_state = state != nullptr, .supports_tail = tail != nullptr, .supports_voice_info = voice_info != nullptr}; @@ -710,6 +713,40 @@ void ClapBridge::run() { .result = std::nullopt}; } }, + [&](clap::ext::render::plugin::HasHardRealtimeRequirement& request) + -> clap::ext::render::plugin::HasHardRealtimeRequirement:: + Response { + const auto& [instance, _] = + get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + render = + instance.extensions.render]() { + return render->has_hard_realtime_requirement( + plugin); + }) + .get(); + }, + [&](clap::ext::render::plugin::Set& request) + -> clap::ext::render::plugin::Set::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + render = instance.extensions.render, + &render_mode = instance.render_mode]() { + if (render->set(plugin, request.mode)) { + // This is used later to do offline processing on + // the GUI thread + render_mode = request.mode; + return true; + } else { + return false; + } + }) + .get(); + }, [&](clap::ext::state::plugin::Save& request) -> clap::ext::state::plugin::Save::Response { const auto& [instance, _] = get_instance(request.instance_id); @@ -1005,13 +1042,28 @@ void ClapBridge::register_plugin_instance( // The actual audio is stored in the shared memory // buffers, so the reconstruction function will need to // know where it should point the `AudioBusBuffers` to - // TODO: Once we add the render extension, process on the - // main thread when doing offline rendering + // HACK: The VST3 version of IK-Multimedia's T-RackS 5 will + // hang if audio processing is done from the audio + // thread while the plugin is in offline processing + // mode. So as a precaution, we'll also do offline + // processing for CLAP plugins on the GUI thread. + clap_process_status result; auto& reconstructed = request.process.reconstruct( instance.process_buffers_input_pointers, instance.process_buffers_output_pointers); - clap_process_status result = instance.plugin->process( - instance.plugin.get(), &reconstructed); + if (instance.render_mode == CLAP_RENDER_OFFLINE) { + result = + main_context_ + .run_in_context([&instance = instance, + &reconstructed]() { + return instance.plugin->process( + instance.plugin.get(), &reconstructed); + }) + .get(); + } else { + result = instance.plugin->process(instance.plugin.get(), + &reconstructed); + } return clap::plugin::ProcessResponse{ .result = result, diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h index b737b7e5..4b7c70ac 100644 --- a/src/wine-host/bridges/clap.h +++ b/src/wine-host/bridges/clap.h @@ -71,6 +71,7 @@ struct ClapPluginExtensions { const clap_plugin_latency_t* latency = nullptr; const clap_plugin_note_ports_t* note_ports = nullptr; const clap_plugin_params_t* params = nullptr; + const clap_plugin_render_t* render = nullptr; const clap_plugin_state_t* state = nullptr; const clap_plugin_tail_t* tail = nullptr; const clap_plugin_voice_info_t* voice_info = nullptr; @@ -161,6 +162,14 @@ struct ClapPluginInstance { * it, so we're also preventing this for CLAP as a precaution. */ bool is_initialized = false; + + /** + * The instance's current rendering mode. This should not be needed, but + * we'll process offline renders on the main thread. T-RackS 5's VST2 and + * VST3 versions deadlock if offline rendering is done from an audio thread, + * so we'll take precautions and also do this for CLAP. + */ + clap_plugin_render_mode render_mode = CLAP_RENDER_REALTIME; }; /**