fix: OFX param instance lifecycle and thread-safety fixes

- Fix SIGSEGV in PluginMisc.Keyer by linking Param::SetInstance to instances.
    Olive's custom param instances (IntegerInstance, DoubleInstance, etc.) were
    not passing the SetInstance pointer to the OpenFX HostSupport base class,
    leaving _paramSetInstance as nullptr. When Keyer called paramSetValue during
    createInstanceAction, the suite function dereferenced the null pointer in
    paramChangedByPlugin(). Now newParam() passes 'this' to every constructor.

  - Fix render-thread crash when OFX plugins set params during rendering.
    MinOFX calls paramSetValue inside createInstanceAction from the render
    thread. SubmitUndoCommand() used to push undo commands directly to
    UndoStack, which modifies QAction state (GUI-only). Added IsGuiThread()
    check: non-GUI threads execute redo_now() and discard the command without
    touching the undo stack.

  - Enable PluginMisc.MergeOver and PluginMisc.Keyer integration tests.
    MergeOver now supplies both Source and Bg textures; Keyer uses U16 format.
    Both pass in the full test suite.

  - Make ViewerQueue thread-safe with QMutex around AppendTimewise/PurgeBefore.
    Adds copy ctor and assignment to support mutex-per-instance semantics.
This commit is contained in:
2026-05-14 22:32:34 +08:00
parent 4aa4d59770
commit e85c6cf60a
6 changed files with 122 additions and 67 deletions
+21 -23
View File
@@ -69,14 +69,6 @@ QString FormatOfxMessage(const char *format, va_list args)
return QString::fromUtf8(dynamic_buffer.constData());
}
bool IsGuiThread()
{
if (auto *app = QCoreApplication::instance()) {
return QThread::currentThread() == app->thread();
}
return true;
}
const std::string &FieldOrderForParams(const VideoParams &params)
{
switch (params.interlacing()) {
@@ -329,36 +321,36 @@ OlivePluginInstance::newParam(const std::string &name,
const std::string &type = desc.getType();
if (type == kOfxParamTypeInteger) {
return new IntegerInstance(node_, desc);
return new IntegerInstance(node_, desc, this);
} else if (type == kOfxParamTypeDouble) {
return new DoubleInstance(node_, name, desc);
return new DoubleInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeBoolean) {
return new BooleanInstance(node_, name, desc);
return new BooleanInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeChoice) {
return new ChoiceInstance(node_, name, desc);
return new ChoiceInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeString) {
return new StringInstance(node_, name, desc);
return new StringInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeRGBA) {
return new RGBAInstance(node_, name, desc);
return new RGBAInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeRGB) {
return new RGBInstance(node_, name, desc);
return new RGBInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeDouble2D) {
return new Double2DInstance(node_, name, desc);
return new Double2DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeInteger2D) {
return new Integer2DInstance(node_, name, desc);
return new Integer2DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeDouble3D) {
return new Double3DInstance(node_, name, desc);
return new Double3DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeInteger3D) {
return new Integer3DInstance(node_, name, desc);
return new Integer3DInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeCustom ||
type == kOfxParamTypeBytes) {
return new CustomInstance(node_, name, desc);
return new CustomInstance(node_, name, desc, this);
} else if (type == kOfxParamTypeGroup) {
return new GroupInstance(desc);
return new GroupInstance(desc, this);
} else if (type == kOfxParamTypePage) {
return new PageInstance(desc);
return new PageInstance(desc, this);
} else if (type == kOfxParamTypePushButton) {
return new PushbuttonInstance(node_, name, desc);
return new PushbuttonInstance(node_, name, desc, this);
}
return nullptr; // 未实现的类型
@@ -431,6 +423,12 @@ void OlivePluginInstance::SubmitUndoCommand(UndoCommand *command,
return;
}
if (!IsGuiThread()) {
command->redo_now();
delete command;
return;
}
Core::instance()->undo_stack()->push(command, label);
}