OFX, alias OFX Image Processing API , est un standard ouvert pour la création d'effets visuels 2D et la composition vidéo. Il fonctionne dans un modèle de développement d'applications de type plugin. Essentiellement, il sert à la fois d'hôte - une application fournissant un ensemble de méthodes, et de plug-in - une application ou un module implémentant cet ensemble.
Cette configuration offre un potentiel d'extension illimitée des fonctionnalités de l'application hôte.
Des applications telles que Final Cut X et DaVinci Resolve Studio, à partir de la version 16, prennent entièrement en charge les pipelines Apple Metal. Semblable à OpenCL et Cuda, dans le cas d'OFX, vous pouvez obtenir un descripteur ou un gestionnaire d'une file d'attente de commandes spécifique à la plate-forme. Le système hôte prend également la responsabilité d'attribuer un pool de telles files d'attente et d'équilibrer les calculs sur celles-ci.
De plus, il place les données des clips d'image source et cible dans la mémoire GPU, simplifiant considérablement le développement de fonctionnalités extensibles.
Avec Resolve, les choses sont un peu plus compliquées. DaVinci annonce le support d'OFX v1.4, mais avec quelques limitations. Plus précisément, certaines méthodes permettant d'utiliser les fonctions d'interface ne sont pas disponibles. Pour déterminer quelle méthode est disponible, OFX vous permet d'examiner la suite prise en charge via des requêtes clé/valeur.
Les méthodes de publication dans le code du plugin sont basées sur des appels C . Mais nous utiliserons le shell OpenFXS C++ adapté pour C++17. Pour plus de commodité, j'ai tout compilé dans un seul référentiel : dehancer-external tiré du projet open source Dehancer .
Dans ce projet, j'utiliserai OpenFXS, une extension C++ d'OpenFX initialement écrite par Bruno Nicoletti et devenue populaire au fil du temps dans les projets de traitement vidéo commerciaux et open source.
L' OpenFXS original n'était pas adapté aux dialectes C++ modernes, je l'ai donc mis à jour pour le rendre compatible avec C++17 .
OFX, et par conséquent OFXS, est un module logiciel autonome chargé dynamiquement par le programme hôte. Il s'agit essentiellement d'une bibliothèque dynamique qui est chargée au démarrage de l'application principale. OpenFXS, comme OFX, doit publier les signatures de méthodes. Par conséquent, nous utilisons une méthode C du code.
Pour commencer à développer dans OpenFXS, vous devez accepter quelques ensembles communs de classes utilisées pour créer de nouvelles fonctionnalités dans votre application. En règle générale, dans un nouveau projet, vous devez hériter de ces classes et implémenter ou remplacer certaines méthodes virtuelles.
Pour créer votre propre plugin sur le système hôte, commençons par nous familiariser avec les classes publiques suivantes et la même méthode :
Une caractéristique qui distingue le processus de prise de vue vidéo de la simple capture d'une image dans une photo est le changement dynamique des scènes et de l'éclairage des scènes dans leur ensemble et des zones de l'image. Cela détermine la manière dont l'exposition est contrôlée pendant le processus de prise de vue.
En vidéo numérique, il existe un mode moniteur de contrôle pour les opérateurs dans lequel le niveau d'exposition des zones est cartographié en un ensemble limité de zones, chacune teintée de sa propre couleur.
Ce mode est parfois appelé « prédateur » ou mode Fausses Couleurs. Les échelles sont généralement référencées à l’échelle IRE.
Un tel moniteur vous permet de voir les zones d'exposition et d'éviter des erreurs importantes lors du réglage des paramètres de prise de vue de l'appareil photo. Quelque chose de similaire dans sa signification est utilisé lors de l'exposition en photographie - le zonage selon Adams, par exemple.
Vous pouvez mesurer une cible spécifique avec un posemètre et voir dans quelle zone elle se trouve, et en temps réel nous voyons les zones, soigneusement teintées pour faciliter la perception.
Le nombre de zones est déterminé par les objectifs et les capacités du moniteur de contrôle. Par exemple, un moniteur utilisé avec les caméras Arri Alexa peut intégrer jusqu'à 6 zones.
Avant de passer à l'exemple, nous devons ajouter quelques classes proxy simples pour implémenter OpenFXS en tant que plate-forme de traitement des données sources, telles que les textures Metal. Ces cours comprennent :
imétalling::Image2Texture : Un foncteur pour transférer les données du tampon de clip vers une texture Metal. Depuis DaVinci, vous pouvez extraire un tampon de n'importe quelle structure et emballage de valeurs de canal d'image dans le plugin, et il doit être renvoyé sous une forme similaire.
Pour faciliter le travail avec le format de flux dans OFX, vous pouvez demander à l'hôte de préparer à l'avance les données d'un type spécifique. J'utiliserai des flotteurs emballés en RGBA - rouge/vert/bleu/alpha.
Nous héritons des classes de base OFXS et écrivons nos fonctionnalités sans entrer dans les détails du fonctionnement du noyau Metal :
De plus, nous aurons besoin de plusieurs classes utilitaires construites sur Metal pour séparer logiquement le code hôte et le code noyau sur MSL. Ceux-ci inclus:
imétalling::FalseColorKernel : Notre classe fonctionnelle principale, un émulateur "prédateur" qui postérise (sous-échantillonne) un nombre spécifié de couleurs.
Le code du noyau pour le mode "prédateur" pourrait ressembler à ceci :
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); }
Initialisation du plugin OFX
Nous commencerons par définir la classe imetalling::falsecolor::Factory.
Dans ce cours, nous définirons un seul paramètre : l'état du moniteur (activé ou éteint). Ceci est nécessaire pour notre exemple.
Nous hériterons de OFX::PluginFactoryHelper
et surchargerons cinq méthodes :
ImageEffectDescriptor
.
décrireInContext(ImageEffectDescriptor&,ContextEnum) : Semblable à la méthode describe
, cette méthode est également appelée lors du chargement du plugin et doit être définie dans notre classe. Il doit définir les propriétés associées au contexte actuel.
Le contexte détermine le type d'opérations avec lesquelles l'application travaille, telles que le filtre, la peinture, l'effet de transition ou le retimer d'image dans un clip.
ImageEffect
. En d'autres termes, notre imetalling::falsecolor::Plugin
dans lequel nous avons défini toutes les fonctionnalités, tant en ce qui concerne les événements utilisateur dans le programme hôte que le rendu (transformation) de la trame source en celle cible : OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) { return new Plugin(handle); }
A ce stade, si vous compilez un bundle avec le module OFX, le plugin sera déjà disponible dans l'application hôte, et dans DaVinci, il pourra être chargé sur le nœud de correction.
Cependant, pour fonctionner pleinement avec une instance de plugin, vous devez définir au moins la partie interactive et la partie associée au traitement du flux vidéo entrant.
Pour ce faire, nous héritons de la classe OFX::ImageEffect et surchargeons les méthodes virtuelles :
changesParam(const OFX::InstanceChangedArgs&, const std::string&) - Cette méthode nous permet de définir la logique de gestion de l'événement. Le type d'événement est déterminé par la valeur de OFX::InstanceChangedArgs::reason et peut être : eChangeUserEdit, eChangePluginEdit, eChangeTime - l'événement s'est produit à la suite d'une propriété modifiée par l'utilisateur, modifiée dans un plugin ou une application hôte, ou à la suite d'un changement de calendrier.
Le deuxième paramètre spécifie le nom de chaîne que nous avons défini lors de l'étape d'initialisation du plugin, dans notre cas, il s'agit d'un paramètre : false_color_enabled_check_box .
Vous pouvez lire l'implémentation de l'interaction interactive avec OFX dans le code Interaction.cpp . Comme vous pouvez le voir, nous recevons des pointeurs vers les clips : celui source et la zone mémoire dans laquelle nous allons mettre la transformation cible.
Nous ajouterons une autre couche logique sur laquelle nous définirons toute la logique de lancement de la transformation. Dans notre cas, c'est la seule méthode de remplacement jusqu'à présent :
Au stade du lancement, un objet avec des propriétés utiles s'est mis à notre disposition : nous disposons d'au moins un pointeur vers le flux vidéo (plus précisément, une zone mémoire avec des données d'image image) et, surtout, une file d'attente de commandes Metal.
Nous pouvons désormais construire une classe générique qui nous rapprochera d’une forme simple de réutilisation du code du noyau. L'extension OpenFXS possède déjà une telle classe : OFX::ImageProcessor ; nous avons juste besoin de le surcharger.
Dans le constructeur, il a le paramètre OFX::ImageEffect, c'est-à-dire que nous y recevrons non seulement l'état actuel des paramètres du plugin, mais aussi tout le nécessaire pour travailler avec le GPU.
A ce stade, il suffit de surcharger la méthode processImagesMetal() et de lancer le traitement des noyaux déjà implémentés sur 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()); } }
Pour construire le projet, vous aurez besoin de CMake, et il doit être au moins en version 3.15. De plus, vous aurez besoin de Qt5.13, qui facilite l'assemblage simple et pratique du bundle avec le programme d'installation du plugin dans le répertoire système. Pour lancer cmake, vous devez d'abord créer un répertoire de construction.
Après avoir créé le répertoire build, vous pouvez exécuter la commande suivante :
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
Ensuite, le programme d'installation, appelé IMFalseColorOfxInstaller.app , apparaîtra dans le répertoire que vous avez spécifié dans le paramètre PLUGIN_INSTALLER_DIR . Allons-y et lançons-le ! Une fois l'installation réussie, vous pouvez démarrer DaVinci Resolve et commencer à utiliser notre nouveau plugin.
Vous pouvez le trouver et le sélectionner dans le panneau OpenFX sur la page de correction des couleurs, et l'ajouter en tant que nœud.
Liens externes