OFX, también conocido como OFX Image Processing API , es un estándar abierto para crear efectos visuales 2D y composición de vídeo. Opera en un modelo de desarrollo de aplicaciones similar a un complemento. Básicamente, sirve como Host (una aplicación que proporciona un conjunto de métodos) y como Complemento (una aplicación o módulo que implementa este conjunto).
Esta configuración ofrece la posibilidad de una expansión ilimitada de la funcionalidad de la aplicación host.
Aplicaciones como Final Cut X y DaVinci Resolve Studio, a partir de la versión 16, son totalmente compatibles con las canalizaciones Apple Metal. De manera similar a OpenCL y Cuda, en el caso de OFX, puede obtener un descriptor o controlador de una cola de comandos específica de la plataforma. El sistema anfitrión también asume la responsabilidad de asignar un conjunto de colas de este tipo y equilibrar los cálculos sobre ellas.
Además, coloca los datos del clip de imagen de origen y de destino en la memoria de la GPU, lo que simplifica significativamente el desarrollo de funciones extensibles.
Con Resolve, las cosas son un poco más complicadas. DaVinci anuncia soporte para OFX v1.4, aunque con algunas limitaciones. Específicamente, algunos métodos para trabajar con funciones de interfaz no están disponibles para su uso. Para determinar qué método está disponible, OFX le permite examinar el conjunto admitido a través de consultas clave/valor.
Los métodos de publicación en el código del complemento se basan en llamadas C. Pero usaremos el shell OpenFXS C++ adaptado para C++17. Para mayor comodidad, he compilado todo en un repositorio: dehancer-external tomado del proyecto de código abierto Dehancer .
En este proyecto, usaré OpenFXS, una extensión C++ para OpenFX que fue escrita originalmente por Bruno Nicoletti y que se ha vuelto popular con el tiempo en proyectos comerciales y de procesamiento de video de código abierto.
El OpenFXS original no se adaptó a los dialectos modernos de C++, así que lo actualicé para hacerlo compatible con C++17 .
OFX, y en consecuencia OFXS, es un módulo de software independiente que el programa anfitrión carga dinámicamente. Básicamente, es una biblioteca dinámica que se carga cuando se inicia la aplicación principal. OpenFXS, al igual que OFX, debe publicar firmas de métodos. Por lo tanto, utilizamos un método C del código.
Para comenzar a desarrollar en OpenFXS, debe aceptar algunos conjuntos comunes de clases que se utilizan para crear nuevas funciones en su aplicación. Normalmente, en un proyecto nuevo, es necesario heredar de estas clases e implementar o anular algunos métodos virtuales.
Para crear su propio complemento en el sistema host, comencemos familiarizándonos con las siguientes clases públicas y el mismo método:
Una característica que distingue el proceso de grabar un vídeo de simplemente capturar una imagen en una fotografía es el cambio dinámico de escenas y la iluminación tanto de las escenas en su conjunto como de las áreas de la imagen. Esto determina la forma en que se controla la exposición durante el proceso de disparo.
En el vídeo digital, existe un modo de monitor de control para los operadores en el que el nivel de exposición de las áreas se asigna a un conjunto limitado de zonas, cada una teñida con su propio color.
Este modo a veces se denomina modo "depredador" o modo Falso Color. Las escalas suelen estar referenciadas a la escala IRE.
Un monitor de este tipo le permite ver las zonas de exposición y evitar errores importantes al configurar los parámetros de disparo de la cámara. Algo similar en significado se utiliza cuando se expone en fotografía: la zonificación según Adams, por ejemplo.
Puedes medir un objetivo específico con un exposímetro y ver en qué zona se encuentra, y en tiempo real vemos las zonas, cuidadosamente teñidas para facilitar la percepción.
El número de zonas está determinado por los objetivos y capacidades del monitor de control. Por ejemplo, un monitor utilizado con cámaras Arri Alexa puede incorporar hasta 6 zonas.
Antes de continuar con el ejemplo, necesitamos agregar algunas clases de proxy simples para implementar OpenFXS como una plataforma para procesar datos de origen, como texturas metálicas. Estas clases incluyen:
imetalling::Image2Texture : un functor para transferir datos desde el búfer de clip a una textura de metal. Desde DaVinci, puede extraer un búfer de cualquier estructura y empaquetado de valores de canales de imágenes en el complemento, y debe devolverse de forma similar.
Para facilitar el trabajo con el formato de transmisión en OFX, puede solicitar al anfitrión que prepare datos de un tipo específico con anticipación. Usaré flotadores empaquetados en RGBA: rojo/verde/azul/alfa.
Heredamos las clases base OFXS y escribimos nuestra funcionalidad sin entrar en detalles de cómo funciona el núcleo Metal:
Además, necesitaremos varias clases de utilidades construidas sobre Metal para separar lógicamente el código del host y el código del kernel en MSL. Éstas incluyen:
imetalling::FalseColorKernel : nuestra clase funcional principal, un emulador "depredador" que posteriza (reduce la resolución) a un número específico de colores.
El código del kernel para el modo "depredador" podría verse así:
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); }
Inicialización del complemento OFX
Comenzaremos definiendo la clase imetalling::falsecolor::Factory.
En esta clase, configuraremos un único parámetro: el estado del monitor (ya sea encendido o apagado). Esto es necesario para nuestro ejemplo.
Heredaremos de OFX::PluginFactoryHelper
y sobrecargaremos cinco métodos:
ImageEffectDescriptor
.
describeInContext(ImageEffectDescriptor&,ContextEnum) : similar al método describe
, este método también se llama cuando se carga el complemento y debe definirse en nuestra clase. Debe definir propiedades asociadas con el contexto actual.
El contexto determina el tipo de operaciones con las que trabaja la aplicación, como filtro, pintura, efecto de transición o temporizador de cuadros en un clip.
ImageEffect
. En otras palabras, nuestro imetalling::falsecolor::Plugin
en el que hemos definido todas las funcionalidades, tanto con respecto a los eventos del usuario en el programa anfitrión como con la renderización (transformación) del fotograma de origen en el de destino: OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) { return new Plugin(handle); }
En esta etapa, si compila un paquete con el módulo OFX, el complemento ya estará disponible en la aplicación host y, en DaVinci, se podrá cargar en el nodo de corrección.
Sin embargo, para trabajar completamente con una instancia de complemento, debe definir al menos la parte interactiva y la parte asociada con el procesamiento de la transmisión de video entrante.
Para hacer esto, heredamos de la clase OFX::ImageEffect y sobrecargamos los métodos virtuales:
changeParam(const OFX::InstanceChangedArgs&, const std::string&) - Este método nos permite definir la lógica para manejar el evento. El tipo de evento está determinado por el valor de OFX::InstanceChangedArgs::reason y puede ser: eChangeUserEdit, eChangePluginEdit, eChangeTime: el evento ocurrió como resultado de que una propiedad fue editada por el usuario, modificada en un complemento o aplicación host, o como resultado de un cambio en el cronograma.
El segundo parámetro especifica el nombre de la cadena que definimos en la etapa de inicialización del complemento; en nuestro caso, es un parámetro: false_color_enabled_check_box .
Puede leer la implementación de la interacción interactiva con OFX en el código Interaction.cpp . Como puede ver, recibimos punteros a los clips: el de origen y el área de memoria en la que colocaremos la transformación de destino.
Añadiremos otra capa lógica sobre la que definiremos toda la lógica para lanzar la transformación. En nuestro caso, este es el único método de anulación hasta el momento:
En la etapa de lanzamiento, tuvimos a nuestra disposición un objeto con propiedades útiles: tenemos al menos un puntero a la transmisión de video (más precisamente, un área de memoria con datos de imágenes de fotogramas) y, lo más importante, una cola de comandos de Metal.
Ahora podemos construir una clase genérica que nos acercará a una forma simple de reutilizar el código del kernel. La extensión OpenFXS ya tiene dicha clase: OFX::ImageProcessor; sólo necesitamos sobrecargarlo.
En el constructor tiene el parámetro OFX::ImageEffect, es decir, en él recibiremos no solo el estado actual de los parámetros del complemento, sino también todo lo necesario para trabajar con la GPU.
En esta etapa, sólo necesitamos sobrecargar el método ProcessImagesMetal() e iniciar el procesamiento de los kernels ya implementados en 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()); } }
Para construir el proyecto, necesitará CMake y debe tener al menos la versión 3.15. Además, necesitará Qt5.13, que ayuda a ensamblar fácil y convenientemente el paquete con el instalador del complemento en el directorio del sistema. Para iniciar cmake, primero debe crear un directorio de compilación.
Después de crear el directorio de compilación, puede ejecutar el siguiente comando:
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
Luego, el instalador, llamado IMFalseColorOfxInstaller.app , aparecerá en el directorio que especificó en el parámetro PLUGIN_INSTALLER_DIR . ¡Sigamos adelante y lancemos! Una vez que la instalación sea exitosa, podrá iniciar DaVinci Resolve y comenzar a utilizar nuestro nuevo complemento.
Puede encontrarlo y seleccionarlo en el panel OpenFX en la página de corrección de color y agregarlo como nodo.
enlaces externos