Finish TODO 8,9

This commit is contained in:
2026-01-04 23:53:12 +08:00
parent 271f56f3dd
commit 4cbdfd1a88
8 changed files with 325 additions and 24 deletions
+8 -2
View File
@@ -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 纹理路径。
---
+75 -2
View File
@@ -32,6 +32,9 @@
#include <cmath>
#include <cstring>
#include <memory>
#ifdef OFX_SUPPORTS_OPENGLRENDER
#include <QOpenGLFunctions>
#endif
extern "C" {
#include <libswscale/swscale.h>
@@ -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<int>(std::floor(rod_d.x1)),
static_cast<int>(std::floor(rod_d.y1)),
static_cast<int>(std::ceil(rod_d.x2)),
static_cast<int>(std::ceil(rod_d.y2)) };
OfxRectI bounds = rod;
if (optionalBounds) {
bounds.x1 = static_cast<int>(std::floor(optionalBounds->x1));
bounds.y1 = static_cast<int>(std::floor(optionalBounds->y1));
bounds.x2 = static_cast<int>(std::ceil(optionalBounds->x2));
bounds.y2 = static_cast<int>(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<uintptr_t>(gl_texture)) + "_" +
std::to_string(static_cast<long long>(time));
const int texture_id = gl_texture->id().value<GLuint>();
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
+8 -1
View File
@@ -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<OfxTime,std::shared_ptr<Image>> images_;
#ifdef OFX_SUPPORTS_OPENGLRENDER
QMap<OfxTime, TexturePtr> input_textures_;
QMap<OfxTime, Texture *> output_textures_;
#endif
};
}
}
+12
View File
@@ -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;
}
+46 -1
View File
@@ -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 &params)
{
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,
+14 -3
View File
@@ -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> persistentErrors_;
@@ -195,6 +205,7 @@ private:
QPointer<ProgressDialog> progress_dialog_;
bool progress_cancelled_ = false;
bool progress_active_ = false;
bool open_gl_enabled_ = false;
};
}
}
+160 -15
View File
@@ -151,6 +151,94 @@ static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams &param
return pix_fmt;
}
static const char *GetRenderFieldForParams(const olive::VideoParams &params)
{
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 &params)
{
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<AVPixelFormat>(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<olive::plugin::OlivePluginInstance *>(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<plugin::OliveClipInstance *>(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<OFX::Host::ImageEffect::Image> 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<OFX::Host::ImageEffect::Image> 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();
}
+2
View File
@@ -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,