From 50693acd787c6bf226ed733b68738f939417f45f Mon Sep 17 00:00:00 2001 From: Joseph Lyncheski Date: Thu, 17 Jul 2025 15:06:28 -0400 Subject: [PATCH 1/4] Fix thread-safety when calling request_resize() In the CLAP API they mention that request_resize() in the clap_host_gui struct is thread-safe. This patch checks if the calling thread is the UI thread and if not sets a flag and stores the requested size so ClapAsVst3::onIdle() can handle it. --- src/detail/vst3/plugview.cpp | 13 +++++++++++++ src/detail/vst3/plugview.h | 7 +++++++ src/wrapasvst3.cpp | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/src/detail/vst3/plugview.cpp b/src/detail/vst3/plugview.cpp index da49a465..d8da8177 100644 --- a/src/detail/vst3/plugview.cpp +++ b/src/detail/vst3/plugview.cpp @@ -1,6 +1,7 @@ #include "plugview.h" #include #include +#include #include WrappedView::WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, @@ -103,6 +104,8 @@ tresult PLUGIN_API WrappedView::isPlatformTypeSupported(FIDString type) tresult PLUGIN_API WrappedView::attached(void* parent, FIDString /*type*/) { + _main_thread_id = std::this_thread::get_id(); + #if WIN _window = {CLAP_WINDOW_API_WIN32, {parent}}; #endif @@ -263,11 +266,21 @@ bool WrappedView::request_resize(uint32_t width, uint32_t height) _rect.right = _rect.left + (int32)width; _rect.bottom = _rect.top + (int32)height; + if (_main_thread_id != std::this_thread::get_id()) + { + requested_width = width; + requested_height = height; + // call request_resize again from ClapAsVst3::onIdle() + needs_resize_from_main_thread = true; + return true; + } + if (_plugFrame && !_plugFrame->resizeView(this, &_rect)) { _rect = oldrect; return false; } + return true; } tresult WrappedView::setContentScaleFactor(IPlugViewContentScaleSupport::ScaleFactor factor) diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index 8039770f..da82b5e2 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -13,6 +13,7 @@ #include #include #include +#include using namespace Steinberg; @@ -94,6 +95,11 @@ class WrappedView : public Steinberg::IPlugView, // wrapper needed interfaces bool request_resize(uint32_t width, uint32_t height); + // processed in ClapAsVst3::onIdle() + std::atomic_bool needs_resize_from_main_thread = {false}; + uint32_t requested_width = 0; + uint32_t requested_height = 0; + private: void ensure_ui(); void drop_ui(); @@ -107,6 +113,7 @@ class WrappedView : public Steinberg::IPlugView, ViewRect _rect = {0, 0, 0, 0}; bool _created = false; bool _attached = false; + std::thread::id _main_thread_id{}; #if LIN public: diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index 8328d0a6..ff3b8ff6 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -9,6 +9,7 @@ #include "detail/vst3/process.h" #include "detail/vst3/parameter.h" #include "detail/clap/fsutil.h" +#include #include #include @@ -1292,6 +1293,12 @@ void ClapAsVst3::onIdle() _plugin->_plugin->on_main_thread(_plugin->_plugin); } + if (_wrappedview->needs_resize_from_main_thread) + { + _wrappedview->request_resize(_wrappedview->requested_width, _wrappedview->requested_height); + _wrappedview->needs_resize_from_main_thread = false; + } + #if LIN if (!_iRunLoop) // don't process timers if we have a runloop. // (but if we don't have a runloop on linux onIdle isn't called From 95de52c33a4418b8ef84da660916aef97331ca07 Mon Sep 17 00:00:00 2001 From: Joseph Lyncheski Date: Thu, 17 Jul 2025 15:15:30 -0400 Subject: [PATCH 2/4] Fix missing #include --- src/detail/vst3/plugview.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index da82b5e2..0f215176 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -14,6 +14,7 @@ #include #include #include +#include using namespace Steinberg; From 56c596f288580adee2bb4cba248ac7e20e5d1eca Mon Sep 17 00:00:00 2001 From: Joseph Lyncheski Date: Mon, 21 Jul 2025 10:32:11 -0400 Subject: [PATCH 3/4] Apply @SamWindell's suggestion to keep operation fully atomic Improves thread safety by performing a single atomic operation. This patch also fixes when we set the _rect member in WrappedView to prevent another race condition and ensures that _wrappedview exists when onIdle() is called. --- src/detail/vst3/plugview.cpp | 13 +++++-------- src/detail/vst3/plugview.h | 10 +++++++--- src/wrapasvst3.cpp | 9 ++++++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/detail/vst3/plugview.cpp b/src/detail/vst3/plugview.cpp index d8da8177..209f3977 100644 --- a/src/detail/vst3/plugview.cpp +++ b/src/detail/vst3/plugview.cpp @@ -262,19 +262,16 @@ tresult PLUGIN_API WrappedView::checkSizeConstraint(ViewRect* rect) bool WrappedView::request_resize(uint32_t width, uint32_t height) { - auto oldrect = _rect; - _rect.right = _rect.left + (int32)width; - _rect.bottom = _rect.top + (int32)height; - if (_main_thread_id != std::this_thread::get_id()) { - requested_width = width; - requested_height = height; - // call request_resize again from ClapAsVst3::onIdle() - needs_resize_from_main_thread = true; + resize_request = {width, height}; return true; } + auto oldrect = _rect; + _rect.right = _rect.left + (int32)width; + _rect.bottom = _rect.top + (int32)height; + if (_plugFrame && !_plugFrame->resizeView(this, &_rect)) { _rect = oldrect; diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index 0f215176..86bf9bc1 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -97,9 +97,13 @@ class WrappedView : public Steinberg::IPlugView, bool request_resize(uint32_t width, uint32_t height); // processed in ClapAsVst3::onIdle() - std::atomic_bool needs_resize_from_main_thread = {false}; - uint32_t requested_width = 0; - uint32_t requested_height = 0; + struct GuiResizeRequest + { + bool operator==(GuiResizeRequest const& other) const = default; + uint32_t w, h; + }; + constexpr static GuiResizeRequest invalid_size = {(uint32_t)-1, (uint32_t)-1}; + std::atomic resize_request{invalid_size}; private: void ensure_ui(); diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index ff3b8ff6..91c5fa81 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -1293,10 +1293,13 @@ void ClapAsVst3::onIdle() _plugin->_plugin->on_main_thread(_plugin->_plugin); } - if (_wrappedview->needs_resize_from_main_thread) + if (_wrappedview) { - _wrappedview->request_resize(_wrappedview->requested_width, _wrappedview->requested_height); - _wrappedview->needs_resize_from_main_thread = false; + if (auto const size = _wrappedview->resize_request.exchange(WrappedView::invalid_size); + size != WrappedView::invalid_size) + { + _wrappedview->request_resize(size.w, size.h); + } } #if LIN From 207cc37820813f1ca5c685c12b94460947eb2867 Mon Sep 17 00:00:00 2001 From: Joseph Lyncheski Date: Mon, 21 Jul 2025 19:38:08 -0400 Subject: [PATCH 4/4] Add operator== and operator!= to fix build when using C++17 and earlier --- src/detail/vst3/plugview.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index 86bf9bc1..ac8872f0 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -99,7 +99,14 @@ class WrappedView : public Steinberg::IPlugView, // processed in ClapAsVst3::onIdle() struct GuiResizeRequest { - bool operator==(GuiResizeRequest const& other) const = default; + bool operator==(GuiResizeRequest const& other) const + { + return w == other.w && h == other.h; + } + bool operator!=(GuiResizeRequest const& other) const + { + return w != other.w || h != other.h; + } uint32_t w, h; }; constexpr static GuiResizeRequest invalid_size = {(uint32_t)-1, (uint32_t)-1};