OFX, também conhecido como OFX Image Processing API , é um padrão aberto para criação de efeitos visuais 2D e composição de vídeo. Ele opera em um modelo de desenvolvimento de aplicativos semelhante a um plugin. Essencialmente, ele serve tanto como Host - um aplicativo que fornece um conjunto de métodos, quanto como Plug-in - um aplicativo ou módulo que implementa esse conjunto.
Essa configuração oferece potencial para expansão ilimitada da funcionalidade do aplicativo host.
Aplicativos como Final Cut X e DaVinci Resolve Studio, a partir da versão 16, oferecem suporte total aos pipelines Apple Metal. Semelhante ao OpenCL e Cuda, no caso do OFX, você pode obter um descritor ou manipulador de uma fila de comandos específica da plataforma. O sistema host também assume a responsabilidade de alocar um conjunto dessas filas e equilibrar os cálculos sobre elas.
Além disso, ele coloca os dados do clipe de imagem de origem e de destino na memória da GPU, simplificando significativamente o desenvolvimento de funcionalidades extensíveis.
Com o Resolve, as coisas são um pouco mais complicadas. DaVinci anuncia suporte para OFX v1.4, embora com algumas limitações. Especificamente, alguns métodos para trabalhar com funções de interface não estão disponíveis para uso. Para determinar qual método está disponível, o OFX permite examinar o conjunto compatível por meio de consultas de chave/valor.
Os métodos de publicação no código do plugin são baseados em chamadas C. Mas usaremos o shell OpenFXS C++ adaptado para C++17. Por conveniência, compilei tudo em um repositório: dehancer-external retirado do projeto Dehancer de código aberto.
Neste projeto, usarei OpenFXS, uma extensão C++ para OpenFX que foi originalmente escrita por Bruno Nicoletti e que se tornou popular ao longo do tempo em projetos comerciais e de processamento de vídeo de código aberto.
O OpenFXS original não foi adaptado aos dialetos C++ modernos, então eu o atualizei para torná-lo compatível com C++17 .
OFX e, consequentemente, OFXS, é um módulo de software independente que é carregado dinamicamente pelo programa host. Essencialmente, é uma biblioteca dinâmica que é carregada quando o aplicativo principal é iniciado. OpenFXS, assim como OFX, deve publicar assinaturas de métodos. Portanto, usamos um método C do código.
Para começar a desenvolver no OpenFXS, você precisa concordar com alguns conjuntos comuns de classes que são usados para criar novas funcionalidades em seu aplicativo. Normalmente, em um novo projeto, você precisa herdar dessas classes e implementar ou substituir alguns métodos virtuais.
Para criar seu próprio plugin no sistema host, vamos começar nos familiarizando com as seguintes classes públicas e o mesmo método:
Um recurso que distingue o processo de gravação de vídeo da simples captura de uma imagem em uma foto é a mudança dinâmica de cenas e a iluminação das cenas como um todo e de áreas da imagem. Isso determina a forma como a exposição é controlada durante o processo de filmagem.
No vídeo digital, existe um modo de monitor de controle para operadores, no qual o nível de exposição das áreas é mapeado em um conjunto limitado de zonas, cada uma tingida com sua própria cor.
Este modo às vezes é chamado de modo "predador" ou cor falsa. As escalas são geralmente referenciadas à escala IRE.
Esse monitor permite ver as zonas de exposição e evitar erros significativos ao definir os parâmetros de disparo da câmera. Algo semelhante em significado é usado na exposição em fotografia - zoneamento de acordo com Adams, por exemplo.
Você pode medir um alvo específico com um medidor de exposição e ver em que zona ele está localizado, e em tempo real vemos as zonas, bem coloridas para facilitar a percepção.
O número de zonas é determinado pelos objetivos e capacidades do monitor de controle. Por exemplo, um monitor usado com câmeras Arri Alexa pode incorporar até 6 zonas.
Antes de prosseguir com o exemplo, precisamos adicionar algumas classes de proxy simples para implementar o OpenFXS como uma plataforma para processamento de dados de origem, como texturas Metal. Essas aulas incluem:
imetalling::Image2Texture : Um functor para transferir dados do buffer de clipe para uma textura Metal. Do DaVinci, você pode extrair um buffer de qualquer estrutura e empacotamento de valores de canal de imagem para o plugin, e ele deve ser retornado de forma semelhante.
Para facilitar o trabalho com o formato de fluxo no OFX, você pode solicitar ao host que prepare dados de um tipo específico com antecedência. Usarei carros alegóricos embalados em RGBA - vermelho/verde/azul/alfa.
Herdamos as classes base OFXS e escrevemos nossa funcionalidade sem entrar em detalhes de como o núcleo Metal funciona:
Além disso, precisaremos de várias classes utilitárias construídas sobre Metal para separar logicamente o código host e o código do kernel no MSL. Esses incluem:
imetalling::FalseColorKernel : Nossa principal classe funcional, um emulador "predador" que posteriza (reduz a resolução) para um número especificado de cores.
O código do kernel para o modo "predador" poderia ser assim:
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); }
Inicialização do plugin OFX
Começaremos definindo a classe imetalling::falsecolor::Factory.
Nesta aula, definiremos um único parâmetro - o status do monitor (ligado ou desligado). Isto é necessário para o nosso exemplo.
Herdaremos de OFX::PluginFactoryHelper
e sobrecarregaremos cinco métodos:
ImageEffectDescriptor
.
descriptionInContext(ImageEffectDescriptor&,ContextEnum) : Semelhante ao método describe
, este método também é chamado quando o plugin é carregado e deve ser definido em nossa classe. Deve definir propriedades associadas ao contexto atual.
O contexto determina o tipo de operações com as quais o aplicativo trabalha, como filtro, pintura, efeito de transição ou retimer de quadro em um clipe.
ImageEffect
. Em outras palavras, nosso imetalling::falsecolor::Plugin
no qual definimos todas as funcionalidades, tanto no que diz respeito aos eventos do usuário no programa host quanto na renderização (transformação) do quadro de origem no quadro de destino: OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) { return new Plugin(handle); }
Nesta fase, se você compilar um pacote com o módulo OFX, o plugin já estará disponível na aplicação host e, no DaVinci, poderá ser carregado no nó de correção.
No entanto, para trabalhar totalmente com uma instância de plug-in, você precisa definir pelo menos a parte interativa e a parte associada ao processamento do fluxo de vídeo recebido.
Para fazer isso, herdamos da classe OFX::ImageEffect e sobrecarregamos os métodos virtuais:
changeParam(const OFX::InstanceChangedArgs&, const std::string&) - Este método nos permite definir a lógica de tratamento do evento. O tipo de evento é determinado pelo valor de OFX::InstanceChangedArgs::reason e pode ser: eChangeUserEdit, eChangePluginEdit, eChangeTime - o evento ocorreu como resultado de uma propriedade sendo editada pelo usuário, alterada em um plugin ou aplicativo host, ou como resultado de uma mudança na linha do tempo.
O segundo parâmetro especifica o nome da string que definimos na fase de inicialização do plugin, no nosso caso, é um parâmetro: false_color_enabled_check_box .
Você pode ler a implementação da interação interativa com OFX no código Interaction.cpp . Como você pode ver, recebemos ponteiros para os clipes: o de origem e a área de memória na qual colocaremos a transformação de destino.
Adicionaremos outra camada lógica na qual definiremos toda a lógica para lançar a transformação. No nosso caso, este é o único método de substituição até agora:
Na fase de lançamento, um objeto com propriedades úteis ficou disponível para nós: temos pelo menos um ponteiro para o stream de vídeo (mais precisamente, uma área de memória com dados de imagem de quadro) e, o mais importante, uma fila de comandos Metal.
Agora podemos construir uma classe genérica que nos aproximará de uma forma simples de reutilizar o código do kernel. A extensão OpenFXS já possui essa classe: OFX::ImageProcessor; só precisamos sobrecarregá-lo.
No construtor possui o parâmetro OFX::ImageEffect, ou seja, nele receberemos não apenas o estado atual dos parâmetros do plugin, mas tudo o que é necessário para trabalhar com a GPU.
Nesta fase, basta sobrecarregar o método processImagesMetal() e iniciar o processamento dos kernels já implementados no 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 o projeto, você precisará do CMake, e deve ser pelo menos a versão 3.15. Além disso, você precisará do Qt5.13, que auxilia na montagem fácil e conveniente do pacote com o instalador do plugin no diretório do sistema. Para iniciar o cmake, você deve primeiro criar um diretório de construção.
Depois de criar o diretório de construção, você pode executar o seguinte 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
Posteriormente, o instalador, denominado IMFalseColorOfxInstaller.app , aparecerá no diretório que você especificou no parâmetro PLUGIN_INSTALLER_DIR . Vamos em frente e lançá-lo! Assim que a instalação for bem-sucedida, você poderá iniciar o DaVinci Resolve e começar a usar nosso novo plugin.
Você pode localizá-lo e selecioná-lo no painel OpenFX na página de correção de cores e adicioná-lo como um nó.
links externos