diff --git a/TODO-zh.md b/TODO-zh.md index 5128c8356..dad0986ff 100644 --- a/TODO-zh.md +++ b/TODO-zh.md @@ -45,12 +45,18 @@ 8) Project Extent / Fielding 行为待确认 - 现状:`OlivePluginInstance::getProjectExtent()` 有 “TODO” 注释。 - 为什么需要:OFX 插件会根据项目尺寸、扫描线场信息做渲染决策。 -- 可能改动:明确工程是否支持不同 extent/fielding,确保返回值与项目设置一致。 +- 已完成: + - `getProjectExtent()` 明确返回项目宽高,不再留 TODO。 + - `getDefaultOutputFielding()` 根据 `VideoParams::interlacing()` 映射为 OFX fielding。 + - 渲染时 `renderAction()` 的 field 参数根据项目是否交错决定(progressive 用 `None`,interlaced 用 `Both`)。 9) OpenGL Render Suite 支持 - 现状:`OliveClipInstance::loadTexture()` 返回 null,OpenGL 渲染路径未实现。 - 为什么需要:有些 OFX 插件只支持 OpenGL 渲染,不支持 CPU 渲染。 -- 可能改动:要么实现 OpenGL texture 的加载与生命周期,要么在 host capability 中明确禁用 OpenGL render。 +- 已完成: + - Host 端声明 `kOfxImageEffectPropOpenGLRenderSupported = true`。 + - `OliveClipInstance::loadTexture()` 返回真实的 OpenGL 纹理句柄。 + - 渲染前设置 Output Clip 的目标纹理,输入纹理走 OpenGL 纹理路径。 --- diff --git a/app/pluginSupport/OliveClip.cpp b/app/pluginSupport/OliveClip.cpp index 7b65c3ff4..d4d7cf19a 100644 --- a/app/pluginSupport/OliveClip.cpp +++ b/app/pluginSupport/OliveClip.cpp @@ -32,6 +32,9 @@ #include #include #include +#ifdef OFX_SUPPORTS_OPENGLRENDER +#include +#endif extern "C" { #include @@ -203,12 +206,15 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi if (!texture) { return; } + this->params_ = texture->params(); +#ifdef OFX_SUPPORTS_OPENGLRENDER + input_textures_.insert(time, texture); +#endif + AVFramePtr frame = texture->frame(); if (!frame || !frame->data[0]) { return; } - - this->params_=texture->params(); AVPixelFormat expected_fmt = FFmpegUtils::GetFFmpegPixelFormat(params_.format(), params_.channel_count()); @@ -282,3 +288,70 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi copy_bytes); } } + +void olive::plugin::OliveClipInstance::setOutputTexture(Texture *texture, + OfxTime time) +{ +#ifdef OFX_SUPPORTS_OPENGLRENDER + if (!texture) { + return; + } + output_textures_.insert(time, texture); +#else + (void)texture; + (void)time; +#endif +} + +#ifdef OFX_SUPPORTS_OPENGLRENDER +OFX::Host::ImageEffect::Texture * +olive::plugin::OliveClipInstance::loadTexture(OfxTime time, const char *format, + const OfxRectD *optionalBounds) +{ + (void)format; + + Texture *gl_texture = nullptr; + if (isOutput()) { + gl_texture = output_textures_.value(time, nullptr); + } else { + TexturePtr input = input_textures_.value(time); + gl_texture = input ? input.get() : nullptr; + } + + if (!gl_texture || gl_texture->IsDummy() || !gl_texture->id().isValid()) { + return nullptr; + } + + OfxRectD rod_d = getRegionOfDefinition(time); + OfxRectI rod = { static_cast(std::floor(rod_d.x1)), + static_cast(std::floor(rod_d.y1)), + static_cast(std::ceil(rod_d.x2)), + static_cast(std::ceil(rod_d.y2)) }; + OfxRectI bounds = rod; + if (optionalBounds) { + bounds.x1 = static_cast(std::floor(optionalBounds->x1)); + bounds.y1 = static_cast(std::floor(optionalBounds->y1)); + bounds.x2 = static_cast(std::ceil(optionalBounds->x2)); + bounds.y2 = static_cast(std::ceil(optionalBounds->y2)); + } + bounds.x1 = std::max(bounds.x1, rod.x1); + bounds.y1 = std::max(bounds.y1, rod.y1); + bounds.x2 = std::min(bounds.x2, rod.x2); + bounds.y2 = std::min(bounds.y2, rod.y2); + + const int bytes_per_row = + params_.width() * params_.channel_count() * params_.format().byte_count(); + const std::string &field = getFieldOrder(); + const std::string unique_id = std::to_string( + reinterpret_cast(gl_texture)) + "_" + + std::to_string(static_cast(time)); + + const int texture_id = gl_texture->id().value(); + OFX::Host::ImageEffect::Texture *texture = + new OFX::Host::ImageEffect::Texture( + *this, 1.0, 1.0, texture_id, GL_TEXTURE_2D, bounds, rod, + bytes_per_row, field, unique_id); + texture->addReference(); + return texture; +} +#endif diff --git a/app/pluginSupport/OliveClip.h b/app/pluginSupport/OliveClip.h index 2cb56962b..575d0c2d0 100644 --- a/app/pluginSupport/OliveClip.h +++ b/app/pluginSupport/OliveClip.h @@ -70,10 +70,13 @@ public: params_ = params; } # ifdef OFX_SUPPORTS_OPENGLRENDER - OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time, const char *format, const OfxRectD *optionalBounds) { return NULL; }; + OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time, + const char *format, + const OfxRectD *optionalBounds) override; # endif void setInputTexture(TexturePtr texture, OfxTime time); + void setOutputTexture(Texture *texture, OfxTime time); private: VideoParams params_; @@ -84,6 +87,10 @@ private: std::string name_; QMap> images_; +#ifdef OFX_SUPPORTS_OPENGLRENDER + QMap input_textures_; + QMap output_textures_; +#endif }; } } diff --git a/app/pluginSupport/OliveHost.cpp b/app/pluginSupport/OliveHost.cpp index e8f5d6a46..40268d223 100644 --- a/app/pluginSupport/OliveHost.cpp +++ b/app/pluginSupport/OliveHost.cpp @@ -77,6 +77,10 @@ ImageEffect::Descriptor * OliveHost::makeDescriptor(ImageEffect::ImageEffectPlugin* plugin) { ImageEffect::Descriptor* desc = new ImageEffect::Descriptor(plugin); +#ifdef OFX_SUPPORTS_OPENGLRENDER + desc->getProps().setStringProperty(kOfxImageEffectPropOpenGLRenderSupported, + "true"); +#endif descriptors_.append(desc); return desc; } @@ -85,6 +89,10 @@ OliveHost::makeDescriptor(const ImageEffect::Descriptor &rootContext, ImageEffect::ImageEffectPlugin *plugin) { ImageEffect::Descriptor* desc = new ImageEffect::Descriptor(rootContext,plugin); +#ifdef OFX_SUPPORTS_OPENGLRENDER + desc->getProps().setStringProperty(kOfxImageEffectPropOpenGLRenderSupported, + "true"); +#endif descriptors_.append(desc); return desc; } @@ -93,6 +101,10 @@ OliveHost::makeDescriptor(const std::string &bundlePath, ImageEffect::ImageEffectPlugin *plugin) { ImageEffect::Descriptor* desc = new ImageEffect::Descriptor(bundlePath, plugin); +#ifdef OFX_SUPPORTS_OPENGLRENDER + desc->getProps().setStringProperty(kOfxImageEffectPropOpenGLRenderSupported, + "true"); +#endif descriptors_.append(desc); return desc; } diff --git a/app/pluginSupport/OlivePluginInstance.cpp b/app/pluginSupport/OlivePluginInstance.cpp index 9a95419c1..7513425fe 100644 --- a/app/pluginSupport/OlivePluginInstance.cpp +++ b/app/pluginSupport/OlivePluginInstance.cpp @@ -18,6 +18,7 @@ #include "OlivePluginInstance.h" #include "OliveClip.h" +#include "ofxGPURender.h" #include "ofxCore.h" #include "ofxMessage.h" #include "common/Current.h" @@ -42,6 +43,19 @@ namespace olive namespace plugin { namespace { +const std::string &FieldOrderForParams(const VideoParams ¶ms) +{ + switch (params.interlacing()) { + case VideoParams::kInterlaceNone: + return kOfxImageFieldNone; + case VideoParams::kInterlacedTopFirst: + return kOfxImageFieldUpper; + case VideoParams::kInterlacedBottomFirst: + return kOfxImageFieldLower; + } + return kOfxImageFieldNone; +} + class DeferredRedoCommand : public UndoCommand { public: explicit DeferredRedoCommand(UndoCommand *inner) @@ -107,6 +121,11 @@ ViewerOutput *GetActiveViewerOutput() } } // namespace +const std::string &OlivePluginInstance::getDefaultOutputFielding() const +{ + return FieldOrderForParams(params_); +} + OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id, const char *format, va_list args) { @@ -196,7 +215,6 @@ void OlivePluginInstance::getProjectExtent(double &xSize, double &ySize) const { xSize =params_.width(); ySize =params_.height(); - // TODO: Ensure this project does not support this. } double OlivePluginInstance::getProjectPixelAspectRatio() const { @@ -392,6 +410,22 @@ bool OlivePluginInstance::progressUpdate(double t) return !progress_cancelled_; } +OfxStatus OlivePluginInstance::contextAttachedAction() +{ + if (!open_gl_enabled_) { + return kOfxStatReplyDefault; + } + return kOfxStatOK; +} + +OfxStatus OlivePluginInstance::contextDetachedAction() +{ + if (!open_gl_enabled_) { + return kOfxStatReplyDefault; + } + return kOfxStatOK; +} + double OlivePluginInstance::timeLineGetTime() { if (ViewerOutput *viewer = GetActiveViewerOutput()) { @@ -420,6 +454,17 @@ void OlivePluginInstance::timeLineGetBounds(double &t1, double &t2) t2 = 0.0; } +void OlivePluginInstance::setCustomInArgs(const std::string &action, + OFX::Host::Property::Set &inArgs) +{ + if (action == kOfxImageEffectActionRender || + action == kOfxImageEffectActionBeginSequenceRender || + action == kOfxImageEffectActionEndSequenceRender) { + inArgs.setIntProperty(kOfxImageEffectPropOpenGLEnabled, + open_gl_enabled_ ? 1 : 0); + } +} + OFX::Host::ImageEffect::ClipInstance *OlivePluginInstance::newClipInstance( OFX::Host::ImageEffect::Instance *plugin, OFX::Host::ImageEffect::ClipDescriptor *descriptor, diff --git a/app/pluginSupport/OlivePluginInstance.h b/app/pluginSupport/OlivePluginInstance.h index f1e12893d..908638eed 100644 --- a/app/pluginSupport/OlivePluginInstance.h +++ b/app/pluginSupport/OlivePluginInstance.h @@ -67,9 +67,7 @@ public: } explicit OlivePluginInstance(Instance & instance):Instance(instance){}; ~OlivePluginInstance() override = default; - const std::string &getDefaultOutputFielding() const{ - return kOfxImageFieldNone; - }; + const std::string &getDefaultOutputFielding() const override; void setVideoParam(VideoParams params) { @@ -79,6 +77,10 @@ public: { node_ = node; } + void setOpenGLEnabled(bool enabled) + { + open_gl_enabled_ = enabled; + } OFX::Host::ImageEffect::ClipInstance *newClipInstance( OFX::Host::ImageEffect::Instance *plugin, OFX::Host::ImageEffect::ClipDescriptor *descriptor, @@ -165,6 +167,11 @@ public: /// false if you should abandon processing, true to continue virtual bool progressUpdate(double t) override; +#ifdef OFX_SUPPORTS_OPENGLRENDER + virtual OfxStatus contextAttachedAction() override; + virtual OfxStatus contextDetachedAction() override; +#endif + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -182,6 +189,9 @@ public: /// get the first and last times available on the effect's timeline virtual void timeLineGetBounds(double &t1, double &t2); + void setCustomInArgs(const std::string &action, + OFX::Host::Property::Set &inArgs) override; + private: QList persistentErrors_; @@ -195,6 +205,7 @@ private: QPointer progress_dialog_; bool progress_cancelled_ = false; bool progress_active_ = false; + bool open_gl_enabled_ = false; }; } } diff --git a/app/render/plugin/pluginrenderer.cpp b/app/render/plugin/pluginrenderer.cpp index d4e464cd1..6f5bf8cdd 100644 --- a/app/render/plugin/pluginrenderer.cpp +++ b/app/render/plugin/pluginrenderer.cpp @@ -151,6 +151,94 @@ static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶m return pix_fmt; } +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; +} + +static olive::AVFramePtr ReadbackTextureToFrame(olive::Texture *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()) { + texture->renderer()->DownloadFromTexture( + texture->id(), params, frame->data[0], frame->linesize[0]); + } + 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()) { + texture->renderer()->DownloadFromTexture( + texture->id(), rgba_params, rgba_frame->data[0], + rgba_frame->linesize[0]); + } + + 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; +} + static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, const olive::VideoParams &dst_params) { @@ -198,6 +286,16 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: bool clear_destination, bool interactive) { auto instance=job.pluginInstance(); + if (!instance) { + return; + } + auto *olive_instance = + dynamic_cast(instance); + const bool use_opengl = + destination && destination->renderer() && destination->id().isValid(); + if (olive_instance) { + olive_instance->setOpenGLEnabled(use_opengl); + } OfxStatus stat; stat = instance->createInstanceAction(); if(stat != kOfxStatOK && stat != kOfxStatReplyDefault) { @@ -243,8 +341,17 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: return; } + if (use_opengl) { + instance->contextAttachedAction(); + AttachOutputTexture(destination); + } + OliveClipInstance *clip=dynamic_cast(instance->getClip("Output")); if (!clip) { + if (use_opengl) { + DetachOutputTexture(); + instance->contextDetachedAction(); + } instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive ); return; @@ -255,6 +362,7 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: clip->setParams(destination_params); clip->setRegionOfDefinition(regionOfDefinition, frame); + clip->setOutputTexture(destination, frame); const NodeValueRow &values = job.GetValues(); const auto &clips = instance->getDescriptor().getClips(); @@ -288,28 +396,65 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: assert(stat == kOfxStatOK || stat == kOfxStatReplyDefault); // render a frame - stat = instance->renderAction(0,kOfxImageFieldBoth,renderWindow, renderScale, true, interactive, interactive); + const char *render_field = GetRenderFieldForParams(destination_params); + stat = instance->renderAction(0, render_field, renderWindow, renderScale, + true, interactive, interactive); assert(stat == kOfxStatOK); - // get the output image buffer - std::shared_ptr output_image = clip->getOutputImage(frame); - if (!output_image) { - instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true,interactive - ); - return; + // get the output image buffer (CPU path only) + std::shared_ptr output_image; + if (!use_opengl) { + output_image = clip->getOutputImage(frame); + if (!output_image) { + instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, + renderScale, true, interactive); + return; + } + } else { + if (!destination || !destination->id().isValid()) { + DetachOutputTexture(); + instance->contextDetachedAction(); + instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, + renderScale, true, interactive); + return; + } } - AVFramePtr frame_ptr = create_avframe_from_ofx_image(*output_image); - if (!frame_ptr) { - instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive - ); - return; + if (!use_opengl) { + AVFramePtr frame_ptr = create_avframe_from_ofx_image(*output_image); + if (!frame_ptr) { + instance->endRenderAction(0, numFramesToRender, 1.0, interactive, + renderScale, true, interactive); + return; + } + AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params); + destination->handleFrame(converted); + } else { + AVFramePtr frame_ptr = + ReadbackTextureToFrame(destination, destination_params); + DetachOutputTexture(); + instance->contextDetachedAction(); + if (frame_ptr) { + AVFramePtr converted = + ConvertFrameIfNeeded(frame_ptr, destination_params); + destination->handleFrame(converted); + } } - AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params); - destination->handleFrame(converted); - instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive ); } + +void olive::plugin::PluginRenderer::AttachOutputTexture(olive::Texture *texture) +{ + if (!texture) { + return; + } + AttachTextureAsDestination(texture->id()); +} + +void olive::plugin::PluginRenderer::DetachOutputTexture() +{ + DetachTextureAsDestination(); +} diff --git a/app/render/plugin/pluginrenderer.h b/app/render/plugin/pluginrenderer.h index 13e725598..721a64b07 100644 --- a/app/render/plugin/pluginrenderer.h +++ b/app/render/plugin/pluginrenderer.h @@ -42,6 +42,8 @@ class PluginRenderer : public olive::OpenGLRenderer{ public: PluginRenderer(QObject *parent=nullptr):OpenGLRenderer(parent){}; virtual ~PluginRenderer() override{}; + void AttachOutputTexture(olive::Texture *texture); + void DetachOutputTexture(); protected: virtual void RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job, olive::Texture *destination,