Add 12 gtest files (~400 tests) covering previously untested or under-tested areas: - node_math_test: MathNode operations across number/rational/vector/ matrix/color/sample pairings, shader code generation - node_undo_test: all nodeundo command classes redo/undo - track_test: Track block management, lookup, references, Value() - render_diskcache_test: FrameHashCache EXR/JPEG round trips, DiskManager LRU eviction, state persistence - node_group_test: NodeGroup passthrough registration and serialization - plugin_paraminstance_test: OFX param instances and clip image logic - audio_waveform_test: AudioVisualWaveform + AudioProcessor - node_value_extended_test: NodeValue conversions, NodeValueTable ops, NodeKeyframe/bezier behavior - render_projectcopier_test: ProjectCopier sync, PlaybackCache, AudioPlaybackCache PCM segments - node_core_test: Node input arrays, flags, contexts, links, keyframe events, CopyInputs - clip_traverser_test: ClipBlock speed/reverse/loop time mapping, traverser time propagation - audio_manager_viewer_test: AudioManager device API, ViewerOutput params/streams/signals Also fixes two real bugs found by the new tests: - MathNode vec-vec divide crashed (debug) or produced NaN (release) on the zero padding components of vec2/vec3 operands - NodeKeyframe's default constructor left previous_/next_ and the bezier handles uninitialized
890 lines
30 KiB
C++
890 lines
30 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 MakeParams(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 CreateFrame(const olive::VideoParams ¶ms)
|
|
{
|
|
olive::AVFramePtr frame = olive::CreateAVFramePtr();
|
|
frame->set_format(olive::FFmpegUtils::GetFFmpegPixelFormat(
|
|
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::IsNormalisedCoordinateSystem(descriptor));
|
|
|
|
descriptor.addStandardParamProps(kOfxParamTypeDouble);
|
|
EXPECT_FALSE(olive::plugin::IsNormalisedCoordinateSystem(descriptor));
|
|
|
|
descriptor.getProperties().setStringProperty(
|
|
kOfxParamPropDefaultCoordinateSystem, kOfxParamCoordinatesNormalised);
|
|
EXPECT_TRUE(olive::plugin::IsNormalisedCoordinateSystem(descriptor));
|
|
|
|
EXPECT_DOUBLE_EQ(olive::plugin::ToNormalised(960.0, 1920.0), 0.5);
|
|
EXPECT_DOUBLE_EQ(olive::plugin::ToCanonical(0.5, 1920.0), 960.0);
|
|
// A non-positive extent passes the value through unchanged.
|
|
EXPECT_DOUBLE_EQ(olive::plugin::ToNormalised(7.5, 0.0), 7.5);
|
|
EXPECT_DOUBLE_EQ(olive::plugin::ToCanonical(7.5, 0.0), 7.5);
|
|
}
|
|
|
|
TEST(PluginParamInstance, ParamChangeLabelContainsParamName)
|
|
{
|
|
OFX::Host::Param::Descriptor descriptor(kOfxParamTypeDouble, "Gain");
|
|
EXPECT_EQ(olive::plugin::ParamChangeLabel(descriptor),
|
|
QStringLiteral("Change Gain"));
|
|
}
|
|
|
|
TEST(PluginParamInstance, SubmitUndoCommandIgnoresNullCommand)
|
|
{
|
|
EXPECT_NO_THROW(olive::plugin::SubmitUndoCommand(
|
|
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.SetNode(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.SetNode(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 =
|
|
MakeParams(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 =
|
|
MakeParams(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 = MakeParams(
|
|
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 =
|
|
MakeParams(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 =
|
|
MakeParams(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 =
|
|
MakeParams(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 =
|
|
MakeParams(16, 16, olive::core::PixelFormat::U8, 4, false);
|
|
progressive.set_interlacing(olive::VideoParams::kInterlaceNone);
|
|
olive::plugin::OliveClipInstance progressive_clip(nullptr, desc,
|
|
progressive);
|
|
EXPECT_EQ(progressive_clip.getFieldOrder(), kOfxImageFieldNone);
|
|
|
|
olive::VideoParams lower =
|
|
MakeParams(16, 16, olive::core::PixelFormat::U8, 4, false);
|
|
lower.set_interlacing(olive::VideoParams::kInterlacedBottomFirst);
|
|
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 =
|
|
MakeParams(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 =
|
|
MakeParams(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, OutputImageBoundsFollowRegionOfDefinition)
|
|
{
|
|
OFX::Host::ImageEffect::ClipDescriptor desc(kOfxImageEffectOutputClipName);
|
|
olive::VideoParams params =
|
|
MakeParams(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 =
|
|
MakeParams(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 =
|
|
MakeParams(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 = MakeParams(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 =
|
|
MakeParams(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 =
|
|
MakeParams(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 =
|
|
MakeParams(4, 2, olive::core::PixelFormat::U8, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
olive::AVFramePtr frame = CreateFrame(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->handleFrame(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 =
|
|
MakeParams(2, 1, olive::core::PixelFormat::F32, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
olive::AVFramePtr frame = CreateFrame(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->handleFrame(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 =
|
|
MakeParams(2, 2, olive::core::PixelFormat::F32, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
olive::AVFramePtr frame = CreateFrame(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->handleFrame(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 =
|
|
MakeParams(16, 16, olive::core::PixelFormat::U8, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
for (int t = 1;
|
|
t <= olive::plugin::OliveClipInstance::kMaxInputImageCache + 1; ++t) {
|
|
auto texture = std::make_shared<olive::Texture>(params);
|
|
clip.setInputTexture(texture, static_cast<OfxTime>(t), true);
|
|
}
|
|
clip.pruneImagesCache();
|
|
|
|
// The oldest entry (time 1) was evicted and is recreated on demand,
|
|
// while later entries remain cached.
|
|
OFX::Host::ImageEffect::Image *evicted_a = clip.getImage(1.0, nullptr);
|
|
OFX::Host::ImageEffect::Image *evicted_b = clip.getImage(1.0, nullptr);
|
|
EXPECT_NE(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 =
|
|
MakeParams(16, 16, olive::core::PixelFormat::U8, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
for (int t = 1;
|
|
t <= olive::plugin::OliveClipInstance::kMaxInputImageCache + 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.pruneImagesCache();
|
|
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 =
|
|
MakeParams(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 =
|
|
MakeParams(16, 16, olive::core::PixelFormat::U8, 4, false);
|
|
olive::plugin::OliveClipInstance clip(nullptr, desc, params);
|
|
|
|
olive::VideoParams updated =
|
|
MakeParams(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 =
|
|
MakeParams(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);
|
|
}
|