Files
oak-editor/tests/gtest/plugin_paraminstance_test.cpp
T
Mike-Solar bb40b4923e style: unify identifier naming per updated conventions
Automated with clang-tidy readability-identifier-naming (config added to
.clang-tidy) plus scripted passes, per the updated rules now documented
in CONTRIBUTING.md:

- types (class/struct/enum/alias/template params): PascalCase
- functions, variables, members: snake_case (incl. rational -> Rational)
- private/protected members: trailing underscore; static member
  variables likewise (instance_, available_themes_)
- constants and enum values: snake_case (kLinear -> k_linear,
  F32P -> f32p); ALL_CAPS reserved for macros
- macros: OAK_ prefix (OLIVE_ADD_TEST/OLIVE_ASSERT/OLIVE_CONFIG ->
  OAK_ADD_TEST/OAK_ASSERT/OAK_CONFIG, GL_PREAMBLE -> OAK_GL_PREAMBLE,
  include guards -> OAK_*)
- file names: all lowercase (Current/Plugin/OliveHost/OliveClip/
  OlivePluginInstance -> current/plugin/olivehost/oliveclip/
  oliveplugininstance)
- getters share the member name sans underscore, setters set_foo()
- Qt and third-party (OpenFX) virtual overrides and framework callbacks
  keep their original names (exempt in .clang-tidy)

Manual follow-ups required where automation could not reach:
- string-based QMetaObject/SIGNAL/SLOT references updated to renamed
  methods (AddTask, CreatedFile, DeleteSpecificFile, moveSelectionUp, ...)
- macro bodies referencing renamed methods (OLIVE_CONFIG,
  NODE_DEFAULT_DESTRUCTOR, MANAGEDDISPLAYWIDGET_*)
- self-shadowing locals renamed where signals/methods became same-named
  (size_changed, worker_count, selected_items, import param, filters)
- third_party OFX member/namespace usages restored (OFX::Host::*,
  _created, _clipPrefsDirty, createInstance, clearPersistentMessage)
- STL protocol aliases restored (const_iterator) with .clang-tidy
  ignore rules; qHash overloads restored

Full build and test suite pass: ctest 4/4, ~1960 gtest cases green.
2026-07-19 16:10:54 +08:00

922 lines
31 KiB
C++

