/* * Oak Video Editor - Non-Linear Video Editor * Copyright (C) 2025 Olive CE Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // // Created by mikesolar on 25-10-19. // #include "ofxCore.h" #include "ofxhPropertySuite.h" #include "olive/core/render/pixelformat.h" #include "render/texture.h" #include "render/opengl/openglrenderer.h" #include "node/value.h" #include "render/videoparams.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pluginrenderer.h" #include "core.h" #include "undo/undostack.h" #include "pluginSupport/OliveClip.h" #include "pluginSupport/OlivePluginInstance.h" #include "common/ffmpegutils.h" #include "ofxhParam.h" #include "ofxImageEffect.h" #include "ofxhUtilities.h" #include "ofxGPURender.h" #include "olive/core/util/color.h" extern "C"{ #include #include #include } // 作用:从 OFX Image 属性推导 FFmpeg 像素格式,并返回每像素字节数。 // Purpose: Infer FFmpeg pixel format from OFX image properties and return bytes-per-pixel. static AVPixelFormat GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image, int *bytes_per_pixel) { const std::string &depth = image.getStringProperty(kOfxImageEffectPropPixelDepth); const std::string &components = image.getStringProperty(kOfxImageEffectPropComponents); olive::core::PixelFormat pixel_format = olive::core::PixelFormat::INVALID; if (depth == kOfxBitDepthByte) { pixel_format = olive::core::PixelFormat::U8; } else if (depth == kOfxBitDepthShort) { pixel_format = olive::core::PixelFormat::U16; } else if (depth == kOfxBitDepthHalf) { pixel_format = olive::core::PixelFormat::F16; } else if (depth == kOfxBitDepthFloat) { pixel_format = olive::core::PixelFormat::F32; } int channel_count = 0; if (components == kOfxImageComponentRGBA) { channel_count = 4; } else if (components == kOfxImageComponentRGB) { channel_count = 3; } else if (components == kOfxImageComponentAlpha) { channel_count = 1; } AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(pixel_format, channel_count); if (pix_fmt == AV_PIX_FMT_NONE && channel_count == 1) { if (pixel_format == olive::core::PixelFormat::U8) { pix_fmt = AV_PIX_FMT_GRAY8; } else if (pixel_format == olive::core::PixelFormat::U16) { pix_fmt = AV_PIX_FMT_GRAY16LE; } else if (pixel_format == olive::core::PixelFormat::F16) { pix_fmt = AV_PIX_FMT_GRAYF16; } else if (pixel_format == olive::core::PixelFormat::F32) { pix_fmt = AV_PIX_FMT_GRAYF32; } } if (pix_fmt == AV_PIX_FMT_NONE) { return AV_PIX_FMT_NONE; } const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); if (!desc) { return AV_PIX_FMT_NONE; } int bits_per_pixel = av_get_bits_per_pixel(desc); if (bits_per_pixel <= 0 || bits_per_pixel % 8 != 0) { return AV_PIX_FMT_NONE; } *bytes_per_pixel = bits_per_pixel / 8; return pix_fmt; } // 作用:为插件实例注入当前帧的参数值,避免依赖节点实时回读。 static void ApplyParamOverrides(OFX::Host::ImageEffect::Instance &instance, const olive::NodeValueRow &values, OfxTime time) { const auto ¶ms = instance.getParams(); for (const auto &entry : params) { if (!entry.second) { continue; } const QString key = QString::fromStdString(entry.first); if (!values.contains(key)) { continue; } const olive::NodeValue &value = values.value(key); if (value.type() == olive::NodeValue::kNone || value.type() == olive::NodeValue::kTexture || value.type() == olive::NodeValue::kSamples) { continue; } const std::string &type = entry.second->getType(); if (type == kOfxParamTypeInteger) { if (auto *param = dynamic_cast( entry.second)) { param->set(time, value.data().toInt()); } continue; } if (type == kOfxParamTypeDouble) { if (auto *param = dynamic_cast( entry.second)) { double v = value.data().toDouble(); if (std::isnan(v) || std::isinf(v)) { qWarning() << "[PLUGIN] NaN/Inf in double param" << key << "replacing with default"; auto *default_prop = entry.second->getProperties().fetchDoubleProperty( kOfxParamPropDefault); v = default_prop ? default_prop->getValue() : 0.0; } auto *min_prop = entry.second->getProperties().fetchDoubleProperty( kOfxParamPropMin); if (min_prop && v < min_prop->getValue()) { v = min_prop->getValue(); } auto *max_prop = entry.second->getProperties().fetchDoubleProperty( kOfxParamPropMax); if (max_prop && v > max_prop->getValue()) { v = max_prop->getValue(); } param->set(time, v); } continue; } if (type == kOfxParamTypeBoolean) { if (auto *param = dynamic_cast( entry.second)) { param->set(time, value.data().toBool()); } continue; } if (type == kOfxParamTypeChoice) { if (auto *param = dynamic_cast( entry.second)) { param->set(time, value.data().toInt()); } continue; } if (type == kOfxParamTypeString || type == kOfxParamTypeCustom || type == kOfxParamTypeBytes || type == kOfxParamTypeStrChoice) { if (auto *param = dynamic_cast( entry.second)) { const QByteArray utf8 = value.data().toString().toUtf8(); param->set(time, utf8.constData()); } continue; } if (type == kOfxParamTypeRGBA) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const auto c = value.data().value(); param->set(time, c.red(), c.green(), c.blue(), c.alpha()); } else if (value.data().canConvert()) { const QVector4D v = value.data().value(); param->set(time, v.x(), v.y(), v.z(), v.w()); } else if (value.data().canConvert()) { const QVector3D v = value.data().value(); param->set(time, v.x(), v.y(), v.z(), 1.0); } } continue; } if (type == kOfxParamTypeRGB) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const auto c = value.data().value(); param->set(time, c.red(), c.green(), c.blue()); } else if (value.data().canConvert()) { const QVector4D v = value.data().value(); param->set(time, v.x(), v.y(), v.z()); } else if (value.data().canConvert()) { const QVector3D v = value.data().value(); param->set(time, v.x(), v.y(), v.z()); } } continue; } if (type == kOfxParamTypeDouble2D) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const QVector2D v = value.data().value(); param->set(time, v.x(), v.y()); } } continue; } if (type == kOfxParamTypeInteger2D) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const QVector2D v = value.data().value(); param->set(time, static_cast(v.x()), static_cast(v.y())); } } continue; } if (type == kOfxParamTypeDouble3D) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const QVector3D v = value.data().value(); param->set(time, v.x(), v.y(), v.z()); } } continue; } if (type == kOfxParamTypeInteger3D) { if (auto *param = dynamic_cast( entry.second)) { if (value.data().canConvert()) { const QVector3D v = value.data().value(); param->set(time, static_cast(v.x()), static_cast(v.y()), static_cast(v.z())); } } continue; } } } static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶ms); // 作用:读取 clip 偏好(像素深度与分量)并更新 VideoParams。 // Purpose: Apply clip preferences (depth/components) into VideoParams. static bool ApplyClipPreferencesToParams( const OFX::Host::ImageEffect::ClipInstance &clip, olive::VideoParams *params) { if (!params) { return false; } olive::core::PixelFormat format = olive::core::PixelFormat::INVALID; const std::string &depth = clip.getPixelDepth(); if (depth == kOfxBitDepthByte) { format = olive::core::PixelFormat::U8; } else if (depth == kOfxBitDepthShort) { format = olive::core::PixelFormat::U16; } else if (depth == kOfxBitDepthHalf) { format = olive::core::PixelFormat::F16; } else if (depth == kOfxBitDepthFloat) { format = olive::core::PixelFormat::F32; } int channels = 0; const std::string &components = clip.getComponents(); if (components == kOfxImageComponentRGBA) { channels = 4; } else if (components == kOfxImageComponentRGB) { channels = 3; } else if (components == kOfxImageComponentAlpha) { channels = 1; } if (format == olive::core::PixelFormat::INVALID || channels == 0) { return false; } params->set_format(format); params->set_channel_count(channels); return true; } // 作用:将 OFX bit depth 字符串映射为内部 PixelFormat。 // Purpose: Map OFX bit depth string to internal PixelFormat. static olive::core::PixelFormat PixelFormatFromOfxDepth( const std::string &depth) { if (depth == kOfxBitDepthByte) { return olive::core::PixelFormat::U8; } if (depth == kOfxBitDepthShort) { return olive::core::PixelFormat::U16; } if (depth == kOfxBitDepthHalf) { return olive::core::PixelFormat::F16; } if (depth == kOfxBitDepthFloat) { return olive::core::PixelFormat::F32; } return olive::core::PixelFormat::INVALID; } // 作用:将内部 PixelFormat 转为 OFX bit depth 字符串。 // Purpose: Map internal PixelFormat to OFX bit depth string. static const char *OfxDepthFromPixelFormat(olive::core::PixelFormat format) { switch (format) { case olive::core::PixelFormat::U8: return kOfxBitDepthByte; case olive::core::PixelFormat::U16: return kOfxBitDepthShort; case olive::core::PixelFormat::F16: return kOfxBitDepthHalf; case olive::core::PixelFormat::F32: return kOfxBitDepthFloat; case olive::core::PixelFormat::INVALID: case olive::core::PixelFormat::COUNT: break; } return kOfxBitDepthNone; } // 作用:将 OFX components 字符串映射为通道数。 // Purpose: Map OFX components string to channel count. static int ChannelCountFromOfxComponent(const std::string &components) { if (components == kOfxImageComponentRGBA) { return 4; } if (components == kOfxImageComponentRGB) { return 3; } if (components == kOfxImageComponentAlpha) { return 1; } return 0; } // 作用:将通道数映射为 OFX components 字符串。 // Purpose: Map channel count to OFX components string. static const char *OfxComponentsFromChannels(int channel_count) { switch (channel_count) { case 1: return kOfxImageComponentAlpha; case 3: return kOfxImageComponentRGB; case 4: return kOfxImageComponentRGBA; default: break; } return kOfxImageComponentNone; } // 作用:判断插件是否支持指定像素深度。 // Purpose: Check whether effect supports a given pixel depth. static bool EffectSupportsPixelDepth( const OFX::Host::ImageEffect::Instance &instance, const std::string &depth) { const auto &effect_props = instance.getDescriptor().getProps(); const int depth_count = effect_props.getDimension(kOfxImageEffectPropSupportedPixelDepths); for (int i = 0; i < depth_count; ++i) { if (effect_props.getStringProperty( kOfxImageEffectPropSupportedPixelDepths, i) == depth) { return true; } } return false; } // 作用:判断 clip 是否支持指定组件格式。 // Purpose: Check whether clip supports a given components string. static bool ClipSupportsComponents( const OFX::Host::ImageEffect::ClipInstance &clip, const std::string &components) { const auto &supported_components = clip.getSupportedComponents(); for (const auto &comp : supported_components) { if (comp == components) { return true; } } return false; } // 作用:估算从源参数到目标参数的转换代价,用于排序选择。 // Purpose: Estimate conversion cost from source to target params for ranking. static int ConversionCost(const olive::VideoParams &src, const olive::VideoParams &dst) { const int src_bpp = src.channel_count() * src.format().byte_count(); const int dst_bpp = dst.channel_count() * dst.format().byte_count(); int cost = std::abs(dst_bpp - src_bpp); if (src.format() != dst.format()) { cost += 4; } if (src.channel_count() != dst.channel_count()) { cost += 2; } return cost; } // 作用:判断目标参数能否转换为可用的 AVPixelFormat。 // Purpose: Check if params map to a valid AVPixelFormat. static bool ParamsConvertible(const olive::VideoParams ¶ms) { return GetDestinationAVPixelFormat(params) != AV_PIX_FMT_NONE; } // 作用:在 clip 偏好无效时,选择一个插件支持的输出格式。 // Purpose: Pick a supported output format when clip preferences are invalid. static void ChooseSupportedOutputParams( const OFX::Host::ImageEffect::Instance &instance, const OFX::Host::ImageEffect::ClipInstance &clip, const olive::VideoParams &preferred, olive::VideoParams *out) { if (!out) { return; } *out = preferred; const char *preferred_components = OfxComponentsFromChannels(preferred.channel_count()); if (std::strcmp(preferred_components, kOfxImageComponentNone) != 0 && ClipSupportsComponents(clip, preferred_components)) { out->set_channel_count(preferred.channel_count()); } else if (ClipSupportsComponents(clip, kOfxImageComponentRGBA)) { out->set_channel_count(4); } else if (ClipSupportsComponents(clip, kOfxImageComponentRGB)) { out->set_channel_count(3); } else if (ClipSupportsComponents(clip, kOfxImageComponentAlpha)) { out->set_channel_count(1); } const olive::core::PixelFormat preferred_format = preferred.format(); const std::array candidates = { preferred_format, olive::core::PixelFormat::F16, olive::core::PixelFormat::F32, olive::core::PixelFormat::U16, olive::core::PixelFormat::U8, }; for (const auto &candidate : candidates) { if (candidate == olive::core::PixelFormat::INVALID) { continue; } if (!EffectSupportsPixelDepth( instance, OfxDepthFromPixelFormat(candidate))) { continue; } olive::VideoParams test_params = *out; test_params.set_format(candidate); if (!ParamsConvertible(test_params)) { continue; } out->set_format(candidate); return; } } // Forward declarations for functions defined later in this file. static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, const olive::VideoParams &dst_params, olive::Renderer *renderer); static olive::TexturePtr ConvertTextureForParams(olive::TexturePtr src, const olive::VideoParams &dst_params); // 作用:根据插件能力与偏好选择输入格式并执行转换。 // Purpose: Select a supported input format and convert texture for the clip. static olive::TexturePtr ConvertTextureForClip( const OFX::Host::ImageEffect::Instance &instance, const OFX::Host::ImageEffect::ClipInstance &clip, olive::TexturePtr src, const olive::VideoParams &preferred_params, bool force_preferred, olive::VideoParams *out_params) { if (!src || !out_params) { return nullptr; } const olive::VideoParams &src_params = src->params(); auto add_candidate = [](std::vector &list, const olive::VideoParams ¶ms) { for (const auto &existing : list) { if (existing.format() == params.format() && existing.channel_count() == params.channel_count()) { return; } } list.push_back(params); }; std::vector channel_candidates; const auto &supported_components = clip.getSupportedComponents(); for (const auto &comp : supported_components) { int channels = ChannelCountFromOfxComponent(comp); if (channels > 0 && std::find(channel_candidates.begin(), channel_candidates.end(), channels) == channel_candidates.end()) { channel_candidates.push_back(channels); } } if (channel_candidates.empty() && preferred_params.channel_count() > 0) { channel_candidates.push_back(preferred_params.channel_count()); } std::vector format_candidates; const auto &effect_props = instance.getDescriptor().getProps(); const int depth_count = effect_props.getDimension(kOfxImageEffectPropSupportedPixelDepths); for (int i = 0; i < depth_count; ++i) { olive::core::PixelFormat fmt = PixelFormatFromOfxDepth(effect_props.getStringProperty( kOfxImageEffectPropSupportedPixelDepths, i)); if (fmt != olive::core::PixelFormat::INVALID && std::find(format_candidates.begin(), format_candidates.end(), fmt) == format_candidates.end()) { format_candidates.push_back(fmt); } } if (format_candidates.empty() && preferred_params.format() != olive::core::PixelFormat::INVALID) { format_candidates.push_back(preferred_params.format()); } std::vector candidates; add_candidate(candidates, preferred_params); const bool prefer_rgba8 = (preferred_params.format() == olive::core::PixelFormat::U8 || preferred_params.format() == olive::core::PixelFormat::INVALID) && ClipSupportsComponents(clip, kOfxImageComponentRGBA) && EffectSupportsPixelDepth(instance, kOfxBitDepthByte); if (prefer_rgba8) { olive::VideoParams rgba_candidate = src_params; rgba_candidate.set_format(olive::core::PixelFormat::U8); rgba_candidate.set_channel_count(4); if (ParamsConvertible(rgba_candidate)) { add_candidate(candidates, rgba_candidate); } } for (olive::core::PixelFormat fmt : format_candidates) { for (int channels : channel_candidates) { if (fmt == olive::core::PixelFormat::INVALID || channels <= 0) { continue; } olive::VideoParams candidate = src_params; candidate.set_format(fmt); candidate.set_channel_count(channels); if (!ParamsConvertible(candidate)) { continue; } add_candidate(candidates, candidate); } } if (candidates.empty()) { return nullptr; } std::stable_sort(candidates.begin(), candidates.end(), [&src_params, &preferred_params, prefer_rgba8, force_preferred](const auto &a, const auto &b) { if (force_preferred) { const bool a_pref = (a.format() == preferred_params.format() && a.channel_count() == preferred_params.channel_count()); const bool b_pref = (b.format() == preferred_params.format() && b.channel_count() == preferred_params.channel_count()); if (a_pref != b_pref) { return a_pref; } } if (prefer_rgba8) { const bool a_rgba8 = a.format() == olive::core::PixelFormat::U8 && a.channel_count() == 4; const bool b_rgba8 = b.format() == olive::core::PixelFormat::U8 && b.channel_count() == 4; if (a_rgba8 != b_rgba8) { return a_rgba8; } } const int cost_a = ConversionCost(src_params, a); const int cost_b = ConversionCost(src_params, b); if (cost_a != cost_b) { return cost_a < cost_b; } if (a.format() == preferred_params.format() && a.channel_count() == preferred_params.channel_count()) { return true; } return false; }); for (const auto &candidate : candidates) { if (candidate.format() == src_params.format() && candidate.channel_count() == src_params.channel_count()) { *out_params = src_params; return src; } olive::TexturePtr converted = ConvertTextureForParams(src, candidate); if (converted) { *out_params = candidate; return converted; } } return nullptr; } // 作用:从 OFX Image 复制数据到 AVFrame(按图像属性推导格式)。 // Purpose: Copy OFX Image data into an AVFrame with inferred format. static olive::AVFramePtr create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image) { void *data_ptr = image.getPointerProperty(kOfxImagePropData); if (!data_ptr) { qWarning().noquote() << "OFX output image missing data pointer"; return nullptr; } int bounds[4] = {0, 0, 0, 0}; image.getIntPropertyN(kOfxImagePropBounds, bounds, 4); int width = bounds[2] - bounds[0]; int height = bounds[3] - bounds[1]; if (width <= 0 || height <= 0) { qWarning().noquote() << "OFX output image has invalid bounds" << bounds[0] << bounds[1] << bounds[2] << bounds[3]; return nullptr; } int bytes_per_pixel = 0; AVPixelFormat pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel); if (pix_fmt == AV_PIX_FMT_NONE || bytes_per_pixel <= 0) { qWarning().noquote() << "OFX output image has unsupported pixel format depth=" << QString::fromStdString(image.getStringProperty( kOfxImageEffectPropPixelDepth)) << "components=" << QString::fromStdString( image.getStringProperty(kOfxImageEffectPropComponents)); return nullptr; } int row_bytes = image.getIntProperty(kOfxImagePropRowBytes); if (row_bytes <= 0) { row_bytes = width * bytes_per_pixel; } uint8_t *src = static_cast(data_ptr); src += bounds[1] * row_bytes + bounds[0] * bytes_per_pixel; olive::AVFramePtr frame = olive::CreateAVFramePtr(); frame->width = width; frame->height = height; frame->format = pix_fmt; if (av_frame_get_buffer(frame.get(), 0) < 0) { return nullptr; } const int copy_bytes = width * bytes_per_pixel; if (frame->linesize[0] == row_bytes && row_bytes == copy_bytes) { std::memcpy(frame->data[0], src, copy_bytes * height); } else { for (int y = 0; y < height; ++y) { std::memcpy(frame->data[0] + y * frame->linesize[0], src + y * row_bytes, copy_bytes); } } return frame; } // 前置声明:必要时将 AVFrame 转换为目标 VideoParams 对应格式。 // Forward declaration: Convert AVFrame to match destination VideoParams when needed. // 作用:按指定 VideoParams 复制 OFX Image 到 AVFrame,必要时做格式转换。 // Purpose: Copy OFX Image data into an AVFrame using target VideoParams with format conversion. static olive::AVFramePtr create_avframe_from_ofx_image_with_params( OFX::Host::ImageEffect::Image &image, const olive::VideoParams ¶ms, olive::Renderer *renderer = nullptr) { void *data_ptr = image.getPointerProperty(kOfxImagePropData); if (!data_ptr) { return nullptr; } int bounds[4] = {0, 0, 0, 0}; image.getIntPropertyN(kOfxImagePropBounds, bounds, 4); int width = bounds[2] - bounds[0]; int height = bounds[3] - bounds[1]; if (width <= 0 || height <= 0) { return nullptr; } // Get ACTUAL source format from image properties std::string image_depth = image.getStringProperty(kOfxImageEffectPropPixelDepth); std::string image_comp = image.getStringProperty(kOfxImageEffectPropComponents); int src_channel_count = 4; if (image_comp == kOfxImageComponentRGB) src_channel_count = 3; else if (image_comp == kOfxImageComponentAlpha) src_channel_count = 1; // NOTE: FP16 (Half) format handling has been removed. // FP16 data is now treated as U16 (2 bytes per component) and converted via FFmpeg. int src_bytes_per_component = 1; if (image_depth == kOfxBitDepthShort) src_bytes_per_component = 2; else if (image_depth == kOfxBitDepthHalf) src_bytes_per_component = 2; // Treat as U16 else if (image_depth == kOfxBitDepthFloat) src_bytes_per_component = 4; const int src_bytes_per_pixel = src_channel_count * src_bytes_per_component; const int dst_bytes_per_pixel = params.channel_count() * params.format().byte_count(); int row_bytes = image.getIntProperty(kOfxImagePropRowBytes); if (row_bytes <= 0) { row_bytes = width * src_bytes_per_pixel; } uint8_t *src = static_cast(data_ptr); src += bounds[1] * row_bytes + bounds[0] * src_bytes_per_pixel; // Check if format conversion is needed bool needs_conversion = (src_bytes_per_pixel != dst_bytes_per_pixel) || (src_channel_count != params.channel_count()); if (needs_conversion) { // Create source frame with actual format AVPixelFormat src_fmt = AV_PIX_FMT_NONE; if (src_channel_count == 4) { if (src_bytes_per_component == 1) src_fmt = AV_PIX_FMT_RGBA; else if (src_bytes_per_component == 2) src_fmt = AV_PIX_FMT_RGBA64LE; else if (src_bytes_per_component == 4) src_fmt = AV_PIX_FMT_RGBAF32LE; } else if (src_channel_count == 3) { if (src_bytes_per_component == 1) src_fmt = AV_PIX_FMT_RGB24; else if (src_bytes_per_component == 2) src_fmt = AV_PIX_FMT_RGB48LE; else if (src_bytes_per_component == 4) src_fmt = AV_PIX_FMT_RGBF32LE; } else if (src_channel_count == 1) { if (src_bytes_per_component == 1) src_fmt = AV_PIX_FMT_GRAY8; else if (src_bytes_per_component == 2) src_fmt = AV_PIX_FMT_GRAY16LE; else if (src_bytes_per_component == 4) src_fmt = AV_PIX_FMT_GRAYF32LE; } if (src_fmt != AV_PIX_FMT_NONE) { olive::AVFramePtr src_frame = olive::CreateAVFramePtr(); src_frame->width = width; src_frame->height = height; src_frame->format = src_fmt; if (av_frame_get_buffer(src_frame.get(), 0) >= 0) { // Copy source data row by row (or as a single block if strides match) const int copy_bytes = width * src_bytes_per_pixel; if (src_frame->linesize[0] == row_bytes && row_bytes == copy_bytes) { memcpy(src_frame->data[0], src, copy_bytes * height); } else { for (int y = 0; y < height; ++y) { memcpy(src_frame->data[0] + y * src_frame->linesize[0], src + y * row_bytes, copy_bytes); } } // Convert to destination format return ConvertFrameIfNeeded(src_frame, params, renderer); } else { qWarning().noquote() << "[WARN] av_frame_get_buffer failed for src_fmt=" << src_fmt; } } else { qWarning().noquote() << "[WARN] src_fmt is NONE for depth=" << QString::fromStdString(image_depth); } } // Same format - direct copy AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); if (pix_fmt == AV_PIX_FMT_NONE) { return nullptr; } olive::AVFramePtr frame = olive::CreateAVFramePtr(); frame->width = width; frame->height = height; frame->format = pix_fmt; if (av_frame_get_buffer(frame.get(), 0) < 0) { return nullptr; } const int copy_bytes = width * std::min(src_bytes_per_pixel, dst_bytes_per_pixel); for (int y = 0; y < height; ++y) { memcpy(frame->data[0] + y * frame->linesize[0], src + y * row_bytes, copy_bytes); } return frame; } // 作用:将 VideoParams 映射为最终输出的 AVPixelFormat。 // Purpose: Map VideoParams to the final AVPixelFormat. static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶ms) { AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(params.format(), params.channel_count()); if (pix_fmt == AV_PIX_FMT_NONE && params.channel_count() == 1) { if (params.format() == olive::core::PixelFormat::U8) { pix_fmt = AV_PIX_FMT_GRAY8; } else if (params.format() == olive::core::PixelFormat::U16) { pix_fmt = AV_PIX_FMT_GRAY16LE; } else if (params.format() == olive::core::PixelFormat::F16) { pix_fmt = AV_PIX_FMT_GRAYF16; } else if (params.format() == olive::core::PixelFormat::F32) { pix_fmt = AV_PIX_FMT_GRAYF32; } } return pix_fmt; } // 作用:根据交错设置返回 OFX render field 字符串。 // Purpose: Return OFX render field string based on interlacing. static const char *GetRenderFieldForParams(const olive::VideoParams ¶ms) { switch (params.interlacing()) { case olive::VideoParams::kInterlaceNone: return kOfxImageFieldNone; case olive::VideoParams::kInterlacedTopFirst: case olive::VideoParams::kInterlacedBottomFirst: return kOfxImageFieldBoth; } return kOfxImageFieldNone; } // 作用:从 GPU 纹理回读到 AVFrame(必要时做格式转换)。 // Purpose: Read back GPU texture into AVFrame with format conversion if needed. static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture, const olive::VideoParams ¶ms) { if (!texture || texture->IsDummy()) { return nullptr; } AVPixelFormat pix_fmt = GetDestinationAVPixelFormat(params); if (pix_fmt == AV_PIX_FMT_NONE) { return nullptr; } const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); if (!desc) { return nullptr; } if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) { olive::AVFramePtr frame = olive::CreateAVFramePtr(); frame->format = pix_fmt; frame->width = params.width(); frame->height = params.height(); if (av_frame_get_buffer(frame.get(), 0) < 0) { return nullptr; } if (texture->renderer()) { const int linesize_pixels = olive::plugin::detail::BytesToPixels(frame->linesize[0], params); texture->renderer()->DownloadFromTexture( texture->id(), params, frame->data[0], linesize_pixels); } return frame; } // Planar formats: read back as RGBA and convert. olive::VideoParams rgba_params( params.width(), params.height(), olive::core::PixelFormat::U8, 4, params.pixel_aspect_ratio(), params.interlacing(), params.divider()); olive::AVFramePtr rgba_frame = olive::CreateAVFramePtr(); rgba_frame->format = AV_PIX_FMT_RGBA; rgba_frame->width = params.width(); rgba_frame->height = params.height(); if (av_frame_get_buffer(rgba_frame.get(), 0) < 0) { return nullptr; } if (texture->renderer()) { const int linesize_pixels = olive::plugin::detail::BytesToPixels(rgba_frame->linesize[0], rgba_params); texture->renderer()->DownloadFromTexture( texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels); } olive::AVFramePtr dst = olive::CreateAVFramePtr(); dst->format = pix_fmt; dst->width = params.width(); dst->height = params.height(); if (av_frame_get_buffer(dst.get(), 0) < 0) { return rgba_frame; } SwsContext *sws_ctx = sws_getContext( rgba_frame->width, rgba_frame->height, static_cast(rgba_frame->format), dst->width, dst->height, pix_fmt, SWS_POINT, nullptr, nullptr, nullptr); if (!sws_ctx) { return rgba_frame; } sws_scale(sws_ctx, rgba_frame->data, rgba_frame->linesize, 0, rgba_frame->height, dst->data, dst->linesize); sws_freeContext(sws_ctx); return dst; } // 作用:将字节行跨度转换为像素行跨度。 // Purpose: Convert byte stride to pixel stride. int olive::plugin::detail::BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms) { const int bytes_per_pixel = olive::VideoParams::GetBytesPerPixel(params.format(), params.channel_count()); if (byte_linesize <= 0 || bytes_per_pixel <= 0) { return 0; } return byte_linesize / bytes_per_pixel; } // 作用:将 AVPixelFormat 映射为 Olive 的 PixelFormat 和通道数(仅常见 packed 格式)。 static void GetOliveFormatFromAV(AVPixelFormat fmt, olive::core::PixelFormat *out_fmt, int *out_ch) { switch (fmt) { case AV_PIX_FMT_GRAY8: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 1; return; case AV_PIX_FMT_RGB24: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 3; return; case AV_PIX_FMT_RGBA: *out_fmt = olive::core::PixelFormat::U8; *out_ch = 4; return; case AV_PIX_FMT_GRAY16LE: case AV_PIX_FMT_GRAY16BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 1; return; case AV_PIX_FMT_RGB48LE: case AV_PIX_FMT_RGB48BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 3; return; case AV_PIX_FMT_RGBA64LE: case AV_PIX_FMT_RGBA64BE: *out_fmt = olive::core::PixelFormat::U16; *out_ch = 4; return; case AV_PIX_FMT_GRAYF32LE: case AV_PIX_FMT_GRAYF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 1; return; case AV_PIX_FMT_RGBF32LE: case AV_PIX_FMT_RGBF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 3; return; case AV_PIX_FMT_RGBAF32LE: case AV_PIX_FMT_RGBAF32BE: *out_fmt = olive::core::PixelFormat::F32; *out_ch = 4; return; default: *out_fmt = olive::core::PixelFormat::INVALID; *out_ch = 0; return; } } // 作用:必要时将 AVFrame 转换为目标 VideoParams 对应格式。 // 优先使用 FFmpeg sws_scale;若不支持且 renderer 可用,则走 GPU 路径。 // 删除所有手写 CPU 像素循环,避免精度损失与性能瓶颈。 static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, const olive::VideoParams &dst_params, olive::Renderer *renderer = nullptr) { if (!src) { return nullptr; } AVPixelFormat dst_fmt = GetDestinationAVPixelFormat(dst_params); if (dst_fmt == AV_PIX_FMT_NONE) { return src; } // Same format & size, no conversion needed if (src->format == dst_fmt && src->width == dst_params.width() && src->height == dst_params.height()) { return src; } olive::AVFramePtr dst = olive::CreateAVFramePtr(); dst->format = dst_fmt; dst->width = dst_params.width(); dst->height = dst_params.height(); if (av_frame_get_buffer(dst.get(), 0) < 0) { qWarning().noquote() << "[WARN] av_frame_get_buffer failed for dst_fmt=" << dst_fmt; return src; } // Try FFmpeg sws_scale first SwsContext *sws_ctx = sws_getContext( src->width, src->height, static_cast(src->format), dst->width, dst->height, dst_fmt, SWS_POINT, nullptr, nullptr, nullptr); if (sws_ctx) { int ret = sws_scale(sws_ctx, src->data, src->linesize, 0, src->height, dst->data, dst->linesize); sws_freeContext(sws_ctx); if (ret > 0) { return dst; } } // sws_scale failed (e.g. RGBAF32 not supported). Use GPU if renderer available. if (renderer && src->data[0]) { olive::core::PixelFormat src_fmt; int src_ch; GetOliveFormatFromAV(static_cast(src->format), &src_fmt, &src_ch); if (src_fmt != olive::core::PixelFormat::INVALID && src_ch > 0) { // Ensure renderer's OpenGL context is current before GPU operations. // The context may have been switched by upstream DownloadFromTexture calls. auto *gl_renderer = dynamic_cast(renderer); if (gl_renderer) { gl_renderer->EnsureContextCurrent(__FUNCTION__); } olive::VideoParams src_vp(src->width, src->height, src_fmt, src_ch); int src_bpp = olive::VideoParams::GetBytesPerPixel(src_fmt, src_ch); int src_linesize_pixels = (src_bpp > 0) ? src->linesize[0] / src_bpp : src->width; olive::TexturePtr src_tex = renderer->CreateTexture( src_vp, src->data[0], src_linesize_pixels); if (src_tex) { olive::TexturePtr dst_tex = renderer->CreateTexture(dst_params); if (dst_tex) { olive::ShaderJob job; job.Insert(QStringLiteral("ove_maintex"), olive::NodeValue(olive::NodeValue::kTexture, QVariant::fromValue(src_tex))); renderer->BlitToTexture(renderer->GetDefaultShader(), job, dst_tex.get(), false); // Download result back to AVFrame int dst_bpp = dst_params.GetBytesPerPixel(); int dst_linesize_pixels = (dst_bpp > 0) ? dst->linesize[0] / dst_bpp : dst->width; dst_tex->Download(dst->data[0], dst_linesize_pixels); return dst; } } } } qWarning().noquote() << "[WARN] ConvertFrameIfNeeded failed (sws_scale + GPU both unavailable). " << "Returning unconverted source. src_fmt=" << src->format << " dst_fmt=" << dst_fmt; return src; } // 作用:从字节行跨度换算像素行跨度。 // Purpose: Convert byte line size to pixel line size. static int LinesizeToPixels(const olive::VideoParams ¶ms, int linesize_bytes) { const int bytes_per_pixel = params.channel_count() * params.format().byte_count(); if (bytes_per_pixel <= 0) { return 0; } return linesize_bytes / bytes_per_pixel; } // 作用:将纹理转换为指定 VideoParams。优先使用 GPU shader 做格式转换, // 避免 CPU 回读/转换/上传的性能损失和精度损失。 static olive::TexturePtr ConvertTextureForParams(olive::TexturePtr src, const olive::VideoParams &dst_params) { if (!src) { return nullptr; } const olive::VideoParams &src_params = src->params(); if (src_params.format() == dst_params.format() && src_params.channel_count() == dst_params.channel_count() && src_params.width() == dst_params.width() && src_params.height() == dst_params.height()) { return src; } // GPU path: blit directly to destination format using default shader. // OpenGL texture sampling automatically normalizes U8/U16 to float, // and write-out quantizes float back to U8/U16 when needed. if (auto *renderer = src->renderer()) { olive::TexturePtr dst = renderer->CreateTexture(dst_params); if (dst) { olive::ShaderJob job; job.Insert(QStringLiteral("ove_maintex"), olive::NodeValue(olive::NodeValue::kTexture, QVariant::fromValue(src))); renderer->BlitToTexture(renderer->GetDefaultShader(), job, dst.get(), false); return dst; } } // CPU fallback: readback, sws_scale, re-upload olive::AVFramePtr frame = src->frame(); if (!frame || !frame->data[0]) { frame = ReadbackTextureToFrame(src, src_params); } if (!frame || !frame->data[0]) { return nullptr; } olive::AVFramePtr converted = ConvertFrameIfNeeded(frame, dst_params, nullptr); if (!converted || !converted->data[0]) { return nullptr; } if (converted->linesize[0] <= 0) { return nullptr; } olive::TexturePtr dst; if (auto *renderer = src->renderer()) { int linesize_pixels = LinesizeToPixels(dst_params, converted->linesize[0]); if (linesize_pixels <= 0) { linesize_pixels = dst_params.effective_width(); } dst = renderer->CreateTexture(dst_params, converted->data[0], linesize_pixels); } else { dst = std::make_shared(dst_params); int linesize_pixels = LinesizeToPixels(dst_params, converted->linesize[0]); if (linesize_pixels <= 0) { linesize_pixels = dst_params.effective_width(); } dst->Upload(converted->data[0], linesize_pixels); } if (dst) { dst->handleFrame(converted); } return dst; } // 作用:安全获取插件标识符,便于日志输出。 // Purpose: Safely fetch plugin identifier for logging. static QString PluginIdForInstance(const OFX::Host::ImageEffect::Instance *instance) { if (!instance) { return QStringLiteral(""); } auto *plugin = instance->getPlugin(); if (!plugin) { return QStringLiteral(""); } return QString::fromStdString(plugin->getIdentifier()); } // 作用:统一 OFX 调用失败日志输出。 // Purpose: Centralized logging for OFX action failures. static void LogOfxFailure(const char *action, OfxStatus stat, const OFX::Host::ImageEffect::Instance *instance) { if (stat == kOfxStatOK || stat == kOfxStatReplyDefault) { return; } qWarning().noquote() << "OFX action failed:" << action << "plugin=" << PluginIdForInstance(instance) << "status=" << OFX::StatStr(stat) << "(" << stat << ")"; } // 作用:输出 clip 的声明属性与关联 VideoParams,辅助定位格式不一致。 // Purpose: Log clip declared properties and VideoParams for debugging. static void LogClipState(const char *label, const OFX::Host::ImageEffect::ClipInstance *clip, const olive::VideoParams *params) { if (!clip) { qWarning().noquote() << "OFX clip state" << label << ""; return; } qWarning().noquote() << "OFX clip state" << label << "name=" << QString::fromStdString(clip->getName()) << "pixelDepth=" << QString::fromStdString(clip->getPixelDepth()) << "components=" << QString::fromStdString(clip->getComponents()); if (params) { qWarning().noquote() << "OFX clip params" << label << "width=" << params->width() << "height=" << params->height() << "format=" << static_cast(params->format()) << "channels=" << params->channel_count(); } } // 作用:输出 OFX Image 的属性(深度/组件/行跨度/边界)。 // Purpose: Log OFX image properties (depth/components/stride/bounds). static void LogImageProps(const char *label, OFX::Host::ImageEffect::Image *image) { if (!image) { /*qWarning().noquote() << "OFX image props" << label << ""; return;*/ } int bounds[4] = {0, 0, 0, 0}; int rod[4] = {0, 0, 0, 0}; image->getIntPropertyN(kOfxImagePropBounds, bounds, 4); image->getIntPropertyN(kOfxImagePropRegionOfDefinition, rod, 4); const int row_bytes = image->getIntProperty(kOfxImagePropRowBytes); const std::string &depth = image->getStringProperty(kOfxImageEffectPropPixelDepth); const std::string &components = image->getStringProperty(kOfxImageEffectPropComponents); /*qWarning().noquote() << "OFX image props" << label << "pixelDepth=" << QString::fromStdString(depth) << "components=" << QString::fromStdString(components) << "rowBytes=" << row_bytes << "bounds=" << bounds[0] << bounds[1] << bounds[2] << bounds[3] << "rod=" << rod[0] << rod[1] << rod[2] << rod[3];*/ } // 作用:渲染失败时标记目标画面(紫色)提示错误。 // Purpose: Mark render failure on destination (magenta). static void MarkRenderFailure(olive::TexturePtr destination) { if (destination && destination->renderer()) { destination->renderer()->ClearDestination(destination.get(), 1.0, 0.0, 1.0, 1.0); } } /// Show an error dialog and undo the last operation. Must be called from the GUI thread. static void ShowErrorDialogAndUndo(const QString &message) { if (auto *core = olive::Core::instance()) { if (auto *stack = core->undo_stack()) { if (stack->CanUndo()) { stack->undo(); } } } QMessageBox::critical(nullptr, QObject::tr("Plugin Error"), message); } /// Schedule an error dialog + undo on the GUI thread from a render thread. static void ScheduleErrorDialogAndUndo(const QString &message) { if (auto *app = QCoreApplication::instance()) { QMetaObject::invokeMethod(app, [message]() { ShowErrorDialogAndUndo(message); }, Qt::QueuedConnection); } } static olive::AVFramePtr DownloadTextureToFrame(const olive::TexturePtr &tex) { if (!tex || tex->IsDummy() || !tex->renderer()) { return nullptr; } const olive::VideoParams ¶ms = tex->params(); return ReadbackTextureToFrame(tex, params); } inline std::vector GetPluginSupportedDepths(const OFX::Host::ImageEffect::Descriptor& desc) { std::vector depths; const OFX::Host::Property::Set& props = desc.getProps(); // 获取数组维度(支持几种深度) int dim = props.getDimension(kOfxImageEffectPropSupportedPixelDepths); for (int i = 0; i < dim; ++i) { // Host Support Library 返回 const std::string& const std::string& val = props.getStringProperty( kOfxImageEffectPropSupportedPixelDepths, i ); if (!val.empty() && val != kOfxBitDepthNone) { depths.push_back(val); } } return depths; } // 查询插件/宿主是否支持「各 clip 不同深度」 inline bool SupportsMultipleClipDepths(const OFX::Host::ImageEffect::Descriptor& desc) { const OFX::Host::Property::Set& props = desc.getProps(); // 这是单值 int 属性(0 或 1),n = 0 int val = props.getIntProperty(kOfxImageEffectPropSupportsMultipleClipDepths, 0); return val != 0; } // 作用:根据插件描述符声明的 kOfxImageEffectPropSupportedPixelDepths, // 按优先级 F32 > U16 > U8 > F16 选择最佳输入像素格式。 // 参考 OpenFX API: OfxImageEffectPropSupportedPixelDepths // Purpose: Select best input pixel format from plugin descriptor's supported // depth list. Priority: F32 > U16 > U8 > F16. static PixelFormat SelectBestPluginInputFormat( const OFX::Host::ImageEffect::Descriptor& desc) { const OFX::Host::Property::Set& props = desc.getProps(); int dim = props.getDimension(kOfxImageEffectPropSupportedPixelDepths); bool supports_f32 = false; bool supports_u16 = false; bool supports_u8 = false; bool supports_f16 = false; for (int i = 0; i < dim; ++i) { const std::string& depth = props.getStringProperty( kOfxImageEffectPropSupportedPixelDepths, i); if (depth == kOfxBitDepthFloat) { supports_f32 = true; } else if (depth == kOfxBitDepthShort) { supports_u16 = true; } else if (depth == kOfxBitDepthByte) { supports_u8 = true; } else if (depth == kOfxBitDepthHalf) { supports_f16 = true; } } // 优先级:F32 > U16 > U8 > F16 if (supports_f32) return PixelFormat::F32; if (supports_u16) return PixelFormat::U16; if (supports_u8) return PixelFormat::U8; if (supports_f16) return PixelFormat::F16; return PixelFormat::INVALID; } // 作用:执行 OFX 插件渲染全流程(准备输入、调用动作、处理输出)。 // Purpose: Run full OFX plugin render flow (inputs, actions, outputs). void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job, olive::TexturePtr destination, olive::VideoParams destination_params, bool clear_destination, bool interactive) { auto instance=job.pluginInstance(); if (!instance) { return; } // Lock the plugin instance to prevent concurrent access from multiple // RenderProcessors. OlivePluginInstance (and its OliveClipInstance) are not // thread-safe; concurrent calls to setInputTexture/renderAction can corrupt // internal QMap/images_ and params_, leading to invalid pointers being // passed to CImg and subsequent SIGSEGV. std::mutex *instance_mutex = nullptr; if (auto *olive_inst = dynamic_cast(instance)) { instance_mutex = &olive_inst->mutex(); } else { static std::mutex fallback_mutex; instance_mutex = &fallback_mutex; } std::lock_guard instance_lock(*instance_mutex); bool supports_opengl = false; #ifdef OFX_SUPPORTS_OPENGLRENDER const std::string &gl_supported = instance->getDescriptor().getProps().getStringProperty( kOfxImageEffectPropOpenGLRenderSupported); supports_opengl = (gl_supported == "true" || gl_supported == "1"); #endif auto *olive_instance = dynamic_cast(instance); const bool use_opengl = supports_opengl && destination && destination->renderer() && destination->id().isValid(); if (olive_instance) { olive_instance->setVideoParam(destination_params); // Ensure all clip instances inherit the project's params so that // getAspectRatio/getFrameRate etc. return valid values before // createInstanceAction (which may call fetchClip and query them). for (int i = 0; i < olive_instance->getNClips(); ++i) { OliveClipInstance *clip = dynamic_cast( olive_instance->getNthClip(i)); if (clip) { clip->setParams(destination_params); } } } // current render scale of 1 OfxPointD renderScale; renderScale.x = renderScale.y = 1.0; int numFramesToRender=1; // Output Clip OliveClipInstance *output_clip=dynamic_cast(instance->getClip("Output")); if (!output_clip) { return; } // ensure the instance was created OfxStatus stat = kOfxStatOK; if (olive_instance && !olive_instance->isCreated()) { stat = instance->createInstanceAction(); if(stat != kOfxStatOK && stat != kOfxStatReplyDefault) { LogOfxFailure("createInstance", stat, instance); MarkRenderFailure(destination); return; } } OfxTime frame = job.time_seconds(); const auto &clips = olive_instance->getDescriptor().getClips(); QString effect_input_id; if (const auto *node = job.node()) { effect_input_id = node->GetEffectInputID(); } auto is_usable_input = [](const TexturePtr &tex) { if (!tex) { return false; } if (!tex->IsDummy() && tex->renderer()) { return true; } AVFramePtr frame = tex->frame(); return frame && frame->data[0]; }; std::map input_textures; std::map input_clips; std::map input_params; auto values = job.GetValues(); for (const auto &entry : clips) { if (entry.first == kOfxImageEffectOutputClipName) { continue; } OliveClipInstance *input_clip = dynamic_cast(instance->getClip(entry.first)); if (!input_clip) { continue; } const QString clip_key = QString::fromStdString(entry.first); TexturePtr input_tex = nullptr; if (!effect_input_id.isEmpty() && clip_key == effect_input_id && is_usable_input(src)) { input_tex = src; } else { input_tex = values.value(clip_key).toTexture(); if (!input_tex && entry.first == kOfxImageEffectSimpleSourceClipName) { input_tex = values.value(kTextureInput).toTexture(); } } if (!is_usable_input(input_tex) && entry.first == kOfxImageEffectSimpleSourceClipName && is_usable_input(src)) { input_tex = src; } if (is_usable_input(input_tex)) { input_textures[entry.first] = input_tex; olive::VideoParams params = input_tex->params(); // First pass: set params only, no CPU readback yet. // Readback will happen below (CPU path) or be skipped entirely (GL path). input_clip->setInputTexture(input_tex, frame, false); input_clips[entry.first] = input_clip; } } // call getClipPreferences to know which format plugin requires. // getClipPreferences internally calls setupClipPreferencesArgs, which // validates that all connected input clips have the same frame rate. // Wrap in try/catch because setupClipPreferencesArgs throws on validation // failure and would otherwise crash the render thread. bool ok = false; try { 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) << "stat=" << e.getStatus(); MarkRenderFailure(destination); ScheduleErrorDialogAndUndo( QObject::tr("Plugin %1 failed because connected inputs have different frame rates.\n" "The last operation has been undone.") .arg(PluginIdForInstance(instance))); return; } catch (const std::exception &e) { qWarning().noquote() << "OFX getClipPreferences threw exception for plugin=" << PluginIdForInstance(instance) << "what=" << e.what(); MarkRenderFailure(destination); ScheduleErrorDialogAndUndo( QObject::tr("Plugin %1 encountered an error: %2\n" "The last operation has been undone.") .arg(PluginIdForInstance(instance), QString::fromUtf8(e.what()))); return; } if (!ok) { qWarning().noquote() << "OFX getClipPreferences failed for plugin=" << PluginIdForInstance(instance); MarkRenderFailure(destination); ScheduleErrorDialogAndUndo( QObject::tr("Plugin %1 failed to get clip preferences.\n" "The last operation has been undone.") .arg(PluginIdForInstance(instance))); return; } /// RoI is in canonical coords. OfxRectD regionOfInterest; regionOfInterest.x1 = 0.0; regionOfInterest.y1 = 0.0; regionOfInterest.x2 = destination_params.width() * destination_params.pixel_aspect_ratio().toDouble(); regionOfInterest.y2 = destination_params.height(); OfxRectD regionOfDefinition = regionOfInterest; output_clip->setRegionOfDefinition(regionOfDefinition, frame); output_clip->setOutputTexture(destination, frame); // get the RoI for each input clip // the regions of interest for each input clip are returned in a std::map // on a real host, these will be the regions of each input clip that the // effect needs to render a given frame (clipped to the RoD). // // In our example we are doing full frame fetches regardless. // set correct format for input // Ensure all input textures are fully rendered before CPU readback. // BlitColorManaged may have executed in a different shared OpenGL context; // glFinish() in our context does NOT wait for commands in that context, // so we must flush the renderer that actually produced the texture. for (const auto &entry : input_textures) { if (entry.second && entry.second->renderer()) { entry.second->renderer()->Flush(); } } auto &descriptor = instance->getDescriptor(); for (const auto &entry : input_clips) { if (entry.first == kOfxImageEffectOutputClipName) { continue; } OliveClipInstance *input_clip = entry.second; if (!input_clip) { continue; } const QString clip_key = QString::fromStdString(entry.first); TexturePtr input_tex = input_textures[entry.first]; if (is_usable_input(input_tex)) { // Query the plugin descriptor for supported pixel depths and pick // the best one according to our priority: F32 > U16 > U8 > F16. // OpenFX reference: kOfxImageEffectPropSupportedPixelDepths on // the image effect descriptor lists all depths the plugin can handle. PixelFormat chosen_fmt = SelectBestPluginInputFormat(descriptor); VideoParams params = input_tex->params(); if (chosen_fmt != PixelFormat::INVALID && params.format() != chosen_fmt) { params.set_format(chosen_fmt); TexturePtr converted_tex = ConvertTextureForParams(input_tex, params); if (converted_tex && is_usable_input(converted_tex)) { input_tex = converted_tex; input_textures[entry.first] = input_tex; } } input_clip->setInputTexture(input_tex, frame, !use_opengl); OfxRectD rod; rod.x1 = 0; rod.y1 = 0; rod.x2 = params.width() * params.pixel_aspect_ratio().toDouble(); rod.y2 = params.height(); input_clip->setRegionOfDefinition(rod, frame); input_clips[entry.first] = input_clip; } } std::map rois; stat = instance->getRegionOfInterestAction(frame, renderScale, regionOfInterest, rois); if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { // Some plugins (e.g. CImg filters) return BadHandle from getRegionOfInterest // when internal clip/property handles are not fully initialized. // Treat this as a non-fatal error and fall back to default RoI. if (stat == kOfxStatErrBadHandle) { qWarning().noquote() << "OFX getRegionOfInterest returned BadHandle for plugin=" << PluginIdForInstance(instance) << "- using default RoI"; } else { LogOfxFailure("getRegionOfInterest", stat, instance); MarkRenderFailure(destination); return; } } // set correct format for output // Query plugin-supported depths and pick best according to our priority: // F32 > U16 > U8 > F16. If plugin supports F32 we render directly to F32 // (zero conversion). If plugin only supports U8/U16 we let it render in // that format and ConvertFrameIfNeeded will convert back to F32 afterwards. VideoParams output_params = destination_params; PixelFormat best_fmt = SelectBestPluginInputFormat(descriptor); if (best_fmt != PixelFormat::INVALID) { output_params.set_format(best_fmt); } output_clip->setParams(output_params); std::string component = output_clip->getUnmappedComponents(); if (!component.empty() && component != kOfxImageComponentNone) { output_params.set_channel_count(component); } // Re-set params so the clip knows the possibly-changed channel count output_clip->setParams(output_params); // The render window is in pixel coordinates // ie: render scale and a PAR of not 1 OfxRectI renderWindow; renderWindow.x1 = renderWindow.y1 = 0; renderWindow.x2 = destination_params.width(); renderWindow.y2 = destination_params.height(); stat = instance->beginRenderAction(frame, numFramesToRender, 1.0, false, renderScale, true, interactive); if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { LogOfxFailure("beginRender", stat, instance); MarkRenderFailure(destination); return; } #ifdef OFX_SUPPORTS_OPENGLRENDER if (use_opengl) { instance->contextAttachedAction(); AttachOutputTexture(destination); } #endif if (!output_params.is_valid()) { qWarning().noquote() << "OFX render skipped due to invalid output params for plugin=" << PluginIdForInstance(instance); MarkRenderFailure(destination); instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); 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, true, interactive, interactive); if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { LogOfxFailure("render", stat, instance); LogClipState("output", output_clip, &output_params); for (const auto &entry : input_clips) { const auto params_it = input_params.find(entry.first); const olive::VideoParams *params = (params_it != input_params.end()) ? ¶ms_it->second : nullptr; LogClipState("input", entry.second, params); OFX::Host::ImageEffect::Image *image = entry.second->getImage(frame, nullptr); LogImageProps("input", image); //if (image) { //image->releaseReference(); //} } OFX::Host::ImageEffect::Image* output_image = output_clip->getOutputImage(frame); LogImageProps("output", output_image); MarkRenderFailure(destination); instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } // get the output image buffer (CPU path only) OFX::Host::ImageEffect::Image* output_image; if (!use_opengl) { output_image = output_clip->getOutputImage(frame); if (!output_image) { qWarning().noquote() << "OFX getOutputImage returned null for plugin=" << PluginIdForInstance(instance); MarkRenderFailure(destination); instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } // Diagnostic: peek at first few pixels void *img_data = output_image->getPointerProperty(kOfxImagePropData); bool img_black = true; if (img_data) { float *f = static_cast(img_data); for (int i = 0; i < 16; ++i) { if (f[i] != 0.0f) { img_black = false; break; } } } } else { if (!destination || !destination->id().isValid()) { #ifdef OFX_SUPPORTS_OPENGLRENDER DetachOutputTexture(); instance->contextDetachedAction(); #endif instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } } if (!use_opengl) { AVFramePtr frame_ptr = create_avframe_from_ofx_image_with_params(*output_image, output_params); if (!frame_ptr) { qWarning().noquote() << "OFX output image conversion failed for plugin=" << PluginIdForInstance(instance); instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params, this); const AVPixelFormat expected_fmt = GetDestinationAVPixelFormat(destination_params); destination->handleFrame(converted); if (destination->renderer() && converted && converted->data[0] && (expected_fmt == AV_PIX_FMT_NONE || converted->format == expected_fmt)) { int linesize_pixels = LinesizeToPixels(destination_params, converted->linesize[0]); if (linesize_pixels <= 0) { linesize_pixels = destination_params.effective_width(); } destination->Upload(converted->data[0], linesize_pixels); } else if (destination->renderer() && converted && converted->data[0]) { qWarning().noquote() << "OFX output pixel format mismatch for plugin=" << PluginIdForInstance(instance); } } else { // OpenGL path: plugin has already rendered directly into the destination // texture via FBO/GL. No CPU readback or conversion needed. #ifdef OFX_SUPPORTS_OPENGLRENDER DetachOutputTexture(); instance->contextDetachedAction(); #endif instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true,interactive ); } // 作用:绑定输出纹理到 OFX 的 GL 输出路径。 // Purpose: Attach output texture for OFX GL rendering. void olive::plugin::PluginRenderer::AttachOutputTexture(olive::TexturePtr texture) { if (!texture) { return; } AttachTextureAsDestination(texture->id()); } // 作用:解除 OFX 的 GL 输出绑定。 // Purpose: Detach OFX GL output binding. void olive::plugin::PluginRenderer::DetachOutputTexture() { DetachTextureAsDestination(); }