OFX, auch bekannt als OFX Image Processing API , ist ein offener Standard für die Erstellung visueller 2D-Effekte und Video-Compositing. Es arbeitet in einem Plugin-ähnlichen Anwendungsentwicklungsmodell. Im Wesentlichen dient es sowohl als Host – eine Anwendung, die eine Reihe von Methoden bereitstellt, als auch als Plug-in – eine Anwendung oder ein Modul, die diese Reihe implementiert.
Diese Konfiguration bietet die Möglichkeit einer unbegrenzten Erweiterung der Funktionalität der Host-Anwendung.
Anwendungen wie Final Cut X und DaVinci Resolve Studio unterstützen ab Version 16 Apple Metal Pipelines vollständig. Ähnlich wie bei OpenCL und Cuda können Sie im Fall von OFX einen Deskriptor oder Handler einer plattformspezifischen Befehlswarteschlange erhalten. Das Hostsystem übernimmt auch die Verantwortung für die Zuweisung eines Pools solcher Warteschlangen und den Ausgleich der Berechnungen darauf.
Darüber hinaus werden die Quell- und Ziel-Bildclipdaten im GPU-Speicher abgelegt, was die Entwicklung erweiterbarer Funktionen erheblich vereinfacht.
Bei Resolve sind die Dinge etwas komplizierter. DaVinci kündigt Unterstützung für OFX v1.4 an, allerdings mit einigen Einschränkungen. Insbesondere stehen einige Methoden zum Arbeiten mit Schnittstellenfunktionen nicht zur Verfügung. Um festzustellen, welche Methode verfügbar ist, können Sie mit OFX die unterstützte Suite durch Schlüssel-/Wertabfragen untersuchen.
Veröffentlichungsmethoden im Plugin-Code basieren auf C-Aufrufen . Wir werden jedoch die für C++17 angepasste OpenFXS C++-Shell verwenden. Der Einfachheit halber habe ich alles in einem Repository zusammengestellt: dehancer-external aus dem Open-Source -Projekt Dehancer .
In diesem Projekt verwende ich OpenFXS, eine C++-Erweiterung von OpenFX, die ursprünglich von Bruno Nicoletti geschrieben wurde und im Laufe der Zeit in kommerziellen und Open-Source-Videoverarbeitungsprojekten populär geworden ist.
Das ursprüngliche OpenFXS wurde nicht an moderne C++-Dialekte angepasst, daher habe ich es aktualisiert, um es mit C++17 kompatibel zu machen.
OFX und folglich OFXS ist ein eigenständiges Softwaremodul, das vom Hostprogramm dynamisch geladen wird. Im Wesentlichen handelt es sich um eine dynamische Bibliothek, die beim Start der Hauptanwendung geladen wird. OpenFXS muss wie OFX Methodensignaturen veröffentlichen. Daher verwenden wir eine C-Methode aus dem Code.
Um mit der Entwicklung in OpenFXS zu beginnen, müssen Sie einigen gemeinsamen Klassensätzen zustimmen, die zum Erstellen neuer Funktionen in Ihrer Anwendung verwendet werden. Normalerweise müssen Sie in einem neuen Projekt von diesen Klassen erben und einige virtuelle Methoden implementieren oder überschreiben.
Um Ihr eigenes Plugin auf dem Hostsystem zu erstellen, machen wir uns zunächst mit den folgenden öffentlichen Klassen und derselben Methode vertraut:
Ein Merkmal, das den Prozess der Videoaufnahme von der einfachen Aufnahme eines Bildes auf einem Foto unterscheidet, ist der dynamische Szenenwechsel und die Beleuchtung sowohl der Szenen als Ganzes als auch der Bereiche im Bild. Dies bestimmt die Art und Weise, wie die Belichtung während des Aufnahmevorgangs gesteuert wird.
Bei digitalen Videos gibt es einen Kontrollmonitormodus für Bediener, bei dem die Belichtungsstufe von Bereichen auf eine begrenzte Anzahl von Zonen abgebildet wird, die jeweils mit einer eigenen Farbe getönt sind.
Dieser Modus wird manchmal als „Raubtier“ oder Falschfarbenmodus bezeichnet. Die Skalen beziehen sich üblicherweise auf die IRE-Skala.
Mit einem solchen Monitor können Sie die Belichtungszonen sehen und erhebliche Fehler bei der Einstellung der Kameraaufnahmeparameter vermeiden. Eine ähnliche Bedeutung wird bei der Belichtung in der Fotografie verwendet – zum Beispiel die Zoneneinteilung nach Adams.
Sie können ein bestimmtes Ziel mit einem Belichtungsmesser messen und sehen, in welcher Zone es sich befindet. In Echtzeit sehen wir die Zonen, sauber getönt, um die Wahrnehmung zu erleichtern.
Die Anzahl der Zonen wird durch die Ziele und Fähigkeiten des Kontrollmonitors bestimmt. Beispielsweise kann ein Monitor, der mit Arri Alexa- Kameras verwendet wird, bis zu 6 Zonen integrieren.
Bevor wir mit dem Beispiel fortfahren, müssen wir einige einfache Proxy-Klassen hinzufügen, um OpenFXS als Plattform für die Verarbeitung von Quelldaten, wie z. B. Metalltexturen, zu implementieren. Zu diesen Kursen gehören:
imetalling::Image2Texture : Ein Funktor zum Übertragen von Daten aus dem Clip-Puffer in eine Metal-Textur. Von DaVinci aus können Sie einen Puffer beliebiger Struktur und Paketierung von Bildkanalwerten in das Plugin extrahieren, der in ähnlicher Form zurückgegeben werden sollte.
Um die Arbeit mit dem Stream-Format in OFX zu erleichtern, können Sie den Host auffordern, vorab Daten eines bestimmten Typs vorzubereiten. Ich werde in RGBA gepackte Floats verwenden – Rot/Grün/Blau/Alpha.
Wir erben die OFXS-Basisklassen und schreiben unsere Funktionalität, ohne auf die Details der Funktionsweise des Metal-Kerns einzugehen:
Darüber hinaus benötigen wir mehrere auf Metal aufbauende Dienstprogrammklassen, um den Host-Code und den Kernel-Code auf MSL logisch zu trennen. Diese beinhalten:
imetalling::FalseColorKernel : Unsere Hauptfunktionsklasse, ein „Predator“-Emulator, der eine Posterisierung (Downsampling) auf eine bestimmte Anzahl von Farben durchführt.
Der Kernel-Code für den „Predator“-Modus könnte so aussehen:
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); }
Initialisierung des OFX-Plugins
Wir beginnen mit der Definition der Klasse imetalling::falsecolor::Factory.
In dieser Klasse legen wir einen einzelnen Parameter fest – den Status des Monitors (entweder ein oder aus). Dies ist für unser Beispiel notwendig.
Wir werden von OFX::PluginFactoryHelper
erben und fünf Methoden überladen:
ImageEffectDescriptor
Code.
beschreibenInContext(ImageEffectDescriptor&,ContextEnum) : Ähnlich wie die describe
wird diese Methode auch beim Laden des Plugins aufgerufen und muss in unserer Klasse definiert werden. Es sollte Eigenschaften definieren, die mit dem aktuellen Kontext verknüpft sind.
Der Kontext bestimmt die Art der Vorgänge, mit denen die Anwendung arbeitet, z. B. Filter, Farbe, Übergangseffekt oder Frame-Retimer in einem Clip.
ImageEffect
zurück. Mit anderen Worten, unser imetalling::falsecolor::Plugin
, in dem wir alle Funktionalitäten definiert haben, sowohl im Hinblick auf Benutzerereignisse im Host-Programm als auch auf das Rendern (Umwandeln) des Quell-Frames in den Ziel-Frame: OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) { return new Plugin(handle); }
Wenn Sie zu diesem Zeitpunkt ein Bundle mit dem OFX-Modul kompilieren, ist das Plugin bereits in der Host-Anwendung verfügbar und kann in DaVinci auf den Korrekturknoten geladen werden.
Um jedoch vollständig mit einer Plugin-Instanz arbeiten zu können, müssen Sie mindestens den interaktiven Teil und den Teil definieren, der mit der Verarbeitung des eingehenden Videostreams verbunden ist.
Dazu erben wir von der Klasse OFX::ImageEffect und überladen virtuelle Methoden:
changesParam(const OFX::InstanceChangedArgs&, const std::string&) – Mit dieser Methode können wir die Logik für die Behandlung des Ereignisses definieren. Der Ereignistyp wird durch den Wert von OFX::InstanceChangedArgs::reason bestimmt und kann sein: eChangeUserEdit, eChangePluginEdit, eChangeTime – das Ereignis ist als Ergebnis einer vom Benutzer bearbeiteten Eigenschaft, einer Änderung in einem Plugin oder einer Hostanwendung oder aufgetreten als Ergebnis einer Änderung in der Zeitleiste.
Der zweite Parameter gibt den String-Namen an, den wir in der Plugin-Initialisierungsphase definiert haben. In unserem Fall handelt es sich um einen Parameter: false_color_enabled_check_box .
Sie können die Implementierung der interaktiven Interaktion mit OFX im Interaction.cpp- Code lesen. Wie Sie sehen, erhalten wir Zeiger auf die Clips: den Quellclip und den Speicherbereich, in den wir die Zieltransformation einfügen werden.
Wir werden eine weitere logische Ebene hinzufügen, auf der wir die gesamte Logik für den Start der Transformation definieren. In unserem Fall ist dies bisher die einzige Methode zum Überschreiben:
In der Startphase stand uns ein Objekt mit nützlichen Eigenschaften zur Verfügung: Wir haben mindestens einen Zeiger auf den Videostream (genauer gesagt einen Speicherbereich mit Frame-Bilddaten) und vor allem eine Warteschlange mit Metal-Befehlen.
Jetzt können wir eine generische Klasse erstellen, die uns einer einfachen Form der Wiederverwendung von Kernel-Code näher bringt. Die OpenFXS-Erweiterung verfügt bereits über eine solche Klasse: OFX::ImageProcessor; wir müssen es nur überladen.
Im Konstruktor gibt es den Parameter OFX::ImageEffect, d. h. wir erhalten darin nicht nur den aktuellen Status der Plugin-Parameter, sondern alles, was für die Arbeit mit der GPU notwendig ist.
Zu diesem Zeitpunkt müssen wir lediglich die Methode „processImagesMetal()“ überladen und die Verarbeitung der bereits auf Metal implementierten Kernel initiieren.
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()); } }
Um das Projekt zu erstellen, benötigen Sie CMake und es muss mindestens Version 3.15 sein. Darüber hinaus benötigen Sie Qt5.13, das die einfache und bequeme Zusammenstellung des Bundles mit dem Plugin-Installer im Systemverzeichnis unterstützt. Um cmake zu starten, müssen Sie zunächst ein Build-Verzeichnis erstellen.
Nachdem Sie das Build-Verzeichnis erstellt haben, können Sie den folgenden Befehl ausführen:
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
Anschließend wird das Installationsprogramm mit dem Namen IMFalseColorOfxInstaller.app in dem Verzeichnis angezeigt, das Sie im Parameter PLUGIN_INSTALLER_DIR angegeben haben. Lasst uns loslegen und es starten! Sobald die Installation erfolgreich ist, können Sie DaVinci Resolve starten und unser neues Plugin verwenden.
Sie können es im OpenFX-Bedienfeld auf der Farbkorrekturseite finden, auswählen und als Knoten hinzufügen.
Externe Links