From 4d393f3d2379c7e88a6cdc5b0d72db66e161f4f1 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sat, 10 Jan 2026 18:56:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E8=A7=A3=E5=86=B3=E6=8F=92?= =?UTF-8?q?=E4=BB=B6bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clangd | 3 +- CMakeLists.txt | 7 + app/node/plugins/Plugin.cpp | 19 +- app/pluginSupport/OliveClip.cpp | 283 +++++++++++++- app/pluginSupport/OliveClip.h | 4 +- app/pluginSupport/OliveHost.cpp | 61 ++- app/pluginSupport/OliveHost.h | 19 +- app/pluginSupport/OlivePluginInstance.cpp | 157 +++++--- app/pluginSupport/paraminstance.h | 6 +- app/render/job/pluginjob.h | 15 +- app/render/opengl/openglrenderer.cpp | 58 ++- app/render/opengl/openglrenderer.h | 2 + app/render/plugin/pluginrenderer.cpp | 366 +++++++++++++++++- app/render/plugin/pluginrenderer.h | 7 +- app/render/renderprocessor.cpp | 2 +- .../standardcombos/channellayoutcombobox.h | 11 +- tests/gtest/CMakeLists.txt | 3 + tests/gtest/opengl_readback_guard_test.cpp | 26 ++ tests/gtest/plugin_ofx_integration_test.cpp | 2 +- tests/gtest/plugin_renderer_readback_test.cpp | 19 + tests/gtest/plugin_support_param_test.cpp | 24 ++ .../HostSupport/include/ofxhImageEffect.h | 6 +- .../HostSupport/include/ofxhImageEffectAPI.h | 6 +- .../HostSupport/src/ofxhImageEffectAPI.cpp | 14 +- 24 files changed, 959 insertions(+), 161 deletions(-) create mode 100644 tests/gtest/opengl_readback_guard_test.cpp create mode 100644 tests/gtest/plugin_renderer_readback_test.cpp create mode 100644 tests/gtest/plugin_support_param_test.cpp diff --git a/.clangd b/.clangd index 579419f34..bfb28d152 100644 --- a/.clangd +++ b/.clangd @@ -1,6 +1,7 @@ CompileFlags: Add: [] Remove: [-mlongcalls, -fstrict-volatile-bitfields, -fno-shrink-wrap, -fno-tree-switch-conversion, -mno-direct-extern-access] - +CompileFlags: + CompilationDatabase: build Diagnostics: Suppress: ['drv_unknown_argument', 'unused-includes', 'pp_file_not_found'] \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5301aa2b4..09a81edcf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,10 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory -g") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory -g") set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=memory) endif () + + +set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -DOFX_SUPPORTS_OPENGLRENDER) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOFX_SUPPORTS_OPENGLRENDER") option(BUILD_QT6 "Build with Qt 6 over 5 (experimental)" ON) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) option(BUILD_TESTS "Build unit tests" OFF) @@ -55,6 +59,7 @@ if(MSVC) /external:W0 "$<$:/O2>" "$<$:/MP>" + /DOFX_SUPPORTS_OPENGLRENDER ) if (USE_WERROR) list(APPEND OLIVE_COMPILE_OPTIONS "/WX") @@ -68,6 +73,8 @@ else() -Wextra -Wno-unused-parameter -Wshadow + -DOFX_SUPPORTS_OPENGLRENDER + ) if (USE_WERROR) list(APPEND OLIVE_COMPILE_OPTIONS "-Werror") diff --git a/app/node/plugins/Plugin.cpp b/app/node/plugins/Plugin.cpp index 289924844..da9bf4a35 100644 --- a/app/node/plugins/Plugin.cpp +++ b/app/node/plugins/Plugin.cpp @@ -122,7 +122,24 @@ olive::plugin::PluginNode::PluginNode( } const QString input_id = QString::fromStdString(param.second->getName()); + if (input_id.isEmpty()) { + continue; + } + const auto &props = param.second->getProperties(); + if (props.getIntProperty(kOfxParamPropSecret) != 0) { + continue; + } + if (type == NodeValue::kNone) { + continue; + } AddInput(input_id, type); + const QString label = + QString::fromStdString(param.second->getLabel()); + if (!label.isEmpty()) { + SetInputName(input_id, label); + } else { + SetInputName(input_id, input_id); + } const QString parent = QString::fromStdString(param.second->getParentName()); if (!parent.isEmpty()) { @@ -209,7 +226,7 @@ void olive::plugin::PluginNode::Value(const NodeValueRow &value, } } if (tex && plugin_instance_) { - PluginJob job(plugin_instance_, this, value); + PluginJob job(plugin_instance_, this, value, globals.time().in()); table->Push(NodeValue::kTexture, tex->toJob(job), this); } } diff --git a/app/pluginSupport/OliveClip.cpp b/app/pluginSupport/OliveClip.cpp index 517bfd474..a3ed569bd 100644 --- a/app/pluginSupport/OliveClip.cpp +++ b/app/pluginSupport/OliveClip.cpp @@ -35,9 +35,11 @@ #ifdef OFX_SUPPORTS_OPENGLRENDER #include #endif - +#include "common/ffmpegutils.h" +#include "render/renderer.h" extern "C" { #include +#include } namespace { const std::string kBitDepthNoneStr(kOfxBitDepthNone); @@ -54,6 +56,239 @@ const std::string kImageUnPremultStr(kOfxImageUnPreMultiplied); const std::string kImageFieldNoneStr(kOfxImageFieldNone); const std::string kImageFieldUpperStr(kOfxImageFieldUpper); const std::string kImageFieldLowerStr(kOfxImageFieldLower); + +static int BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms) +{ + const int bytes_per_pixel = + params.channel_count() * params.format().byte_count(); + if (bytes_per_pixel <= 0) { + return 0; + } + return byte_linesize / bytes_per_pixel; +} + +static int PackedFloatChannels(AVPixelFormat fmt) +{ + switch (fmt) { + case AV_PIX_FMT_GRAYF32LE: + case AV_PIX_FMT_GRAYF32BE: + return 1; + case AV_PIX_FMT_RGBF32LE: + case AV_PIX_FMT_RGBF32BE: + return 3; + case AV_PIX_FMT_RGBAF32LE: + case AV_PIX_FMT_RGBAF32BE: + return 4; + default: + return 0; + } +} + +static bool PackedDstInfo(AVPixelFormat fmt, int *channels, + int *bytes_per_component) +{ + switch (fmt) { + case AV_PIX_FMT_GRAY8: + *channels = 1; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_RGB24: + *channels = 3; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_RGBA: + *channels = 4; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_GRAY16LE: + *channels = 1; + *bytes_per_component = 2; + return true; + case AV_PIX_FMT_RGB48LE: + *channels = 3; + *bytes_per_component = 2; + return true; + case AV_PIX_FMT_RGBA64LE: + *channels = 4; + *bytes_per_component = 2; + return true; + default: + return false; + } +} + +static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture, + const olive::VideoParams ¶ms) +{ + if (!texture || texture->IsDummy() || !texture->renderer()) { + return nullptr; + } + + AVPixelFormat pix_fmt = + olive::FFmpegUtils::GetFFmpegPixelFormat(params.format(), + params.channel_count()); + 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; + } + const int linesize_pixels = BytesToPixels(frame->linesize[0], params); + texture->renderer()->DownloadFromTexture(texture->id(), params, + frame->data[0], + linesize_pixels); + return frame; + } + + 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; + } + + const int linesize_pixels = + 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; +} + +static olive::AVFramePtr ConvertPackedFloatFrame(olive::AVFramePtr src, + AVPixelFormat dst_fmt) +{ + if (!src || !src->data[0]) { + return nullptr; + } + const int src_channels = + PackedFloatChannels(static_cast(src->format)); + if (src_channels == 0) { + return nullptr; + } + + int dst_channels = 0; + int bytes_per_component = 0; + if (!PackedDstInfo(dst_fmt, &dst_channels, &bytes_per_component)) { + return nullptr; + } + + olive::AVFramePtr dst = olive::CreateAVFramePtr(); + dst->format = dst_fmt; + dst->width = src->width; + dst->height = src->height; + if (av_frame_get_buffer(dst.get(), 0) < 0) { + return nullptr; + } + + auto clamp01 = [](float v) -> float { + return std::clamp(v, 0.0f, 1.0f); + }; + + for (int y = 0; y < src->height; ++y) { + const float *src_row = reinterpret_cast( + src->data[0] + y * src->linesize[0]); + uint8_t *dst_row = dst->data[0] + y * dst->linesize[0]; + + if (bytes_per_component == 2) { + auto *dst_row_u16 = reinterpret_cast(dst_row); + for (int x = 0; x < src->width; ++x) { + const float *pix = src_row + x * src_channels; + float r = pix[0]; + float g = (src_channels > 1) ? pix[1] : r; + float b = (src_channels > 2) ? pix[2] : r; + float a = (src_channels > 3) ? pix[3] : 1.0f; + if (dst_channels == 1) { + float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; + dst_row_u16[x] = static_cast( + std::lround(clamp01(luma) * 65535.0f)); + continue; + } + dst_row_u16[x * dst_channels + 0] = + static_cast( + std::lround(clamp01(r) * 65535.0f)); + dst_row_u16[x * dst_channels + 1] = + static_cast( + std::lround(clamp01(g) * 65535.0f)); + dst_row_u16[x * dst_channels + 2] = + static_cast( + std::lround(clamp01(b) * 65535.0f)); + if (dst_channels == 4) { + dst_row_u16[x * dst_channels + 3] = + static_cast( + std::lround(clamp01(a) * 65535.0f)); + } + } + } else { + for (int x = 0; x < src->width; ++x) { + const float *pix = src_row + x * src_channels; + float r = pix[0]; + float g = (src_channels > 1) ? pix[1] : r; + float b = (src_channels > 2) ? pix[2] : r; + float a = (src_channels > 3) ? pix[3] : 1.0f; + if (dst_channels == 1) { + float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; + dst_row[x] = static_cast( + std::lround(clamp01(luma) * 255.0f)); + continue; + } + dst_row[x * dst_channels + 0] = + static_cast( + std::lround(clamp01(r) * 255.0f)); + dst_row[x * dst_channels + 1] = + static_cast( + std::lround(clamp01(g) * 255.0f)); + dst_row[x * dst_channels + 2] = + static_cast( + std::lround(clamp01(b) * 255.0f)); + if (dst_channels == 4) { + dst_row[x * dst_channels + 3] = + static_cast( + std::lround(clamp01(a) * 255.0f)); + } + } + } + } + + return dst; +} } const std::string &olive::plugin::OliveClipInstance::getUnmappedBitDepth() const @@ -125,14 +360,29 @@ const std::string &olive::plugin::OliveClipInstance::getFieldOrder() const bool olive::plugin::OliveClipInstance::getConnected() const { if (name_ == kOfxImageEffectOutputClipName) { - return true; +#ifdef OFX_SUPPORTS_OPENGLRENDER + if (!output_textures_.isEmpty()) { + return true; + } +#endif + for (auto it = images_.cbegin(); it != images_.cend(); ++it) { + if (it.value()) { + return true; + } + } + return false; } #ifdef OFX_SUPPORTS_OPENGLRENDER if (!input_textures_.isEmpty()) { return true; } #endif - return !images_.isEmpty(); + for (auto it = images_.cbegin(); it != images_.cend(); ++it) { + if (it.value()) { + return true; + } + } + return false; } double olive::plugin::OliveClipInstance::getUnmappedFrameRate() const { @@ -227,7 +477,7 @@ OfxRectD olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const { if (regionOfDefinitions_.contains(time)) { - return regionOfDefinitions_[time]; + return regionOfDefinitions_.value(time); } OfxRectD regionOfDefinition; regionOfDefinition.x1 = regionOfDefinition.y1 = 0; @@ -251,6 +501,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi return; } VideoParams incoming = texture->params(); + // AI wrote this. Why? if (params_.format() != PixelFormat::INVALID && params_.channel_count() > 0) { incoming.set_format(params_.format()); @@ -264,7 +515,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi AVFramePtr frame = texture->frame(); if (!frame || !frame->data[0]) { - return; + frame = ReadbackTextureToFrame(texture, params_); } AVPixelFormat expected_fmt = FFmpegUtils::GetFFmpegPixelFormat(params_.format(), @@ -295,10 +546,23 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi return; } + if (!frame || !frame->data[0]) { + std::memset(dst, 0, image->row_bytes() * image->height()); + return; + } + AVFramePtr src_frame = frame; if (frame->format != expected_fmt || frame->width != params_.width() || frame->height != params_.height()) { + if (PackedFloatChannels(static_cast(frame->format)) > 0) { + AVFramePtr converted = + ConvertPackedFloatFrame(frame, expected_fmt); + if (converted) { + src_frame = converted; + goto copy_pixels; + } + } AVFramePtr converted = CreateAVFramePtr(); converted->format = expected_fmt; converted->width = params_.width(); @@ -324,6 +588,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi src_frame = converted; } +copy_pixels: int bytes_per_component = params_.format().byte_count(); int bytes_per_row = params_.width() * params_.channel_count() * bytes_per_component; @@ -340,7 +605,7 @@ void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTi } } -void olive::plugin::OliveClipInstance::setOutputTexture(Texture *texture, +void olive::plugin::OliveClipInstance::setOutputTexture(TexturePtr texture, OfxTime time) { #ifdef OFX_SUPPORTS_OPENGLRENDER @@ -361,12 +626,12 @@ olive::plugin::OliveClipInstance::loadTexture(OfxTime time, const char *format, { (void)format; - Texture *gl_texture = nullptr; + TexturePtr 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; + gl_texture = input ? input : nullptr; } if (!gl_texture || gl_texture->IsDummy() || !gl_texture->id().isValid()) { @@ -394,7 +659,7 @@ olive::plugin::OliveClipInstance::loadTexture(OfxTime time, const char *format, 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)) + "_" + + reinterpret_cast(gl_texture.get())) + "_" + std::to_string(static_cast(time)); const int texture_id = gl_texture->id().value(); diff --git a/app/pluginSupport/OliveClip.h b/app/pluginSupport/OliveClip.h index 2c47f4295..357a1bd32 100644 --- a/app/pluginSupport/OliveClip.h +++ b/app/pluginSupport/OliveClip.h @@ -73,7 +73,7 @@ public: # endif void setInputTexture(TexturePtr texture, OfxTime time); - void setOutputTexture(Texture *texture, OfxTime time); + void setOutputTexture(TexturePtr texture, OfxTime time); private: VideoParams params_; @@ -86,7 +86,7 @@ private: QMap> images_; #ifdef OFX_SUPPORTS_OPENGLRENDER QMap input_textures_; - QMap output_textures_; + QMap output_textures_; #endif }; } diff --git a/app/pluginSupport/OliveHost.cpp b/app/pluginSupport/OliveHost.cpp index f080ef1ad..570e24448 100644 --- a/app/pluginSupport/OliveHost.cpp +++ b/app/pluginSupport/OliveHost.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -108,14 +109,6 @@ void olive::plugin::loadPlugins(QString path) } OliveHost::~OliveHost() { - for(auto& descriptor:descriptors_){ - delete descriptor; - descriptor=nullptr; - } - for(auto& instance:instances_){ - delete instance; - instance=nullptr; - } } @@ -124,45 +117,37 @@ void OliveHost::destroyInstance(OFX::Host::ImageEffect::Instance* instance) if (!instance) { return; } - if (instances_.contains(instance)) - { - instances_.removeOne(instance); - delete instance; + for (auto it = instances_.begin(); it != instances_.end(); ++it) { + if (it->get() == instance) { + instances_.erase(it); + break; + } } } -ImageEffect::Descriptor * -OliveHost::makeDescriptor(ImageEffect::ImageEffectPlugin* plugin) +std::shared_ptr +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); + std::shared_ptr desc = + std::make_shared(plugin); + descriptors_.append(std::shared_ptr(desc)); return desc; } -ImageEffect::Descriptor * +std::shared_ptr OliveHost::makeDescriptor(const ImageEffect::Descriptor &rootContext, - ImageEffect::ImageEffectPlugin *plugin) + ImageEffect::ImageEffectPlugin *plugin) { - ImageEffect::Descriptor* desc = new ImageEffect::Descriptor(rootContext,plugin); -#ifdef OFX_SUPPORTS_OPENGLRENDER - desc->getProps().setStringProperty(kOfxImageEffectPropOpenGLRenderSupported, - "true"); -#endif - descriptors_.append(desc); + std::shared_ptr desc = + std::make_shared(rootContext, plugin); + descriptors_.append(std::shared_ptr(desc)); return desc; } -ImageEffect::Descriptor * +std::shared_ptr OliveHost::makeDescriptor(const std::string &bundlePath, - ImageEffect::ImageEffectPlugin *plugin) + ImageEffect::ImageEffectPlugin *plugin) { - ImageEffect::Descriptor* desc = new ImageEffect::Descriptor(bundlePath, plugin); -#ifdef OFX_SUPPORTS_OPENGLRENDER - desc->getProps().setStringProperty(kOfxImageEffectPropOpenGLRenderSupported, - "true"); -#endif - descriptors_.append(desc); + std::shared_ptr desc = + std::make_shared(bundlePath, plugin); + descriptors_.append(std::shared_ptr(desc)); return desc; } @@ -177,7 +162,7 @@ ImageEffect::Instance* OliveHost::newInstance(void *clientData, instance->setNode( std::shared_ptr(node, [](PluginNode *) {})); } - instances_.append(instance); + instances_.append(std::shared_ptr(instance)); return instance; }; OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, const char *format, @@ -217,7 +202,7 @@ OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, c return kOfxStatOK; } - +// TODO: Persistent messages shouldn't use pop-up window. OfxStatus olive::plugin::OliveHost::setPersistentMessage( const char *type, const char *id, const char *format, va_list args) { diff --git a/app/pluginSupport/OliveHost.h b/app/pluginSupport/OliveHost.h index ce8615ef2..31adfe056 100644 --- a/app/pluginSupport/OliveHost.h +++ b/app/pluginSupport/OliveHost.h @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace olive { namespace plugin { @@ -70,16 +71,16 @@ public: const std::string& context) override; - OFX::Host::ImageEffect::Descriptor *makeDescriptor( + std::shared_ptr makeDescriptor( OFX::Host::ImageEffect::ImageEffectPlugin* plugin) override; - OFX::Host::ImageEffect::Descriptor *makeDescriptor( - const OFX::Host::ImageEffect::Descriptor &rootContext, - OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override; + std::shared_ptr + makeDescriptor(const OFX::Host::ImageEffect::Descriptor &rootContext, + OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override; - OFX::Host::ImageEffect::Descriptor *makeDescriptor( - const std::string &bundlePath, - OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override; + std::shared_ptr + makeDescriptor(const std::string &bundlePath, + OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override; /// vmessage virtual OfxStatus vmessage(const char *type, const char *id, const char *format, va_list args); @@ -98,8 +99,8 @@ public: }; #endif private: - QList descriptors_; - QList instances_; + QList> descriptors_; + QList> instances_; QList persistent_messages_; }; } diff --git a/app/pluginSupport/OlivePluginInstance.cpp b/app/pluginSupport/OlivePluginInstance.cpp index 3d8edcf86..ac366bb4c 100644 --- a/app/pluginSupport/OlivePluginInstance.cpp +++ b/app/pluginSupport/OlivePluginInstance.cpp @@ -30,11 +30,11 @@ #include "panel/timeline/timeline.h" #include -#include -#include #include -#include -#include +#include +#include +#include +#include #include #include #include @@ -48,6 +48,35 @@ const std::string kImageFieldNoneStr(kOfxImageFieldNone); const std::string kImageFieldUpperStr(kOfxImageFieldUpper); const std::string kImageFieldLowerStr(kOfxImageFieldLower); +QString FormatOfxMessage(const char *format, va_list args) +{ + char buffer[1024]; + va_list args_copy; + va_copy(args_copy, args); + const int needed = vsnprintf(buffer, sizeof(buffer), format, args_copy); + va_end(args_copy); + if (needed < 0) { + return QString(); + } + if (needed < static_cast(sizeof(buffer))) { + return QString::fromUtf8(buffer); + } + QByteArray dynamic_buffer(needed + 1, 0); + const int written = vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args); + if (written < 0) { + return QString(); + } + return QString::fromUtf8(dynamic_buffer.constData()); +} + +bool IsGuiThread() +{ + if (auto *app = QCoreApplication::instance()) { + return QThread::currentThread() == app->thread(); + } + return true; +} + const std::string &FieldOrderForParams(const VideoParams ¶ms) { switch (params.interlacing()) { @@ -134,75 +163,101 @@ const std::string &OlivePluginInstance::getDefaultOutputFielding() const OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id, const char *format, va_list args) { - char *buffer = new char[1024]; - - memset(buffer, 0, 1024 * sizeof(char)); - vsprintf(buffer, format, args); - - QString message(buffer); - - delete[] buffer; - - if (strncmp(type, kOfxMessageQuestion, strlen(kOfxMessageQuestion))) { - auto ret = QMessageBox::information( - nullptr, "", message, QMessageBox::Ok, QMessageBox::Cancel); - - if (ret == QMessageBox::Ok) { - return kOfxStatReplyYes; - } else { - return kOfxStatReplyNo; - } - - } else { - QMessageBox::information(nullptr, "", message); - return kOfxStatOK; + const QString message = FormatOfxMessage(format, args); + if (message.isEmpty()) { + return kOfxStatFailed; } + + const bool is_question = + strncmp(type, kOfxMessageQuestion, strlen(kOfxMessageQuestion)) == 0; + OfxStatus result = kOfxStatOK; + auto show_message = [&]() { + if (is_question) { + const auto ret = QMessageBox::question( + nullptr, "", message, QMessageBox::Ok, QMessageBox::Cancel); + result = (ret == QMessageBox::Ok) ? kOfxStatReplyYes : kOfxStatReplyNo; + } else { + QMessageBox::information(nullptr, "", message); + result = kOfxStatOK; + } + }; + + if (IsGuiThread()) { + show_message(); + } else if (auto *app = QCoreApplication::instance()) { + if (is_question) { + QMetaObject::invokeMethod(app, show_message, Qt::BlockingQueuedConnection); + } else { + QMetaObject::invokeMethod(app, show_message, Qt::QueuedConnection); + } + } else if (is_question) { + result = kOfxStatReplyNo; + } + + return result; } OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char *id, const char *format, va_list args) { - char *buffer = new char[1024]; - - memset(buffer, 0, 1024 * sizeof(char)); - int ret = vsprintf(buffer, format, args); - if (ret < 0) { + const QString message = FormatOfxMessage(format, args); + if (message.isEmpty()) { return kOfxStatFailed; } - QString message(buffer); - - delete[] buffer; + ErrorType error_type; // If This is a error message if (strncmp(type, kOfxMessageError, strlen(kOfxMessageError)) == 0) { - persistentErrors_.append({ ErrorType::Error, message }); - QMessageBox::critical(nullptr, "", message); - // TODO: tell the shell to show the error + error_type = ErrorType::Error; } // A warning - else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageError)) == 0) { - persistentErrors_.append({ ErrorType::Warning, message }); - QMessageBox::warning(nullptr, "", message); - // TODO: tell the shell to show the warning - + else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) == 0) { + error_type = ErrorType::Warning; } // A simple information - else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageError)) == 0) { - persistentErrors_.append({ ErrorType::Message, message }); - QMessageBox::information(nullptr, "", message); + else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) == 0) { + error_type = ErrorType::Message; } else { return kOfxStatFailed; } - if (node_) { - emit node_->MessageCountChanged(); + + auto update_ui = [this, error_type, message]() { + persistentErrors_.append({ error_type, message }); + switch (error_type) { + case ErrorType::Error: + QMessageBox::critical(nullptr, "", message); + break; + case ErrorType::Warning: + QMessageBox::warning(nullptr, "", message); + break; + case ErrorType::Message: + QMessageBox::information(nullptr, "", message); + break; + } + if (node_) { + emit node_->MessageCountChanged(); + } + }; + + if (IsGuiThread()) { + update_ui(); + } else if (auto *app = QCoreApplication::instance()) { + QMetaObject::invokeMethod(app, update_ui, Qt::QueuedConnection); } return kOfxStatOK; } OfxStatus OlivePluginInstance::clearPersistentMessage() { - persistentErrors_.clear(); - // TODO: tell the shell to remove message. - if (node_) { - emit node_->MessageCountChanged(); + auto clear_ui = [this]() { + persistentErrors_.clear(); + // TODO: tell the shell to remove message. + if (node_) { + emit node_->MessageCountChanged(); + } + }; + if (IsGuiThread()) { + clear_ui(); + } else if (auto *app = QCoreApplication::instance()) { + QMetaObject::invokeMethod(app, clear_ui, Qt::QueuedConnection); } return kOfxStatOK; } diff --git a/app/pluginSupport/paraminstance.h b/app/pluginSupport/paraminstance.h index d7412debf..8450e3db2 100644 --- a/app/pluginSupport/paraminstance.h +++ b/app/pluginSupport/paraminstance.h @@ -65,8 +65,10 @@ protected: int value_ = 0; public: IntegerInstance(std::shared_ptrnode, OFX::Host::Param::Descriptor &descriptor) - : _node(node), _descriptor(descriptor), - OFX::Host::Param::IntegerInstance(_descriptor){} + : OFX::Host::Param::IntegerInstance(descriptor) + , _node(node) + , _descriptor(descriptor) + {} OfxStatus get(int &a) { if (!_node) { diff --git a/app/render/job/pluginjob.h b/app/render/job/pluginjob.h index 3b96ab004..28831779b 100644 --- a/app/render/job/pluginjob.h +++ b/app/render/job/pluginjob.h @@ -21,6 +21,7 @@ #define PLUGINJOB_H #include "acceleratedjob.h" #include "pluginSupport/OlivePluginInstance.h" +#include "olive/core/util/rational.h" #include #include @@ -31,13 +32,20 @@ namespace plugin { class PluginJob :public AcceleratedJob{ public: explicit PluginJob(const OFX::Host::ImageEffect::Instance* pluginInstance, - const PluginNode* node, NodeValueRow row) + const PluginNode* node, NodeValueRow row, + const olive::core::rational &time) : AcceleratedJob() + , time_seconds_(time.toDouble()) { this->pluginInstance_ = pluginInstance; this->node_=node; Insert(row); } + explicit PluginJob(const OFX::Host::ImageEffect::Instance* pluginInstance, + const PluginNode* node, NodeValueRow row) + : PluginJob(pluginInstance, node, row, olive::core::rational(0)) + { + } PluginNode *node() const { return const_cast(node_); @@ -47,6 +55,10 @@ public: return const_cast(pluginInstance_); } + double time_seconds() const { + return time_seconds_; + } + private: const OFX::Host::ImageEffect::Instance *pluginInstance_=nullptr; @@ -55,6 +67,7 @@ private: QHash params; const PluginNode *node_=nullptr; + double time_seconds_ = 0.0; }; } // plugin diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 304b1bd1f..255d01719 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -151,8 +151,9 @@ void OpenGLRenderer::DestroyInternal() if (context_) { GL_PREAMBLE; - // Delete framebuffer - functions_->glDeleteFramebuffers(1, &framebuffer_); + if (functions_ && framebuffer_) { + functions_->glDeleteFramebuffers(1, &framebuffer_); + } framebuffer_ = 0; // Delete context if it belongs to us @@ -160,6 +161,7 @@ void OpenGLRenderer::DestroyInternal() delete context_; } context_ = nullptr; + functions_ = nullptr; } } @@ -335,11 +337,28 @@ void OpenGLRenderer::DownloadFromTexture(const QVariant &id, { GL_PREAMBLE; + if (!EnsureContextCurrent(__FUNCTION__)) { + return; + } + + GLuint texture_id = id.value(); + if (!texture_id || !functions_->glIsTexture(texture_id)) { + qWarning() << "DownloadFromTexture called with invalid texture"; + return; + } + GLint current_tex; functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex); AttachTextureAsDestination(id); + GLenum status = functions_->glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qWarning() << "DownloadFromTexture framebuffer incomplete" << status; + DetachTextureAsDestination(); + return; + } + functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize); { @@ -921,4 +940,39 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code) return shader; } +bool OpenGLRenderer::EnsureContextCurrent(const char *caller) +{ + if (!context_) { + qWarning() << caller << "called without an OpenGL context"; + return false; + } + + if (QOpenGLContext::currentContext() != context_) { + if (context_->parent() == this && surface_.isValid()) { + if (!context_->makeCurrent(&surface_)) { + qWarning() << caller << "failed to make context current"; + return false; + } + } else { + qWarning() << caller << "OpenGL context not current"; + return false; + } + } + + if (!functions_) { + functions_ = context_->functions(); + } + + if (!functions_) { + qWarning() << caller << "OpenGL functions not available"; + return false; + } + + if (!framebuffer_) { + functions_->glGenFramebuffers(1, &framebuffer_); + } + + return true; +} + } diff --git a/app/render/opengl/openglrenderer.h b/app/render/opengl/openglrenderer.h index 2b355835c..cec990eed 100644 --- a/app/render/opengl/openglrenderer.h +++ b/app/render/opengl/openglrenderer.h @@ -99,6 +99,8 @@ private: static GLenum GetPixelFormat(int channel_count); + bool EnsureContextCurrent(const char *caller); + void PrepareInputTexture(GLenum target, Texture::Interpolation interp); void ClearDestinationInternal(double r = 0.0, double g = 0.0, diff --git a/app/render/plugin/pluginrenderer.cpp b/app/render/plugin/pluginrenderer.cpp index d86eab963..53a807745 100644 --- a/app/render/plugin/pluginrenderer.cpp +++ b/app/render/plugin/pluginrenderer.cpp @@ -20,6 +20,9 @@ // // Created by mikesolar on 25-10-19. // +#include "render/texture.h" +#include +#include #include #include #include @@ -31,6 +34,7 @@ #include "common/ffmpegutils.h" #include "ofxImageEffect.h" #include "ofxhUtilities.h" +#include "ofxGPURender.h" extern "C"{ #include #include @@ -280,7 +284,7 @@ static const char *GetRenderFieldForParams(const olive::VideoParams ¶ms) return kOfxImageFieldNone; } -static olive::AVFramePtr ReadbackTextureToFrame(olive::Texture *texture, +static olive::AVFramePtr ReadbackTextureToFrame(olive::TexturePtr texture, const olive::VideoParams ¶ms) { if (!texture || texture->IsDummy()) { @@ -307,8 +311,11 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::Texture *texture, } if (texture->renderer()) { + const int linesize_pixels = + olive::plugin::detail::BytesToPixels(frame->linesize[0], + params); texture->renderer()->DownloadFromTexture( - texture->id(), params, frame->data[0], frame->linesize[0]); + texture->id(), params, frame->data[0], linesize_pixels); } return frame; } @@ -327,9 +334,11 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::Texture *texture, } 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], - rgba_frame->linesize[0]); + texture->id(), rgba_params, rgba_frame->data[0], linesize_pixels); } olive::AVFramePtr dst = olive::CreateAVFramePtr(); @@ -356,6 +365,18 @@ static olive::AVFramePtr ReadbackTextureToFrame(olive::Texture *texture, return dst; } +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; +} + static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, const olive::VideoParams &dst_params) { @@ -382,6 +403,240 @@ static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src, return src; } + auto float_channels = [](AVPixelFormat fmt) -> int { + switch (fmt) { + case AV_PIX_FMT_GRAYF32LE: + case AV_PIX_FMT_GRAYF32BE: + return 1; + case AV_PIX_FMT_RGBF32LE: + case AV_PIX_FMT_RGBF32BE: + return 3; + case AV_PIX_FMT_RGBAF32LE: + case AV_PIX_FMT_RGBAF32BE: + return 4; + default: + return 0; + } + }; + + auto dst_packed_info = [](AVPixelFormat fmt, int *channels, + int *bytes_per_component) -> bool { + switch (fmt) { + case AV_PIX_FMT_GRAY8: + *channels = 1; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_RGB24: + *channels = 3; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_RGBA: + *channels = 4; + *bytes_per_component = 1; + return true; + case AV_PIX_FMT_GRAY16LE: + *channels = 1; + *bytes_per_component = 2; + return true; + case AV_PIX_FMT_RGB48LE: + *channels = 3; + *bytes_per_component = 2; + return true; + case AV_PIX_FMT_RGBA64LE: + *channels = 4; + *bytes_per_component = 2; + return true; + default: + return false; + } + }; + + auto float_dst_from_packed = [&](const olive::AVFramePtr &packed_src, + AVPixelFormat float_fmt, + const olive::AVFramePtr &float_dst) -> bool { + if (!packed_src || !float_dst || !packed_src->data[0] || + !float_dst->data[0]) { + return false; + } + const int dst_channels = float_channels(float_fmt); + if (dst_channels == 0) { + return false; + } + int src_channels = 0; + int bytes_per_component = 0; + if (!dst_packed_info(static_cast(packed_src->format), + &src_channels, &bytes_per_component)) { + return false; + } + const float inv_scale = + (bytes_per_component == 2) ? (1.0f / 65535.0f) + : (1.0f / 255.0f); + for (int y = 0; y < packed_src->height; ++y) { + const uint8_t *src_row = + packed_src->data[0] + y * packed_src->linesize[0]; + float *dst_row = reinterpret_cast( + float_dst->data[0] + y * float_dst->linesize[0]); + if (bytes_per_component == 2) { + const uint16_t *src_u16 = + reinterpret_cast(src_row); + for (int x = 0; x < packed_src->width; ++x) { + const uint16_t *pix = src_u16 + x * src_channels; + float r = pix[0] * inv_scale; + float g = (src_channels > 1) ? pix[1] * inv_scale : r; + float b = (src_channels > 2) ? pix[2] * inv_scale : r; + float a = (src_channels > 3) ? pix[3] * inv_scale : 1.0f; + dst_row[x * dst_channels + 0] = r; + if (dst_channels > 1) { + dst_row[x * dst_channels + 1] = g; + } + if (dst_channels > 2) { + dst_row[x * dst_channels + 2] = b; + } + if (dst_channels > 3) { + dst_row[x * dst_channels + 3] = a; + } + } + } else { + for (int x = 0; x < packed_src->width; ++x) { + const uint8_t *pix = src_row + x * src_channels; + float r = pix[0] * inv_scale; + float g = (src_channels > 1) ? pix[1] * inv_scale : r; + float b = (src_channels > 2) ? pix[2] * inv_scale : r; + float a = (src_channels > 3) ? pix[3] * inv_scale : 1.0f; + dst_row[x * dst_channels + 0] = r; + if (dst_channels > 1) { + dst_row[x * dst_channels + 1] = g; + } + if (dst_channels > 2) { + dst_row[x * dst_channels + 2] = b; + } + if (dst_channels > 3) { + dst_row[x * dst_channels + 3] = a; + } + } + } + } + return true; + }; + + const int dst_float_channels = float_channels(dst_fmt); + if (dst_float_channels > 0) { + int src_channels = 0; + int bytes_per_component = 0; + if (dst_packed_info(static_cast(src->format), + &src_channels, &bytes_per_component)) { + if (float_dst_from_packed(src, dst_fmt, dst)) { + return dst; + } + } else { + olive::AVFramePtr packed = olive::CreateAVFramePtr(); + AVPixelFormat packed_fmt = (dst_float_channels == 4) + ? AV_PIX_FMT_RGBA + : (dst_float_channels == 3) + ? AV_PIX_FMT_RGB24 + : AV_PIX_FMT_GRAY8; + packed->format = packed_fmt; + packed->width = dst->width; + packed->height = dst->height; + if (av_frame_get_buffer(packed.get(), 0) >= 0) { + SwsContext *pre_ctx = sws_getContext( + src->width, src->height, + static_cast(src->format), + packed->width, packed->height, packed_fmt, SWS_POINT, + nullptr, nullptr, nullptr); + if (pre_ctx) { + sws_scale(pre_ctx, src->data, src->linesize, 0, src->height, + packed->data, packed->linesize); + sws_freeContext(pre_ctx); + if (float_dst_from_packed(packed, dst_fmt, dst)) { + return dst; + } + } + } + } + } + + auto clamp01 = [](float v) -> float { + return std::clamp(v, 0.0f, 1.0f); + }; + + const int src_float_channels = float_channels( + static_cast(src->format)); + if (src_float_channels > 0) { + int dst_channels = 0; + int bytes_per_component = 0; + if (dst_packed_info(dst_fmt, &dst_channels, &bytes_per_component) && + src->data[0] && dst->data[0]) { + for (int y = 0; y < src->height; ++y) { + const float *src_row = reinterpret_cast( + src->data[0] + y * src->linesize[0]); + uint8_t *dst_row = dst->data[0] + y * dst->linesize[0]; + if (bytes_per_component == 2) { + auto *dst_row_u16 = + reinterpret_cast(dst_row); + for (int x = 0; x < src->width; ++x) { + const float *pix = + src_row + x * src_float_channels; + float r = pix[0]; + float g = (src_float_channels > 1) ? pix[1] : r; + float b = (src_float_channels > 2) ? pix[2] : r; + float a = (src_float_channels > 3) ? pix[3] : 1.0f; + if (dst_channels == 1) { + float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; + dst_row_u16[x] = static_cast( + std::lround(clamp01(luma) * 65535.0f)); + continue; + } + dst_row_u16[x * dst_channels + 0] = + static_cast( + std::lround(clamp01(r) * 65535.0f)); + dst_row_u16[x * dst_channels + 1] = + static_cast( + std::lround(clamp01(g) * 65535.0f)); + dst_row_u16[x * dst_channels + 2] = + static_cast( + std::lround(clamp01(b) * 65535.0f)); + if (dst_channels == 4) { + dst_row_u16[x * dst_channels + 3] = + static_cast( + std::lround(clamp01(a) * 65535.0f)); + } + } + } else { + for (int x = 0; x < src->width; ++x) { + const float *pix = + src_row + x * src_float_channels; + float r = pix[0]; + float g = (src_float_channels > 1) ? pix[1] : r; + float b = (src_float_channels > 2) ? pix[2] : r; + float a = (src_float_channels > 3) ? pix[3] : 1.0f; + if (dst_channels == 1) { + float luma = 0.2126f * r + 0.7152f * g + 0.0722f * b; + dst_row[x] = static_cast( + std::lround(clamp01(luma) * 255.0f)); + continue; + } + dst_row[x * dst_channels + 0] = + static_cast( + std::lround(clamp01(r) * 255.0f)); + dst_row[x * dst_channels + 1] = + static_cast( + std::lround(clamp01(g) * 255.0f)); + dst_row[x * dst_channels + 2] = + static_cast( + std::lround(clamp01(b) * 255.0f)); + if (dst_channels == 4) { + dst_row[x * dst_channels + 3] = + static_cast( + std::lround(clamp01(a) * 255.0f)); + } + } + } + } + return dst; + } + } + SwsContext *sws_ctx = sws_getContext( src->width, src->height, static_cast(src->format), dst->width, dst->height, dst_fmt, SWS_POINT, @@ -407,6 +662,42 @@ static int LinesizeToPixels(const olive::VideoParams ¶ms, int linesize_bytes return linesize_bytes / bytes_per_pixel; } +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; + } + + olive::AVFramePtr frame = src->frame(); + if (!frame || !frame->data[0]) { + frame = ReadbackTextureToFrame(src, src_params); + } + if (!frame || !frame->data[0]) { + return src; + } + + olive::AVFramePtr converted = ConvertFrameIfNeeded(frame, dst_params); + if (!converted || !converted->data[0]) { + return src; + } + + auto 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); + return dst; +} + static QString PluginIdForInstance(const OFX::Host::ImageEffect::Instance *instance) { if (!instance) { @@ -432,15 +723,15 @@ static void LogOfxFailure(const char *action, OfxStatus stat, << "(" << stat << ")"; } -static void MarkRenderFailure(olive::Texture *destination) +static void MarkRenderFailure(olive::TexturePtr destination) { if (destination && destination->renderer()) { - destination->renderer()->ClearDestination(destination, 1.0, 0.0, 1.0, 1.0); + destination->renderer()->ClearDestination(destination.get(), 1.0, 0.0, 1.0, 1.0); } } void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job, - olive::Texture *destination, + olive::TexturePtr destination, olive::VideoParams destination_params, bool clear_destination, bool interactive) { @@ -448,10 +739,18 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: if (!instance) { return; } + 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 = - destination && destination->renderer() && destination->id().isValid(); + supports_opengl && destination && destination->renderer() && + destination->id().isValid(); if (olive_instance) { olive_instance->setOpenGLEnabled(use_opengl); olive_instance->setVideoParam(destination_params); @@ -501,7 +800,7 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: } // call get region of interest on each of the inputs - OfxTime frame = 0; + OfxTime frame = job.time_seconds(); const NodeValueRow &values = job.GetValues(); const auto &clips = instance->getDescriptor().getClips(); @@ -523,7 +822,6 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: } if (input_tex) { input_textures[entry.first] = input_tex; - input_clip->setInputTexture(input_tex, frame); } } @@ -538,6 +836,8 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: return; } + + olive::VideoParams output_params = destination_params; if (ApplyClipPreferencesToParams(*clip, &output_params)) { clip->setParams(output_params); @@ -551,15 +851,27 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: } olive::VideoParams input_params = entry.second->params(); if (ApplyClipPreferencesToParams(*input_clip, &input_params)) { + /* + input_params.set_width(entry.second->params().width()); + input_params.set_height(entry.second->params().height()); + input_params.set_depth(entry.second->params().depth()); + input_params.set_pixel_aspect_ratio( + entry.second->params().pixel_aspect_ratio()); + input_params.set_interlacing(entry.second->params().interlacing()); + input_params.set_premultiplied_alpha( + entry.second->params().premultiplied_alpha()); + input_params.set_divider(entry.second->params().divider());*/ input_clip->setParams(input_params); } - input_clip->setInputTexture(entry.second, frame); + olive::TexturePtr converted = + ConvertTextureForParams(entry.second, input_params); + input_clip->setInputTexture(converted, frame); } clip->setRegionOfDefinition(regionOfDefinition, frame); clip->setOutputTexture(destination, frame); - stat = instance->beginRenderAction(0, numFramesToRender, + stat = instance->beginRenderAction(frame, numFramesToRender, 1.0, false, renderScale, true, interactive); if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { @@ -589,14 +901,24 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: return; } + 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; + } + // render a frame const char *render_field = GetRenderFieldForParams(destination_params); - stat = instance->renderAction(0, render_field, renderWindow, renderScale, + stat = instance->renderAction(frame, render_field, renderWindow, renderScale, true, interactive, interactive); if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) { LogOfxFailure("render", stat, instance); MarkRenderFailure(destination); - instance->endRenderAction(0, numFramesToRender, 1.0, interactive, + instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true, interactive); return; } @@ -636,19 +958,27 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: qWarning().noquote() << "OFX output image conversion failed for plugin=" << PluginIdForInstance(instance); - instance->endRenderAction(0, numFramesToRender, 1.0, interactive, - renderScale, true, interactive); + instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, + renderScale, true, interactive); return; } AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params); + const AVPixelFormat expected_fmt = + GetDestinationAVPixelFormat(destination_params); destination->handleFrame(converted); - if (destination->renderer() && converted && converted->data[0]) { + 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 { AVFramePtr frame_ptr = @@ -669,7 +999,7 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin:: } -void olive::plugin::PluginRenderer::AttachOutputTexture(olive::Texture *texture) +void olive::plugin::PluginRenderer::AttachOutputTexture(olive::TexturePtr texture) { if (!texture) { return; diff --git a/app/render/plugin/pluginrenderer.h b/app/render/plugin/pluginrenderer.h index 832b1a825..033ce26cd 100644 --- a/app/render/plugin/pluginrenderer.h +++ b/app/render/plugin/pluginrenderer.h @@ -37,15 +37,18 @@ namespace olive { namespace plugin{ +namespace detail { +int BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms); +} class PluginRenderer : public olive::OpenGLRenderer{ Q_OBJECT public: PluginRenderer(QObject *parent=nullptr):OpenGLRenderer(parent){}; virtual ~PluginRenderer() override{}; - void AttachOutputTexture(olive::Texture *texture); + void AttachOutputTexture(olive::TexturePtr texture); void DetachOutputTexture(); void RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job, - olive::Texture *destination, + olive::TexturePtr destination, olive::VideoParams destination_params, bool clear_destination, bool interactive); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 10a298344..20d451b11 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -638,7 +638,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture, plugin_renderer_->RenderPlugin( texture, *plugin_job, - destination.get(), + destination, destination->params(), true, false); diff --git a/app/widget/standardcombos/channellayoutcombobox.h b/app/widget/standardcombos/channellayoutcombobox.h index 509cfb185..380034774 100644 --- a/app/widget/standardcombos/channellayoutcombobox.h +++ b/app/widget/standardcombos/channellayoutcombobox.h @@ -59,16 +59,7 @@ public: } } } - void SetChannelLayout(AVChannelLayout &ch) - { - for (int i = 0; i < this->count(); i++) { - if (this->itemData(i).toULongLong() == ch.u.mask) { - this->setCurrentIndex(i); - break; - } - } - } - void SetChannelLayout(AVChannelLayout &&ch) + void SetChannelLayout(const AVChannelLayout &ch) { for (int i = 0; i < this->count(); i++) { if (this->itemData(i).toULongLong() == ch.u.mask) { diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 4eaf2db9d..d1486c695 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -18,7 +18,10 @@ add_executable(olive-gtest plugin_support_test.cpp plugin_support_image_test.cpp plugin_support_clip_test.cpp + plugin_support_param_test.cpp + opengl_readback_guard_test.cpp plugin_render_pipeline_test.cpp + plugin_renderer_readback_test.cpp plugin_ofx_integration_test.cpp codec_frame_test.cpp codec_exportcodec_test.cpp diff --git a/tests/gtest/opengl_readback_guard_test.cpp b/tests/gtest/opengl_readback_guard_test.cpp new file mode 100644 index 000000000..bf4e41df8 --- /dev/null +++ b/tests/gtest/opengl_readback_guard_test.cpp @@ -0,0 +1,26 @@ +#include + +#include +#include + +#include "render/opengl/openglrenderer.h" + +TEST(OpenGLRenderer, DownloadFromTextureWithoutCurrentContext) +{ + QOpenGLContext context; + ASSERT_TRUE(context.create()); + ASSERT_EQ(QOpenGLContext::currentContext(), nullptr); + + olive::OpenGLRenderer renderer; + renderer.Init(&context); + + olive::VideoParams params(4, 4, olive::core::PixelFormat::U8, 4, + olive::core::rational(1, 1), + olive::VideoParams::kInterlaceNone, 1); + + unsigned char buffer[4 * 4 * 4] = {}; + renderer.DownloadFromTexture(QVariant::fromValue(0), params, + buffer, 4 * 4); + + EXPECT_EQ(QOpenGLContext::currentContext(), nullptr); +} diff --git a/tests/gtest/plugin_ofx_integration_test.cpp b/tests/gtest/plugin_ofx_integration_test.cpp index 4ceb00a6e..25e2bbf56 100644 --- a/tests/gtest/plugin_ofx_integration_test.cpp +++ b/tests/gtest/plugin_ofx_integration_test.cpp @@ -114,7 +114,7 @@ TEST(PluginIntegration, ChromaKeyerCreateAndRender) olive::TexturePtr output = std::make_shared(params); olive::plugin::PluginRenderer renderer; - renderer.RenderPlugin(input, job, output.get(), params, true, false); + renderer.RenderPlugin(input, job, output, params, true, false); EXPECT_TRUE(output->frame()); } diff --git a/tests/gtest/plugin_renderer_readback_test.cpp b/tests/gtest/plugin_renderer_readback_test.cpp new file mode 100644 index 000000000..054811942 --- /dev/null +++ b/tests/gtest/plugin_renderer_readback_test.cpp @@ -0,0 +1,19 @@ +#include + +#include "render/plugin/pluginrenderer.h" + +TEST(PluginRendererReadback, BytesToPixels) +{ + olive::VideoParams params(16, 16, olive::core::PixelFormat::U8, 4, + olive::core::rational(1, 1), + olive::VideoParams::kInterlaceNone, 1); + + const int bytes_per_pixel = + olive::VideoParams::GetBytesPerPixel(params.format(), + params.channel_count()); + ASSERT_EQ(bytes_per_pixel, 4); + + EXPECT_EQ(olive::plugin::detail::BytesToPixels(64, params), 16); + EXPECT_EQ(olive::plugin::detail::BytesToPixels(0, params), 0); + EXPECT_EQ(olive::plugin::detail::BytesToPixels(-1, params), 0); +} diff --git a/tests/gtest/plugin_support_param_test.cpp b/tests/gtest/plugin_support_param_test.cpp new file mode 100644 index 000000000..d784a1462 --- /dev/null +++ b/tests/gtest/plugin_support_param_test.cpp @@ -0,0 +1,24 @@ +#include + +#include "ofxParam.h" +#include "ofxhParam.h" +#include "pluginSupport/paraminstance.h" + +TEST(PluginSupportParam, IntegerInstanceNullNodeRoundTrip) +{ + OFX::Host::Param::Descriptor descriptor(kOfxParamTypeInteger, + "TestInteger"); + olive::plugin::IntegerInstance instance(nullptr, descriptor); + + int value = -1; + EXPECT_EQ(instance.get(value), kOfxStatOK); + EXPECT_EQ(value, 0); + + EXPECT_EQ(instance.set(7), kOfxStatOK); + EXPECT_EQ(instance.get(value), kOfxStatOK); + EXPECT_EQ(value, 7); + + int time_value = -1; + EXPECT_EQ(instance.get(1.0, time_value), kOfxStatOK); + EXPECT_EQ(time_value, 7); +} diff --git a/third_party/openfx/HostSupport/include/ofxhImageEffect.h b/third_party/openfx/HostSupport/include/ofxhImageEffect.h index f94b1e2f1..167e768ed 100755 --- a/third_party/openfx/HostSupport/include/ofxhImageEffect.h +++ b/third_party/openfx/HostSupport/include/ofxhImageEffect.h @@ -78,13 +78,13 @@ namespace OFX { virtual bool pluginSupported(ImageEffectPlugin *plugin, std::string &reason) const; /// Override this to create a descriptor, this makes the 'root' descriptor - virtual Descriptor *makeDescriptor(ImageEffectPlugin* plugin) = 0; + virtual std::shared_ptr makeDescriptor(ImageEffectPlugin* plugin) = 0; /// used to construct a context description, rootContext is the main context - virtual Descriptor *makeDescriptor(const Descriptor &rootContext, ImageEffectPlugin *plug) = 0; + virtual std::shared_ptr makeDescriptor(const Descriptor &rootContext, ImageEffectPlugin *plug) = 0; /// used to construct populate the cache - virtual Descriptor *makeDescriptor(const std::string &bundlePath, ImageEffectPlugin *plug) = 0; + virtual std::shared_ptr makeDescriptor(const std::string &bundlePath, ImageEffectPlugin *plug) = 0; /// Override this to initialise an image effect descriptor after it has been /// created. diff --git a/third_party/openfx/HostSupport/include/ofxhImageEffectAPI.h b/third_party/openfx/HostSupport/include/ofxhImageEffectAPI.h index e5b593fdc..638b46d1c 100644 --- a/third_party/openfx/HostSupport/include/ofxhImageEffectAPI.h +++ b/third_party/openfx/HostSupport/include/ofxhImageEffectAPI.h @@ -34,10 +34,10 @@ // this comes off Descriptor's property set after a describe // context independent - Descriptor *_baseDescriptor; /// NEEDS TO BE MADE WITH A FACTORY FUNCTION ON THE HOST!!!!!! + std::shared_ptr _baseDescriptor; /// NEEDS TO BE MADE WITH A FACTORY FUNCTION ON THE HOST!!!!!! /// map to store contexts in - std::map> _contexts; + std::map> _contexts; mutable std::set _knownContexts; mutable bool _madeKnownContexts; @@ -75,7 +75,7 @@ Descriptor *getContext(const std::string &context); void addContext(const std::string &context); - void addContext(const std::string &context, std::unique_ptr ied); + void addContext(const std::string &context, std::shared_ptr ied); virtual void saveXML(std::ostream &os); diff --git a/third_party/openfx/HostSupport/src/ofxhImageEffectAPI.cpp b/third_party/openfx/HostSupport/src/ofxhImageEffectAPI.cpp index 9ce803c81..a402770fd 100755 --- a/third_party/openfx/HostSupport/src/ofxhImageEffectAPI.cpp +++ b/third_party/openfx/HostSupport/src/ofxhImageEffectAPI.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -83,7 +84,6 @@ namespace OFX { } CatchAllSetStatus(stat, gImageEffectHost, op, kOfxActionUnload); (void)stat; } - delete _baseDescriptor; } APICache::PluginAPICacheI &ImageEffectPlugin::getApiHandler() @@ -102,9 +102,9 @@ namespace OFX { return *_baseDescriptor; } - void ImageEffectPlugin::addContext(const std::string &context, std::unique_ptr ied) + void ImageEffectPlugin::addContext(const std::string &context, std::shared_ptr ied) { - _contexts[context] = std::move(ied); + _contexts[context] = ied; _knownContexts.insert(context); _madeKnownContexts = true; } @@ -190,7 +190,7 @@ namespace OFX { Descriptor *ImageEffectPlugin::getContext(const std::string &context) { - std::map>::iterator it = _contexts.find(context); + std::map>::iterator it = _contexts.find(context); if (it != _contexts.end()) { //printf("found context description.\n"); @@ -214,7 +214,7 @@ namespace OFX { if (!ph) { return nullptr; } - std::unique_ptr newContext( gImageEffectHost->makeDescriptor(getDescriptor(), this)); + std::shared_ptr newContext( gImageEffectHost->makeDescriptor(getDescriptor(), this)); OfxStatus stat; try { @@ -463,9 +463,9 @@ namespace OFX { } if (el == "context") { - std::unique_ptr newContext(gImageEffectHost->makeDescriptor(_currentPlugin->getBinary()->getBundlePath(), _currentPlugin)); + std::shared_ptr newContext = gImageEffectHost->makeDescriptor(_currentPlugin->getBinary()->getBundlePath(), _currentPlugin); _currentContext = newContext.get(); - _currentPlugin->addContext(map["name"], std::move(newContext)); + _currentPlugin->addContext(map["name"], newContext); return; }