From ff0eee3a88c20bab666961fa4ce812101e05223a Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sat, 16 May 2026 17:34:29 +0800 Subject: [PATCH] perf(plugin): cache PluginRenderer per-thread and skip redundant clip prefs Fix three performance traps in the OFX plugin preview pipeline that caused slideshow-like performance when any plugin was active. 1. Eliminate per-frame PluginRenderer creation (GL FBO alloc/free) RenderProcessor is stack-allocated per ticket, so its plugin_renderer_ member was constructed and destroyed every frame. PluginRenderer inherits OpenGLRenderer, whose PostInit() calls glGenFramebuffers() and whose destructor calls glDeleteFramebuffers(). On Apple Silicon's TBDR this is pathologically expensive. Fix: use a thread_local cached PluginRenderer so each render thread creates it only once and reuses it forever. 2. Call getClipPreferences() conditionally The code unconditionally called instance->getClipPreferences() on every single frame. This dispatches kOfxImageEffectActionGetClipPreferences into the plugin even when no inputs or parameters have changed. Fix: check areClipPrefsDirty() first. The OpenFX Host Support library already tracks this flag and sets it to true when slave params or clip connections change. 3. Call ApplyParamOverrides() before renderAction ApplyParamOverrides() was defined but never invoked, so animated plugin parameters were never pushed into the OFX instance before rendering. Fix: call it after beginRenderAction() and before renderAction(), injecting the current NodeValueRow values at the correct OfxTime. --- app/render/plugin/pluginrenderer.cpp | 10 +++++++++- app/render/renderprocessor.cpp | 25 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/render/plugin/pluginrenderer.cpp b/app/render/plugin/pluginrenderer.cpp index cb9b96bde..6bf41f166 100644 --- a/app/render/plugin/pluginrenderer.cpp +++ b/app/render/plugin/pluginrenderer.cpp @@ -1493,7 +1493,11 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: // failure and would otherwise crash the render thread. bool ok = false; try { - ok = instance->getClipPreferences(); + if (instance->areClipPrefsDirty()) { + ok = instance->getClipPreferences(); + } else { + ok = true; + } } catch (const OFX::Host::Property::Exception &e) { qWarning().noquote() << "OFX getClipPreferences threw exception for plugin=" << PluginIdForInstance(instance) @@ -1658,6 +1662,10 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: return; } + // Inject current parameter values into the OFX instance before rendering. + // Parameters are bound to PluginNode inputs, so they change every frame. + ApplyParamOverrides(*instance, job.GetValues(), frame); + // render a frame const char *render_field = GetRenderFieldForParams(output_params); stat = instance->renderAction(frame, render_field, renderWindow, renderScale, diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 28265f803..a628dd9dc 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -628,15 +628,24 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture, return destination; } - if (!plugin_renderer_) { - auto *gl = dynamic_cast(render_ctx_); - if (!gl || !gl->context()) { - return destination; + plugin::PluginRenderer *plugin_renderer = nullptr; + { + thread_local static std::shared_ptr + cached_plugin_renderer; + if (!cached_plugin_renderer) { + auto *gl = dynamic_cast(render_ctx_); + if (gl && gl->context()) { + cached_plugin_renderer = + std::make_shared(); + cached_plugin_renderer->Init(gl->context()); + cached_plugin_renderer->PostInit(); + } } + plugin_renderer = cached_plugin_renderer.get(); + } - plugin_renderer_ = std::make_unique(); - plugin_renderer_->Init(gl->context()); - plugin_renderer_->PostInit(); + if (!plugin_renderer) { + return destination; } NodeValueRow &values = plugin_job->GetValues(); @@ -687,7 +696,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture, } } - plugin_renderer_->RenderPlugin( + plugin_renderer->RenderPlugin( src, *plugin_job, destination,