#include <gtest/gtest.h>
#include <limits>
#include "ofxImageEffect.h"
#include "ofxParam.h"
#include "ofxhClip.h"
#include "ofxhParam.h"
#include "common/avframeptr.h"
#include "common/ffmpegutils.h"
#include "pluginSupport/oliveclip.h"
#include "pluginSupport/image.h"
#include "pluginSupport/paraminstance.h"
#include "render/texture.h"
namespace
{
olive::VideoParams make_params(int width, int height,
olive::core::PixelFormat format, int channels,
bool premultiplied)
{
olive::VideoParams params;
params.set_width(width);
params.set_height(height);
params.set_format(format);
params.set_channel_count(channels);
params.set_premultiplied_alpha(premultiplied);
return params;
}
olive::AVFramePtr create_frame(const olive::VideoParams &params)
{
olive::AVFramePtr frame = olive::create_av_frame_ptr();
frame->set_format(olive::FFmpegUtils::get_f_fmpeg_pixel_format(
params.format(), params.channel_count()));
frame->set_width(params.width());
frame->set_height(params.height());
if (frame->get_buffer(0) < 0) {
return nullptr;
}
return frame;
}
}
// ============================================================================
// app/pluginSupport/paraminstance.h
//
// The node-bound code paths require a PluginNode, which dereferences a real
// OFX ImageEffect::Instance in its constructor and therefore cannot be built
// without a plugin bundle. These tests cover the host-side fallback surface:
// descriptor defaults and the cached-value behavior when no node is bound.
// ============================================================================
TEST(PluginParamInstance, CoordinateSystemHelpers)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble,
"TestCoordinate");
// A bare descriptor has no coordinate-system property; the OFX property
// suite reports an empty string which is treated as canonical.
EXPECT_FALSE(olive::plugin::is_normalised_coordinate_system(descriptor));
descriptor.addStandardParamProps(kOfxParamTypeDouble);
EXPECT_FALSE(olive::plugin::is_normalised_coordinate_system(descriptor));
descriptor.getProperties().setStringProperty(
kOfxParamPropDefaultCoordinateSystem, kOfxParamCoordinatesNormalised);
EXPECT_TRUE(olive::plugin::is_normalised_coordinate_system(descriptor));
EXPECT_DOUBLE_EQ(olive::plugin::to_normalised(960.0, 1920.0), 0.5);
EXPECT_DOUBLE_EQ(olive::plugin::to_canonical(0.5, 1920.0), 960.0);
// A non-positive extent passes the value through unchanged.
EXPECT_DOUBLE_EQ(olive::plugin::to_normalised(7.5, 0.0), 7.5);
EXPECT_DOUBLE_EQ(olive::plugin::to_canonical(7.5, 0.0), 7.5);
}
TEST(PluginParamInstance, ParamChangeLabelContainsParamName)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble, "Gain");
EXPECT_EQ(olive::plugin::param_change_label(descriptor),
QStringLiteral("Change Gain"));
}
TEST(PluginParamInstance, SubmitUndoCommandIgnoresNullCommand)
{
EXPECT_NO_THROW(olive::plugin::submit_undo_command(
nullptr, nullptr, QStringLiteral("Ignored")));
}
TEST(PluginParamInstance, IntegerInstanceUsesDescriptorDefault)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeInteger,
"TestIntegerDefault");
descriptor.addStandardParamProps(kOfxParamTypeInteger);
descriptor.getProperties().setIntProperty(kOfxParamPropDefault, 42);
olive::plugin::IntegerInstance instance(nullptr, descriptor);
int value = -1;
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, 42);
int time_value = -1;
EXPECT_EQ(instance.get(1.5, time_value), kOfxStatOK);
EXPECT_EQ(time_value, 42);
// Rebinding to the same (null) node keeps the cached value.
instance.set_node(nullptr);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, 42);
}
TEST(PluginParamInstance, DoubleInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor bare(kOfxParamTypeDouble, "TestDoubleBare");
olive::plugin::DoubleInstance bare_instance(nullptr, "TestDoubleBare",
bare);
double value = -1.0;
EXPECT_EQ(bare_instance.get(value), kOfxStatOK);
EXPECT_DOUBLE_EQ(value, 0.0);
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble, "TestDouble");
descriptor.addStandardParamProps(kOfxParamTypeDouble);
descriptor.getProperties().setDoubleProperty(kOfxParamPropDefault, 3.5);
olive::plugin::DoubleInstance instance(nullptr, "TestDouble", descriptor);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_DOUBLE_EQ(value, 3.5);
EXPECT_EQ(instance.set(7.25), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_DOUBLE_EQ(value, 7.25);
double time_value = 0.0;
EXPECT_EQ(instance.get(1.5, time_value), kOfxStatOK);
EXPECT_DOUBLE_EQ(time_value, 7.25);
EXPECT_EQ(instance.set(0.5, -2.5), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_DOUBLE_EQ(value, -2.5);
double derived = 1.0;
EXPECT_EQ(instance.derive(0.0, derived), kOfxStatErrUnsupported);
double integrated = 1.0;
EXPECT_EQ(instance.integrate(0.0, 1.0, integrated),
kOfxStatErrUnsupported);
}
TEST(PluginParamInstance, BooleanInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor bare(kOfxParamTypeBoolean, "TestBooleanBare");
olive::plugin::BooleanInstance bare_instance(nullptr, "TestBooleanBare",
bare);
bool value = true;
EXPECT_EQ(bare_instance.get(value), kOfxStatOK);
EXPECT_FALSE(value);
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeBoolean,
"TestBoolean");
descriptor.addStandardParamProps(kOfxParamTypeBoolean);
descriptor.getProperties().setIntProperty(kOfxParamPropDefault, 1);
olive::plugin::BooleanInstance instance(nullptr, "TestBoolean", descriptor);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_TRUE(value);
EXPECT_EQ(instance.set(false), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_FALSE(value);
EXPECT_EQ(instance.set(1.0, true), kOfxStatOK);
bool time_value = false;
EXPECT_EQ(instance.get(1.0, time_value), kOfxStatOK);
EXPECT_TRUE(time_value);
}
TEST(PluginParamInstance, ChoiceInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeChoice, "TestChoice");
descriptor.addStandardParamProps(kOfxParamTypeChoice);
descriptor.getProperties().setIntProperty(kOfxParamPropDefault, 2);
olive::plugin::ChoiceInstance instance(nullptr, "TestChoice", descriptor);
int value = -1;
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, 2);
EXPECT_EQ(instance.set(4), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, 4);
int time_value = -1;
EXPECT_EQ(instance.get(2.0, time_value), kOfxStatOK);
EXPECT_EQ(time_value, 4);
}
TEST(PluginParamInstance, StringInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeString, "TestString");
descriptor.addStandardParamProps(kOfxParamTypeString);
descriptor.getProperties().setStringProperty(kOfxParamPropDefault,
"default_text");
olive::plugin::StringInstance instance(nullptr, "TestString", descriptor);
std::string value;
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, "default_text");
EXPECT_EQ(instance.set("hello world"), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, "hello world");
std::string time_value;
EXPECT_EQ(instance.get(3.0, time_value), kOfxStatOK);
EXPECT_EQ(time_value, "hello world");
// A null C-string is stored as an empty string.
EXPECT_EQ(instance.set(nullptr), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_TRUE(value.empty());
}
TEST(PluginParamInstance, CustomInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeCustom, "TestCustom");
olive::plugin::CustomInstance instance(nullptr, "TestCustom", descriptor);
std::string value = "sentinel";
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_TRUE(value.empty());
EXPECT_EQ(instance.set("binary_blob"), kOfxStatOK);
EXPECT_EQ(instance.get(value), kOfxStatOK);
EXPECT_EQ(value, "binary_blob");
std::string time_value;
EXPECT_EQ(instance.get(0.25, time_value), kOfxStatOK);
EXPECT_EQ(time_value, "binary_blob");
}
TEST(PluginParamInstance, RGBAInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeRGBA, "TestRGBA");
olive::plugin::RGBAInstance instance(nullptr, "TestRGBA", descriptor);
double r = -1.0, g = -1.0, b = -1.0, a = -1.0;
EXPECT_EQ(instance.get(r, g, b, a), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 0.0);
EXPECT_DOUBLE_EQ(g, 0.0);
EXPECT_DOUBLE_EQ(b, 0.0);
EXPECT_DOUBLE_EQ(a, 0.0);
EXPECT_EQ(instance.set(0.1, 0.2, 0.3, 0.4), kOfxStatOK);
EXPECT_EQ(instance.get(r, g, b, a), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 0.1);
EXPECT_DOUBLE_EQ(g, 0.2);
EXPECT_DOUBLE_EQ(b, 0.3);
EXPECT_DOUBLE_EQ(a, 0.4);
EXPECT_EQ(instance.set(2.0, 0.5, 0.6, 0.7, 0.8), kOfxStatOK);
EXPECT_EQ(instance.get(2.0, r, g, b, a), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 0.5);
EXPECT_DOUBLE_EQ(g, 0.6);
EXPECT_DOUBLE_EQ(b, 0.7);
EXPECT_DOUBLE_EQ(a, 0.8);
}
TEST(PluginParamInstance, RGBInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeRGB, "TestRGB");
olive::plugin::RGBInstance instance(nullptr, "TestRGB", descriptor);
double r = -1.0, g = -1.0, b = -1.0;
EXPECT_EQ(instance.get(r, g, b), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 0.0);
EXPECT_DOUBLE_EQ(g, 0.0);
EXPECT_DOUBLE_EQ(b, 0.0);
EXPECT_EQ(instance.set(1.0, 0.5, 0.25), kOfxStatOK);
EXPECT_EQ(instance.get(r, g, b), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 1.0);
EXPECT_DOUBLE_EQ(g, 0.5);
EXPECT_DOUBLE_EQ(b, 0.25);
EXPECT_EQ(instance.set(1.5, 0.75, 0.5, 0.125), kOfxStatOK);
EXPECT_EQ(instance.get(1.5, r, g, b), kOfxStatOK);
EXPECT_DOUBLE_EQ(r, 0.75);
EXPECT_DOUBLE_EQ(g, 0.5);
EXPECT_DOUBLE_EQ(b, 0.125);
}
TEST(PluginParamInstance, Double2DInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble2D,
"TestDouble2D");
olive::plugin::Double2DInstance instance(nullptr, "TestDouble2D",
descriptor);
double x = -1.0, y = -1.0;
EXPECT_EQ(instance.get(x, y), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, 0.0);
EXPECT_DOUBLE_EQ(y, 0.0);
EXPECT_EQ(instance.set(1.5, -2.5), kOfxStatOK);
EXPECT_EQ(instance.get(x, y), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, 1.5);
EXPECT_DOUBLE_EQ(y, -2.5);
EXPECT_EQ(instance.set(0.5, 3.0, 4.0), kOfxStatOK);
EXPECT_EQ(instance.get(0.5, x, y), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, 3.0);
EXPECT_DOUBLE_EQ(y, 4.0);
}
TEST(PluginParamInstance, Integer2DInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeInteger2D,
"TestInteger2D");
olive::plugin::Integer2DInstance instance(nullptr, "TestInteger2D",
descriptor);
int x = -1, y = -1;
EXPECT_EQ(instance.get(x, y), kOfxStatOK);
EXPECT_EQ(x, 0);
EXPECT_EQ(y, 0);
EXPECT_EQ(instance.set(3, -7), kOfxStatOK);
EXPECT_EQ(instance.get(x, y), kOfxStatOK);
EXPECT_EQ(x, 3);
EXPECT_EQ(y, -7);
EXPECT_EQ(instance.set(2.0, 10, 20), kOfxStatOK);
EXPECT_EQ(instance.get(2.0, x, y), kOfxStatOK);
EXPECT_EQ(x, 10);
EXPECT_EQ(y, 20);
}
TEST(PluginParamInstance, Double3DInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble3D,
"TestDouble3D");
olive::plugin::Double3DInstance instance(nullptr, "TestDouble3D",
descriptor);
double x = -1.0, y = -1.0, z = -1.0;
EXPECT_EQ(instance.get(x, y, z), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, 0.0);
EXPECT_DOUBLE_EQ(y, 0.0);
EXPECT_DOUBLE_EQ(z, 0.0);
EXPECT_EQ(instance.set(1.0, 2.0, 3.0), kOfxStatOK);
EXPECT_EQ(instance.get(x, y, z), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, 1.0);
EXPECT_DOUBLE_EQ(y, 2.0);
EXPECT_DOUBLE_EQ(z, 3.0);
EXPECT_EQ(instance.set(4.0, -1.0, -2.0, -3.0), kOfxStatOK);
EXPECT_EQ(instance.get(4.0, x, y, z), kOfxStatOK);
EXPECT_DOUBLE_EQ(x, -1.0);
EXPECT_DOUBLE_EQ(y, -2.0);
EXPECT_DOUBLE_EQ(z, -3.0);
}
TEST(PluginParamInstance, Integer3DInstanceNullNodeRoundTrip)
{
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeInteger3D,
"TestInteger3D");
olive::plugin::Integer3DInstance instance(nullptr, "TestInteger3D",
descriptor);
int x = -1, y = -1, z = -1;
EXPECT_EQ(instance.get(x, y, z), kOfxStatOK);
EXPECT_EQ(x, 0);
EXPECT_EQ(y, 0);
EXPECT_EQ(z, 0);
EXPECT_EQ(instance.set(-1, 0, 5), kOfxStatOK);
EXPECT_EQ(instance.get(x, y, z), kOfxStatOK);
EXPECT_EQ(x, -1);
EXPECT_EQ(y, 0);
EXPECT_EQ(z, 5);
EXPECT_EQ(instance.set(3.0, 7, 8, 9), kOfxStatOK);
EXPECT_EQ(instance.get(3.0, x, y, z), kOfxStatOK);
EXPECT_EQ(x, 7);
EXPECT_EQ(y, 8);
EXPECT_EQ(z, 9);
}
TEST(PluginParamInstance, PushbuttonGroupAndPageInstancesExposeNames)
{
OFX::Host::Param::Descriptor button_desc(kOfxParamTypePushButton,
"TestButton");
olive::plugin::PushbuttonInstance button(nullptr, "TestButton",
button_desc);
button.set_node(nullptr);
EXPECT_EQ(button.getName(), "TestButton");
OFX::Host::Param::Descriptor group_desc(kOfxParamTypeGroup, "TestGroup");
olive::plugin::GroupInstance group(group_desc);
EXPECT_EQ(group.getName(), "TestGroup");
OFX::Host::Param::Descriptor page_desc(kOfxParamTypePage, "TestPage");
olive::plugin::PageInstance page(page_desc);
EXPECT_EQ(page.getName(), "TestPage");
}
// ============================================================================
// app/pluginSupport/OliveClip.cpp
// ============================================================================
TEST(PluginClipInstance, UnmappedBitDepthFallsBackToParams)
{
// U16 -> kOfxBitDepthShort is covered by PluginSupportClip.PropertyGetters.
struct Case {
olive::core::PixelFormat format;
const char *expected;
};
const Case cases[] = {
{ olive::core::PixelFormat::u8, kOfxBitDepthByte },
{ olive::core::PixelFormat::u10, kOfxBitDepthNone },
{ olive::core::PixelFormat::f16, kOfxBitDepthHalf },
{ olive::core::PixelFormat::f32, kOfxBitDepthFloat },
{ olive::core::PixelFormat::invalid, kOfxBitDepthNone },
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
SCOPED_TRACE(i);
OFX::Host::ImageEffect::ClipDescriptor desc(
kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, cases[i].format, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
EXPECT_EQ(clip.getUnmappedBitDepth(), cases[i].expected);
}
}
TEST(PluginClipInstance, UnmappedBitDepthPrefersPluginChoice)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
clip.setPixelDepth(kOfxBitDepthFloat);
EXPECT_EQ(clip.getUnmappedBitDepth(), kOfxBitDepthFloat);
// kOfxBitDepthNone means "no preference" and falls back to the params.
clip.setPixelDepth(kOfxBitDepthNone);
EXPECT_EQ(clip.getUnmappedBitDepth(), kOfxBitDepthByte);
}
TEST(PluginClipInstance, UnmappedComponentsFallsBackToParams)
{
// 3 channels -> kOfxImageComponentRGB is covered by
// PluginSupportClip.PropertyGetters.
struct Case {
int channels;
const char *expected;
};
const Case cases[] = {
{ 1, kOfxImageComponentAlpha },
{ 4, kOfxImageComponentRGBA },
{ 2, kOfxImageComponentNone },
{ 0, kOfxImageComponentNone },
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
SCOPED_TRACE(i);
OFX::Host::ImageEffect::ClipDescriptor desc(
kOfxImageEffectOutputClipName);
olive::VideoParams params = make_params(
16, 16, olive::core::PixelFormat::u8, cases[i].channels, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
EXPECT_EQ(clip.getUnmappedComponents(), cases[i].expected);
}
}
TEST(PluginClipInstance, UnmappedComponentsPrefersPluginChoice)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
clip.setComponents(kOfxImageComponentAlpha);
EXPECT_EQ(clip.getUnmappedComponents(), kOfxImageComponentAlpha);
clip.setComponents(kOfxImageComponentNone);
EXPECT_EQ(clip.getUnmappedComponents(), kOfxImageComponentRGBA);
}
TEST(PluginClipInstance, PremultReflectsPremultipliedParams)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, true);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
EXPECT_EQ(clip.getPremult(), kOfxImagePreMultiplied);
}
TEST(PluginClipInstance, AspectRatioDefaultsToOneForZeroPar)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
params.set_pixel_aspect_ratio(olive::core::Rational(0, 1));
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
EXPECT_DOUBLE_EQ(clip.getAspectRatio(), 1.0);
}
TEST(PluginClipInstance, FieldOrderNoneAndLower)
{
// kInterlacedTopFirst -> kOfxImageFieldUpper is covered by
// PluginSupportClip.PropertyGetters.
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams progressive =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
progressive.set_interlacing(olive::VideoParams::k_interlace_none);
olive::plugin::OliveClipInstance progressive_clip(nullptr, desc,
progressive);
EXPECT_EQ(progressive_clip.getFieldOrder(), kOfxImageFieldNone);
olive::VideoParams lower =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
lower.set_interlacing(olive::VideoParams::k_interlaced_bottom_first);
olive::plugin::OliveClipInstance lower_clip(nullptr, desc, lower);
EXPECT_EQ(lower_clip.getFieldOrder(), kOfxImageFieldLower);
}
TEST(PluginClipInstance, RegionOfDefinitionDefaultsToScaledFrame)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(100, 80, olive::core::PixelFormat::u8, 4, false);
params.set_pixel_aspect_ratio(olive::core::Rational(2, 1));
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
OfxRectD rod = clip.getRegionOfDefinition(0.0);
EXPECT_DOUBLE_EQ(rod.x1, 0.0);
EXPECT_DOUBLE_EQ(rod.y1, 0.0);
EXPECT_DOUBLE_EQ(rod.x2, 200.0);
EXPECT_DOUBLE_EQ(rod.y2, 80.0);
}
TEST(PluginClipInstance, RegionOfDefinitionPerTimeOverride)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(100, 80, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
OfxRectD custom = { 1.5, 2.5, 51.5, 21.0 };
clip.setRegionOfDefinition(custom, 3.0);
OfxRectD at_three = clip.getRegionOfDefinition(3.0);
EXPECT_DOUBLE_EQ(at_three.x1, 1.5);
EXPECT_DOUBLE_EQ(at_three.y1, 2.5);
EXPECT_DOUBLE_EQ(at_three.x2, 51.5);
EXPECT_DOUBLE_EQ(at_three.y2, 21.0);
// Other times still fall back to the params-derived region.
OfxRectD at_four = clip.getRegionOfDefinition(4.0);
EXPECT_DOUBLE_EQ(at_four.x1, 0.0);
EXPECT_DOUBLE_EQ(at_four.y1, 0.0);
EXPECT_DOUBLE_EQ(at_four.x2, 100.0);
EXPECT_DOUBLE_EQ(at_four.y2, 80.0);
}
TEST(PluginClipInstance, RegionOfDefinitionFallsBackToStoredDefault)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
// Zero-sized params yield no usable params-derived region, so the stored
// default is used.
olive::VideoParams empty_params =
make_params(0, 0, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance empty_clip(nullptr, desc, empty_params);
OfxRectD stored = { 2.0, 4.0, 202.0, 104.0 };
empty_clip.setDefaultRegionOfDefinition(stored);
OfxRectD fallback = empty_clip.getRegionOfDefinition(0.0);
EXPECT_DOUBLE_EQ(fallback.x1, 2.0);
EXPECT_DOUBLE_EQ(fallback.y1, 4.0);
EXPECT_DOUBLE_EQ(fallback.x2, 202.0);
EXPECT_DOUBLE_EQ(fallback.y2, 104.0);
// A params-derived region still takes precedence over the stored default.
olive::VideoParams params =
make_params(100, 80, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
clip.setDefaultRegionOfDefinition(stored);
OfxRectD derived = clip.getRegionOfDefinition(0.0);
EXPECT_DOUBLE_EQ(derived.x1, 0.0);
EXPECT_DOUBLE_EQ(derived.y1, 0.0);
EXPECT_DOUBLE_EQ(derived.x2, 100.0);
EXPECT_DOUBLE_EQ(derived.y2, 80.0);
}
TEST(PluginClipInstance, OutputImageBoundsFollowRegionOfDefinition)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(100, 80, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
OfxRectD custom = { 1.5, 2.5, 51.5, 21.0 };
clip.setRegionOfDefinition(custom, 3.0);
auto *image =
static_cast<olive::plugin::Image *>(clip.getImage(3.0, nullptr));
ASSERT_NE(image, nullptr);
// Integer bounds are floor(min) to ceil(max).
EXPECT_EQ(image->width(), 51);
EXPECT_EQ(image->height(), 19);
}
TEST(PluginClipInstance, OutputImageCacheIsPerTime)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
OFX::Host::ImageEffect::Image *first = clip.getImage(1.0, nullptr);
OFX::Host::ImageEffect::Image *second = clip.getImage(2.0, nullptr);
ASSERT_NE(first, nullptr);
ASSERT_NE(second, nullptr);
EXPECT_NE(first, second);
}
TEST(PluginClipInstance, GetOutputImageUsesCache)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
OFX::Host::ImageEffect::Image *created = clip.getOutputImage(1.0);
ASSERT_NE(created, nullptr);
EXPECT_EQ(clip.getOutputImage(1.0), created);
OFX::Host::ImageEffect::Image *fetched = clip.getImage(2.0, nullptr);
ASSERT_NE(fetched, nullptr);
EXPECT_EQ(clip.getOutputImage(2.0), fetched);
}
TEST(PluginClipInstance, InputImageNullForInvalidParams)
{
struct Case {
int width;
int height;
olive::core::PixelFormat format;
int channels;
};
const Case cases[] = {
{ 0, 10, olive::core::PixelFormat::u8, 4 },
{ 10, 0, olive::core::PixelFormat::u8, 4 },
{ 10, 10, olive::core::PixelFormat::invalid, 4 },
{ 10, 10, olive::core::PixelFormat::u8, 0 },
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
SCOPED_TRACE(i);
OFX::Host::ImageEffect::ClipDescriptor desc("Source");
olive::VideoParams params = make_params(cases[i].width, cases[i].height,
cases[i].format,
cases[i].channels, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
EXPECT_EQ(clip.getImage(0.0, nullptr), nullptr);
}
}
TEST(PluginClipInstance, ConnectedAfterImageBecomesAvailable)
{
OFX::Host::ImageEffect::ClipDescriptor out_desc(
kOfxImageEffectOutputClipName);
olive::VideoParams out_params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance out_clip(nullptr, out_desc, out_params);
EXPECT_FALSE(out_clip.getConnected());
ASSERT_NE(out_clip.getImage(0.0, nullptr), nullptr);
EXPECT_TRUE(out_clip.getConnected());
OFX::Host::ImageEffect::ClipDescriptor src_desc("Source");
olive::VideoParams src_params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance src_clip(nullptr, src_desc, src_params);
EXPECT_FALSE(src_clip.getConnected());
auto texture = std::make_shared<olive::Texture>(src_params);
src_clip.setInputTexture(texture, 1.0, true);
EXPECT_TRUE(src_clip.getConnected());
}
TEST(PluginClipInstance, SetInputTextureCopiesPixels)
{
OFX::Host::ImageEffect::ClipDescriptor desc("Source");
olive::VideoParams params =
make_params(4, 2, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
olive::AVFramePtr frame = create_frame(params);
ASSERT_NE(frame, nullptr);
ASSERT_NE(frame->data(0), nullptr);
const int values_per_row = params.width() * params.channel_count();
for (int y = 0; y < params.height(); ++y) {
uint8_t *row = frame->data(0) + y * frame->linesize(0);
for (int x = 0; x < values_per_row; ++x) {
row[x] = static_cast<uint8_t>(y * values_per_row + x + 1);
}
}
auto texture = std::make_shared<olive::Texture>(params);
texture->handle_frame(frame);
clip.setInputTexture(texture, 1.0, true);
auto *image =
static_cast<olive::plugin::Image *>(clip.getImage(1.0, nullptr));
ASSERT_NE(image, nullptr);
ASSERT_NE(image->data(), nullptr);
ASSERT_EQ(image->width(), params.width());
ASSERT_EQ(image->height(), params.height());
for (int y = 0; y < params.height(); ++y) {
for (int x = 0; x < values_per_row; ++x) {
EXPECT_EQ(image->data()[y * image->row_bytes() + x],
static_cast<uint8_t>(y * values_per_row + x + 1));
}
}
image->releaseReference();
}
TEST(PluginClipInstance, SetInputTextureCopiesFloatPixels)
{
OFX::Host::ImageEffect::ClipDescriptor desc("Source");
olive::VideoParams params =
make_params(2, 1, olive::core::PixelFormat::f32, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
olive::AVFramePtr frame = create_frame(params);
ASSERT_NE(frame, nullptr);
ASSERT_NE(frame->data(0), nullptr);
const float expected[] = { 0.125f, 0.25f, 0.375f, 0.5f,
0.625f, 0.75f, 0.875f, 1.0f };
auto *dst = reinterpret_cast<float *>(frame->data(0));
for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
dst[i] = expected[i];
}
auto texture = std::make_shared<olive::Texture>(params);
texture->handle_frame(frame);
clip.setInputTexture(texture, 1.0, true);
auto *image =
static_cast<olive::plugin::Image *>(clip.getImage(1.0, nullptr));
ASSERT_NE(image, nullptr);
ASSERT_NE(image->data(), nullptr);
const auto *pixels = reinterpret_cast<const float *>(image->data());
for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
EXPECT_FLOAT_EQ(pixels[i], expected[i]);
}
image->releaseReference();
}
TEST(PluginClipInstance, SetInputTextureScrubsNaNToBlack)
{
OFX::Host::ImageEffect::ClipDescriptor desc("Source");
olive::VideoParams params =
make_params(2, 2, olive::core::PixelFormat::f32, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
olive::AVFramePtr frame = create_frame(params);
ASSERT_NE(frame, nullptr);
ASSERT_NE(frame->data(0), nullptr);
const int stride =
frame->linesize(0) / static_cast<int>(sizeof(float));
for (int y = 0; y < params.height(); ++y) {
float *row = reinterpret_cast<float *>(frame->data(0)) + y * stride;
for (int x = 0; x < params.width() * params.channel_count(); ++x) {
row[x] = 0.5f;
}
}
reinterpret_cast<float *>(frame->data(0))[3] =
std::numeric_limits<float>::quiet_NaN();
auto texture = std::make_shared<olive::Texture>(params);
texture->handle_frame(frame);
clip.setInputTexture(texture, 1.0, true);
ASSERT_TRUE(clip.getConnected());
// A frame containing NaN/Inf is replaced with black rather than being
// passed to the plugin.
auto *image =
static_cast<olive::plugin::Image *>(clip.getImage(1.0, nullptr));
ASSERT_NE(image, nullptr);
ASSERT_NE(image->data(), nullptr);
const auto *pixels = reinterpret_cast<const float *>(image->data());
const int float_count =
params.width() * params.height() * params.channel_count();
for (int i = 0; i < float_count; ++i) {
EXPECT_FLOAT_EQ(pixels[i], 0.0f);
}
image->releaseReference();
}
TEST(PluginClipInstance, PruneImagesCacheEvictsOldestInputImages)
{
OFX::Host::ImageEffect::ClipDescriptor desc("Source");
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
for (int t = 1;
t <= olive::plugin::OliveClipInstance::k_max_input_image_cache + 1; ++t) {
auto texture = std::make_shared<olive::Texture>(params);
clip.setInputTexture(texture, static_cast<OfxTime>(t), true);
}
clip.prune_images_cache();
// The oldest entry (time 1) was evicted; its on-demand recreation is
// cached again, while later entries remained cached throughout.
OFX::Host::ImageEffect::Image *evicted_a = clip.getImage(1.0, nullptr);
OFX::Host::ImageEffect::Image *evicted_b = clip.getImage(1.0, nullptr);
EXPECT_EQ(evicted_a, evicted_b);
OFX::Host::ImageEffect::Image *cached_a = clip.getImage(2.0, nullptr);
OFX::Host::ImageEffect::Image *cached_b = clip.getImage(2.0, nullptr);
EXPECT_EQ(cached_a, cached_b);
evicted_a->releaseReference();
evicted_b->releaseReference();
cached_a->releaseReference();
cached_b->releaseReference();
}
TEST(PluginClipInstance, PruneImagesCacheKeepsOutputImages)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
for (int t = 1;
t <= olive::plugin::OliveClipInstance::k_max_input_image_cache + 1; ++t) {
clip.getImage(static_cast<OfxTime>(t), nullptr);
}
OFX::Host::ImageEffect::Image *before = clip.getImage(1.0, nullptr);
ASSERT_NE(before, nullptr);
// Pruning is a no-op for the output clip.
clip.prune_images_cache();
EXPECT_EQ(clip.getImage(1.0, nullptr), before);
}
#ifdef OFX_SUPPORTS_OPENGLRENDER
TEST(PluginClipInstance, LoadTextureReturnsNullWithoutGpuTexture)
{
OFX::Host::ImageEffect::ClipDescriptor src_desc("Source");
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance src_clip(nullptr, src_desc, params);
// No texture was supplied at all.
EXPECT_EQ(src_clip.loadTexture(1.0, nullptr, nullptr), nullptr);
// A dummy (CPU-only) texture has no GL id, so no OFX texture is made.
auto texture = std::make_shared<olive::Texture>(params);
src_clip.setInputTexture(texture, 1.0, false);
EXPECT_EQ(src_clip.loadTexture(1.0, nullptr, nullptr), nullptr);
OFX::Host::ImageEffect::ClipDescriptor out_desc(
kOfxImageEffectOutputClipName);
olive::plugin::OliveClipInstance out_clip(nullptr, out_desc, params);
out_clip.setOutputTexture(texture, 2.0);
EXPECT_TRUE(out_clip.getConnected());
EXPECT_EQ(out_clip.loadTexture(2.0, nullptr, nullptr), nullptr);
}
#endif
TEST(PluginClipInstance, SetParamsUpdatesClipAndPluginPreferences)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(16, 16, olive::core::PixelFormat::u8, 4, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
olive::VideoParams updated =
make_params(32, 24, olive::core::PixelFormat::f32, 4, false);
updated.set_frame_rate(olive::core::Rational(60, 1));
clip.setParams(updated);
EXPECT_DOUBLE_EQ(clip.getFrameRate(), 60.0);
EXPECT_EQ(clip.getUnmappedBitDepth(), kOfxBitDepthFloat);
olive::VideoParams preferred = clip.getPluginPreferredParams();
EXPECT_EQ(preferred.format(), olive::core::PixelFormat::f32);
EXPECT_EQ(preferred.channel_count(), 4);
}
TEST(PluginClipInstance, PluginPreferredParamsDefaultsToClipParams)
{
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
olive::VideoParams params =
make_params(64, 32, olive::core::PixelFormat::u16, 3, false);
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
olive::VideoParams preferred = clip.getPluginPreferredParams();
EXPECT_EQ(preferred.format(), olive::core::PixelFormat::u16);
EXPECT_EQ(preferred.channel_count(), 3);
EXPECT_EQ(preferred.width(), 64);
EXPECT_EQ(preferred.height(), 32);
clip.setPixelDepth(kOfxBitDepthByte);
clip.setComponents(kOfxImageComponentRGBA);
preferred = clip.getPluginPreferredParams();
EXPECT_EQ(preferred.format(), olive::core::PixelFormat::u8);
EXPECT_EQ(preferred.channel_count(), 4);
EXPECT_EQ(preferred.width(), 64);
}