OFX (別名OFX 画像処理 API)は、2D 視覚効果とビデオ合成を作成するためのオープン スタンダードです。これは、プラグインのようなアプリケーション開発モデルで動作します。基本的に、これはホスト (メソッドのセットを提供するアプリケーション) とプラグイン (このセットを実装するアプリケーションまたはモジュール) の両方として機能します。
この構成により、ホスト アプリケーションの機能を無制限に拡張できる可能性が得られます。
DaVinci Resolveとメタル
Final Cut X や DaVinci Resolve Studio などのアプリケーションは、バージョン 16 以降、Apple Metal パイプラインを完全にサポートします。 OpenCL や Cuda と同様に、OFX の場合も、プラットフォーム固有のコマンド キューの記述子またはハンドラーを取得できます。ホスト システムは、そのようなキューのプールを割り当て、それらのキューで計算のバランスをとる責任も負います。
さらに、ソースおよびターゲットのイメージ クリップ データを GPU メモリに配置するため、拡張可能な機能の開発が大幅に簡素化されます。
Resolve での OFX バージョンのサポート
Resolve では、状況が少し複雑になります。 DaVinci は、いくつかの制限はありますが、OFX v1.4 のサポートを発表しました。特に、インターフェイス関数を操作するための一部のメソッドは使用できません。どの方法が利用可能であるかを判断するために、OFX ではキー/値クエリを通じてサポートされているスイートを調べることができます。
プラグイン コード内のパブリッシュ メソッドはC 呼び出しに基づいています。ただし、C++17 に適合した OpenFXS C++ シェルを使用します。便宜上、すべてを 1 つのリポジトリ (オープンソースDehancer プロジェクトから取得したdehancer-external)にコンパイルしました。
OFXSのコンセプト
このプロジェクトでは、OpenFX の C++ 拡張機能である OpenFXS を使用します。OpenFXS は、もともとBruno Nicolettiによって作成され、商用およびオープンソースのビデオ処理プロジェクトで徐々に普及してきました。
オリジナルのOpenFXS は最新の C++ 方言に適合していなかったので、 C++17と互換性を持たせるために更新しました。
OFX、つまり OFXS は、ホスト プログラムによって動的にロードされるスタンドアロン ソフトウェア モジュールです。基本的に、これはメイン アプリケーションの起動時にロードされる動的ライブラリです。 OpenFXS は、OFX と同様にメソッド シグネチャを公開する必要があります。したがって、コードから 1 つの C メソッドを使用します。
OpenFXS で開発を開始するには、アプリケーションで新しい機能を作成するために使用されるいくつかの共通のクラス セットに同意する必要があります。通常、新しいプロジェクトでは、これらのクラスを継承し、いくつかの仮想メソッドを実装またはオーバーライドする必要があります。
ホスト システム上で独自のプラグインを作成するには、まず、次のパブリック クラスと同じメソッドについて理解しましょう。
- OFX::PluginFactoryHelper は、プラグインのデータ構造スイートとコントロール パネルを作成するための基本テンプレートです (ただし、空のままにすることもできます)。継承されたクラスは、ホスト システムにパラメータとプリセットのセットを登録するシングルトン オブジェクトを作成します。開発者はこれを使用してモジュールを登録します。
- OFX::ParamSetDescriptor - 構造プロパティを作成および保存するための基本コンテナ クラス。
- OFX::ImageEffectDescriptor - データ処理プロシージャを呼び出すときにグラフィック データを操作するときに使用されるプロパティのコンテナー。処理パラメータのコンテキストを内部データベースに保存し、各インスタンスに定義されたプラグイン プロパティを操作するためにホスト アプリケーションによって使用されます。
- OFX::ParamSet - 登録されたデータ構造を操作できるようにする一連の設定。
- OFX::ImageEffect - OFX::ParamSet から継承された、グラフィック データに対するエフェクトの設定セット。
- OFX::MultiThread::Processor - 子クラスでは、画像またはビデオなどのデータ ストリーム処理を実装する必要があります。
- OFX::Plugin::getPluginIDs - ホスト アプリケーションにプラグイン (ファクトリー) を登録するメソッド。
偽色
ビデオ撮影のプロセスを単に写真に画像をキャプチャすることと区別する特徴の 1 つは、シーン全体と画像内の領域の両方のシーンと照明が動的に変化することです。これにより、撮影プロセス中に露出を制御する方法が決まります。
デジタル ビデオには、オペレーター向けの制御モニター モードがあり、エリアの露出レベルが、それぞれが独自の色で着色された限定されたゾーンのセットにマッピングされます。
このモードは、「プレデター」またはフォールス カラー モードと呼ばれることもあります。通常、スケールは IRE スケールを参照します。
このようなモニターを使用すると、露出ゾーンを確認し、カメラの撮影パラメータを設定する際の重大な間違いを避けることができます。写真で露出するときにも同様の意味が使用されます。たとえば、アダムズによればゾーニングです。
露出計で特定のターゲットを測定し、それがどのゾーンに位置するかを確認できます。ゾーンは、認識しやすいようにきれいに色付けされてリアルタイムで表示されます。
ゾーンの数は、制御モニターの目的と機能によって決まります。たとえば、 Arri Alexaカメラで使用されるモニターには、最大 6 つのゾーンを組み込むことができます。
拡張機能の追加
例に進む前に、メタル テクスチャなどのソース データを処理するプラットフォームとして OpenFXS を実装するために、いくつかの単純なプロキシ クラスを追加する必要があります。これらのクラスには次のものが含まれます。
- imetalling::Image : OFX クリップ データのプロキシ クラス。
imetalling::Image2Texture : データをクリップ バッファーから Metal テクスチャに転送するためのファンクター。 DaVinci からは、任意の構造のバッファーと画像チャンネル値のパッケージ化をプラグインに抽出でき、同様の形式で返されるはずです。
OFX でのストリーム形式の操作を容易にするために、事前に特定のタイプのデータを準備するようにホストに要求できます。 RGBA (赤/緑/青/アルファ) でパックされた浮動小数点数を使用します。
- imetalling::ImageFromTexture : ストリームをホスト システム バッファーに変換するためのリバース ファンクター。ご覧のとおり、テクスチャではなくバッファを直接操作するように Metal コンピューティング コアを学習すると、計算が大幅に最適化される可能性があります。
Metal コアがどのように動作するかについては詳しく説明せずに、OXS の基本クラスを継承して機能を記述します。
- imetalling::falsecolor::Processor : ここで、ストリーム変換を実装し、処理を開始します。
- imetalling::falsecolor::Factory : これは、プラグインのスイートの説明の特定の部分になります。構造体のセットアップに関連するいくつかの必須呼び出しを実装し、特定の機能を持つ OFX::ImageEffect クラスのインスタンスを作成する必要があります。実装では、Interaction と Plugin の 2 つのサブクラスに分割します。
- imetalling::falsecolor::Interaction : エフェクトを操作するインタラクティブな部分の実装。基本的に、これはプラグイン パラメーターの変更の処理に関連する OFX::ImageEffect の仮想メソッドのみの実装です。
- imetalling::falsecolor::Plugin : スレッド レンダリングの実装、つまり imetalling::Processor の起動。
さらに、MSL 上のホスト コードとカーネル コードを論理的に分離するには、Metal 上に構築されたいくつかのユーティリティ クラスが必要になります。これらには次のものが含まれます。
- imetalling::Function : Metal コマンド キューでの作業をわかりにくくする基本クラス。主なパラメータは、MSL コード内のカーネルの名前と、カーネル呼び出しの実行者になります。
- imetalling:Kernel : ソース テクスチャをターゲット テクスチャに変換するための一般的なクラス。関数を拡張して、MSL カーネルを呼び出すためのパラメータを設定するだけです。
- imetalling::PassKernel : カーネルをバイパスします。
imetalling::FalseColorKernel : 私たちの主要な機能クラスであり、指定された色数にポスタライズ (ダウンサンプル) する「プレデター」エミュレーターです。
「プレデター」モードのカーネル コードは次のようになります。
static constant float3 kIMP_Y_YUV_factor = {0.2125, 0.7154, 0.0721}; constexpr sampler baseSampler(address::clamp_to_edge, filter::linear, coord::normalized); inline float when_eq(float x, float y) { return 1.0 - abs(sign(x - y)); } static inline float4 sampledColor( texture2d<float, access::sample> inTexture, texture2d<float, access::write> outTexture, uint2 gid ){ float w = outTexture.get_width(); return mix(inTexture.sample(baseSampler, float2(gid) * float2(1.0/(w-1.0), 1.0/float(outTexture.get_height()-1))), inTexture.read(gid), when_eq(inTexture.get_width(), w) // whe equal read exact texture color ); } kernel void kernel_falseColor( texture2d<float, access::sample> inTexture [[texture(0)]], texture2d<float, access::write> outTexture [[texture(1)]], device float3* color_map [[ buffer(0) ]], constant uint& level [[ buffer(1) ]], uint2 gid [[thread_position_in_grid]]) { float4 inColor = sampledColor(inTexture,outTexture,gid); float luminance = dot(inColor.rgb, kIMP_Y_YUV_factor); uint index = clamp(uint(luminance*(level-1)),uint(0),uint(level-1)); float4 color = float4(1); if (index<level) color.rgb = color_map[index]; outTexture.write(color,gid); }
OFXプラグインの初期化
まず、 imetalling::falsecolor::Factory.
このクラスでは、モニターのステータス (オンまたはオフ) という 1 つのパラメーターを設定します。これはこの例では必要です。
OFX::PluginFactoryHelper
から継承し、5 つのメソッドをオーバーロードします。
- load() : このメソッドは、プラグインが最初にロードされるときにインスタンスをグローバルに設定するために呼び出されます。このメソッドのオーバーロードはオプションです。
- unload() : このメソッドは、インスタンスがアンロードされるとき、たとえばメモリをクリアするときに呼び出されます。このメソッドのオーバーロードもオプションです。
- description(ImageEffectDescriptor&) : これは、プラグインがロードされるときに OFX ホストが呼び出す 2 番目のメソッドです。これは仮想的なものであり、クラス内で定義する必要があります。このメソッドでは、コンテキスト タイプに関係なく、プラグインのすべてのプロパティを設定する必要があります。プロパティの詳細については、
ImageEffectDescriptor
コードを参照してください。
descriptionInContext(ImageEffectDescriptor&,ContextEnum) :
describe
メソッドと同様に、このメソッドもプラグインがロードされるときに呼び出され、クラスで定義する必要があります。現在のコンテキストに関連付けられたプロパティを定義する必要があります。
コンテキストによって、アプリケーションが扱う操作のタイプ (フィルター、ペイント、トランジション エフェクト、クリップ内のフレーム リタイマーなど) が決まります。
- createInstance(OfxImageEffectHandle, ContextEnum) : これは、オーバーロードする最も重要なメソッドです。
ImageEffect
型のオブジェクトへのポインタを返します。言い換えれば、imetalling::falsecolor::Plugin
では、ホスト プログラムのユーザー イベントとソース フレームからターゲット フレームへのレンダリング (変換) の両方に関するすべての機能が定義されています。
OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) { return new Plugin(handle); }
イベントの処理
この段階で、OFX モジュールを使用してバンドルをコンパイルすると、プラグインはホスト アプリケーションですでに利用可能になり、DaVinci では補正ノードにロードできます。
ただし、プラグイン インスタンスを完全に操作するには、少なくともインタラクティブな部分と、受信ビデオ ストリームの処理に関連する部分を定義する必要があります。
これを行うには、 OFX::ImageEffectクラスを継承し、仮想メソッドをオーバーロードします。
ChangeParam(const OFX::InstanceChangedArgs&, const std::string&) - このメソッドを使用すると、イベントを処理するロジックを定義できます。イベント タイプは OFX::InstanceChangedArgs::reason の値によって決定され、次のいずれかになります: eChangeUserEdit、eChangePluginEdit、eChangeTime - ユーザーによって編集されたプロパティ、プラグインまたはホスト アプリケーションで変更されたプロパティの結果としてイベントが発生しました。タイムラインの変更の結果。
2 番目のパラメーターは、プラグインの初期化段階で定義した文字列名を指定します。この場合、それは 1 つのパラメーターです: false_color_enabled_check_box 。
- isIdentity(...) - このメソッドを使用すると、イベントに反応するためのロジックを定義し、何かが変更されたかどうか、およびレンダリングが意味があるかどうかを判断する状態を返すことができます。メソッドは false または true を返す必要があります。これは、不必要な計算の数を最適化して削減する方法です。
OFX との対話型対話の実装は、 Interaction.cppコードで読み取ることができます。ご覧のとおり、クリップへのポインター、つまりソース クリップとターゲット変換を配置するメモリ領域を受け取ります。
レンダリング起動の実装
変換を開始するためのすべてのロジックを定義する別の論理レイヤーを追加します。私たちの場合、これは今のところオーバーライドする唯一の方法です。
- render(const OFX::RenderArguments& args) - ここで、クリップのプロパティを確認し、それらをレンダリングする方法を決定できます。また、この段階で、Metal コマンド キューと、現在のタイムライン プロパティに関連付けられたいくつかの便利な属性が利用できるようになります。
処理
起動段階では、便利なプロパティを持つオブジェクトが利用可能になりました。少なくとも、ビデオ ストリームへのポインタ (より正確には、フレーム イメージ データを含むメモリ領域)、そして最も重要なことに、Metal コマンドのキューがあります。
これで、カーネル コードを再利用する単純な形式に近づけるジェネリック クラスを構築できるようになりました。 OpenFXS 拡張機能には、次のようなクラスがすでに存在します。オーバーロードする必要があるだけです。
コンストラクターには OFX::ImageEffect パラメーターがあります。つまり、その中でプラグイン パラメーターの現在の状態だけでなく、GPU の操作に必要なすべてのものを受け取ります。
この段階では、 processImagesMetal()メソッドをオーバーロードし、既に Metal に実装されているカーネルの処理を開始するだけです。
Processor::Processor( OFX::ImageEffect *instance, OFX::Clip *source, OFX::Clip *destination, const OFX::RenderArguments &args, bool enabled ) : OFX::ImageProcessor(*instance), enabled_(enabled), interaction_(instance), wait_command_queue_(false), /// grab the current frame of a clip from OFX host memory source_(source->fetchImage(args.time)), /// create a target frame of a clip with the memory area already specified in OFX destination_(destination->fetchImage(args.time)), source_container_(nullptr), destination_container_(nullptr) { /// Set OFX rendering arguments to GPU setGPURenderArgs(args); /// Set render window setRenderWindow(args.renderWindow); /// Place source frame data in Metal texture source_container_ = std::make_unique<imetalling::Image2Texture>(_pMetalCmdQ, source_); /// Create empty target frame texture in Metal destination_container_ = std::make_unique<imetalling::Image2Texture>(_pMetalCmdQ, destination_); /// Get parameters for packing data in the memory area of the target frame OFX::BitDepthEnum dstBitDepth = destination->getPixelDepth(); OFX::PixelComponentEnum dstComponents = destination->getPixelComponents(); /// and original OFX::BitDepthEnum srcBitDepth = source->getPixelDepth(); OFX::PixelComponentEnum srcComponents = source->getPixelComponents(); /// show a message to the host system that something went wrong /// and cancel rendering of the current frame if ((srcBitDepth != dstBitDepth) || (srcComponents != dstComponents)) { OFX::throwSuiteStatusException(kOfxStatErrValue); } /// set in the current processor context a pointer to the memory area of the target frame setDstImg(destination_.get_ofx_image()); } void Processor::processImagesMetal() { try { if (enabled_) FalseColorKernel(_pMetalCmdQ, source_container_->get_texture(), destination_container_->get_texture()).process(); else PassKernel(_pMetalCmdQ, source_container_->get_texture(), destination_container_->get_texture()).process(); ImageFromTexture(_pMetalCmdQ, destination_, destination_container_->get_texture(), wait_command_queue_); } catch (std::exception &e) { interaction_->sendMessage(OFX::Message::eMessageError, "#message0", e.what()); } }
プロジェクトの構築
プロジェクトをビルドするには CMake が必要で、少なくともバージョン 3.15 である必要があります。さらに、システム ディレクトリ内のプラグイン インストーラーを使用してバンドルを簡単かつ便利に組み立てるのに役立つ Qt5.13 が必要です。 cmake を開始するには、まずビルド ディレクトリを作成する必要があります。
ビルド ディレクトリを作成した後、次のコマンドを実行できます。
cmake -DPRINT_DEBUG=ON -DQT_INSTALLER_PREFIX=/Users/<user>/Develop/QtInstaller -DCMAKE_PREFIX_PATH=/Users/<user>/Develop/Qt/5.13.0/clang_64/lib/cmake -DPLUGIN_INSTALLER_DIR=/Users/<user>/Desktop -DCMAKE_INSTALL_PREFIX=/Library/OFX/Plugins .. && make install
その後、 IMFalseColorOfxInstaller.appというインストーラーが、 PLUGIN_INSTALLER_DIRパラメーターで指定したディレクトリに表示されます。さあ、起動しましょう!インストールが成功したら、DaVinci Resolve を起動して、新しいプラグインの使用を開始できます。
カラーコレクションページの OpenFX パネルで見つけて選択し、ノードとして追加できます。
外部リンク
- フォールスカラー OFX プラグイン コード
- オープンエフェクト協会
- DaVinci Resolve - OFX ヘッダー ファイル バージョンと OXS ライブラリ コードを Resolve + サンプルからダウンロードします。