[ << ] [ >> ]           [Top] [Contents] [Índice] [ ? ]

23. Autoração de plugin

A API de plugin no Cinelerra data de 1997, antes de o LADSPA e antes de o VST se tornarem populares. Ela é fundamentalmente a mesma que era em 1997, com modificações menores para suportar quadros-chave e resposta de interface GUI. O GUI não é abstraído do programador. Isso permite ao programador usar a ferramenta que quiser e permite mais flexibilidade na aparência, mas o custo é mais alto.

Há vários tipos de plugins, cada um com um procedimento comum de implementação e mudanças específicas para aquele tipo particular. O jeito mais fácil de implementar um plugin é pegar o plugin mais simples existente no grupo e renomear os símbolos.


23.1 Introduzindo o método pull

Originalmente, os plugins eram desenhados com o método de empurrar ("push"). O método push é intuitivo e simples. Uma fonte empurra ("pushes") a informação para um plugin, o plugin faz operações matemáticas nela e o plugin a empurra para um destino. Por 6 anos esta era a forma que todos os plugins de tempo-real eram desenhados internamente, mas ele não te permitia reduzir a taxa de reprodução em tempo-real. Embora os plugins ainda possam ser desenhados como se estivessem empurrando a informação, este não é mais o jeito que eles são processados internamente.

A última evolução no desenho de plugins do Cinelerra é o método de puxar ("pull"). A linha de renderização se inicia na saída final e os passos finais na linha de renderização são os de ler a informação do disco. Cada passo na cadeia de renderização envolve pedir informações do passo anterior. Quando a cadeia de renderização eventualmente requere informação de uma cadeia de plugins, cada plugin requere informações do plugin que o antecede.

Isso é menos intuitivo do que o método push, mas é mais poderoso. Plugins tempo-real escritos usando o método pull podem mudar a razão na qual a informação é apresentada ao espectador e a direção da reprodução. O método pull permite que os plugins tragam informações para eles a uma taxa mais alta do que eles a enviam para fora.

Para conseguir o poder de independência de taxa, o método pull requere que os plugins saibam mais sobre a informação do que precisavam sob o método push. Plugins precisam saber a qual taxa o projeto está, a qual taxa sua saída supostamente deve ser e a qual taxa sua entrada supostamente deve estar. Essas taxas diferentes de informação precisam estar correlacionadas para que um plugin se configure devidamente.

Quadros-chave para um plugin são armazenados relativos à taxa de quadros do projeto. Buscas de informações de um plugin para a posição de reprodução atual são dadas relativas à taxa de quadros do projeto. Se foi requerido ao plugin que sua saída esteja a duas vezes a taxa de quadros do projeto, as posições precisam ser convertidas para a taxa do projeto para que os quadros-chave se adequem. Duas classes de taxas de informação foram criadas para lidar com este problema.

Conversões de taxas são feitas em termos da taxa do projeto e da taxa requisitada. A taxa do projeto é idêntica para todos os plugins. Ela é determinada pela janela de configurações->formato. A taxa requisitada é determinada pelo plugin mais abaixo ("downstream") requisitando informações do plugin atual. A taxa requisitada é arbitrária. Exatamente como usar essas taxas está descrito abaixo.


23.2 Funções comuns de plugin

Todos os plugins provêm de um derivativo do PluginClient. Estes derivatidos do PluginClient implementam a maioria dos métodos requeridos no PluginClient, mas usuários ainda definem métodos para o PluginClient. Os métodos mais comumente usados são pré-definidos em macros para reduzir a digitação mas ainda assim permitir flexibilidade.

Os arquivos que eles incluem dependem do tipo de plugin. Plugins de áudio incluem `pluginaclient.h' e plugins de vídeo incluem `pluginvclient.h'. Eles são herdeiros do PluginAClient e do PluginVClient respectivamente.

O Cinelerra faz todos os plugins passarem por pelo menos duas instâncias quando eles são usados em um vídeo. Uma instância é o GUI. A outra é o processador de sinal. A entrada do usuário, por meio de uma seqüência complicada, é propagada a partir da instância GUI para a instância do processador de sinal. Se o processador de sinal quiser alterar o GUI, ele propaga a informação de volta ao GUI. Há funções de ferramentas para se fazer isso.

Todos os plugins definem pelo menos três objetos:


23.2.1 O objeto de processamento

Carregue um plugin simples como o Ganho para ver como esse objeto se parece. O objeto de processamento deve ser herdado do derivativo PluginClient desejado. Este construtor deve levar um argumento do PluginServer.
MyPlugin(PluginServer *server);

Na implementação, o plugin deve conter uma linha de registro com o nome do objeto de processamento, como
REGISTER_PLUGIN(MyPlugin)

O construtor deve conter
PLUGIN_CONSTRUCTOR_MACRO
para inicializar as variáveis mais comuns.

O objeto de processamento deve ter um destrutor contendo
PLUGIN_DESTRUCTOR_MACRO
para apagar as variáveis mais comuns.

Outra função que é útil mas não obrigatória é
int is_multichannel();
Ela deve retornar 1 se uma instância do plugin suporta múltiplas trilhas simultaneamente ou 0 se uma instância do plugin suporta apenas uma trilha. O padrão é 0 se ela estiver omitida.

Plugins multi-canais em sua função de processamento devem se referir a uma função chamada PluginClient::get_total_buffers() para determinar o número de canais.

Para simplificar a implementação de plugins tempo-real, um macro para membros comumente usados foi criado para o cabeçalho de classe ("class header"), levando o objeto de configuração e o assunto ("thread") do objeto da interface de usuário como argumentos. As definições macro se aplicam principalmente a plugins tempo-real e não são úteis em plugins não-tempo-real. Felizmente, plugins não-tempo-real são mais simples.

PLUGIN_CLASS_MEMBERS(config_name, thread_name)

Os membros comumente usados em PLUGIN_CLASS_MEMBERS estão descritos abaixo.

Funções importantes que o objeto de processamento deve definir são as funções que carregam e salvam informações de configuração dos quadros-chave. Essas funções são chamadas pelos macros então tudo o que você precisa se preocupar é em acessar a informação do quadro-chave.
void save_data(KeyFrame *keyframe);
void read_data(KeyFrame *keyframe);

As funções de leitura de informação são usadas apenas nos plugins tempo-real. As funções de leitura de informações traduzem ("translate") a configuração do plugin entre o argumento do KeyFrame e o objeto de configuração para o plugin. Os quadros-chave são armazenados na linha do tempo e podem mudar para cada projeto.

Use um objeto chamado FileXML para fazer toda a readução ("translation") e alguns comandos específicos para obter as informações do argumento do KeyFrame. Veja algum plugin existente para ver o uso de KeyFrame e FileXML.
int load_defaults();
int save_defaults();

As funções de carregar padrões são usadas em plugins tempo-real e plugins não-tempo-real. As funções de carregar padrões traduzem ("translate") as configurações de plugin entre um objeto BC_Hash e a configuração do plugin. O objeto BC_Hash armazena configurações em um arquivo discreto no disco para cada plugin, mas não isola configurações diferentes para projetos diferentes.

A função sobrescrevendo o load_defaults também necessita criar o objeto BC_Hash. Veja um plugin existente para ver o uso do BC_Hash.

Outros membros padrão podem ser definidos no objeto de processamento, dependendo do tipo de plugin.


23.2.2 O objeto de configuração

O objeto de configuração é crítico para atualizações de GUI, processamento de sinal e configurações padrão em plugins tempo-real. Assegure-se que ele não seja usado em plugins não-tempo-real. O objeto de configuração não é herdado de nada e não possui dependências. Ele é simplesmente uma classe ("class") contendo três funções e variáveis específicas aos parâmetros dos plugins.

Normalmente, o objeto de configuração começa com o nome do plugin seguido por Config.

    class MyPluginConfig
    {
    public:
        MyPluginConfig();

Seguindo o nome da classe de configuração, colocamos as três funções requeridas e as variáveis de configuração.

        int equivalent(MyPluginConfig &that);
        void copy_from(MyPluginConfig &that);
        void interpolate(MyPluginConfig &prev,
        MyPluginConfig &next,
        int64_t prev_position,
        int64_t next_position,
        int64_t current_position);
        float parameter1;
        float parameter2;
        int parameter3;
    };

Agora você deve definir as três funções. Equivalent é chamado pelo LOAD_CONFIGURATION_MACRO para determinar se os parâmetros de configuração local são idênticos aos parâmetros de configuração no argumento. Se o equivalent retornar 0, o LOAD_CONFIGURATION_MACRO faz com que o GUI seja redesenhado. Se o equivalent retornar 1, o LOAD_CONFIGURATION_MACRO não redesenha o GUI.

Então, há o copy_from que transfere os valores de configuração do argumento para as variáveis locais. Isso é novamente usado em LOAD_CONFIGURATION_MACRO para armazenar configurações nos temporários. Uma vez que o LOAD_CONFIGURATION_MACRO tenha replicado a configuração, ele carrega uma segunda configuração. Então, ele interpola as duas configurações para obter a configuração atual. A função de interpolação faz a interpolação e armazena o resultado nas variáveis locais.

Normalmente, a função de interpolação calcula uma fração prévia e próxima, usando os argumentos.

    void MyPluginConfig::interpolate(MyPluginConfig &prev,
        MyPluginConfig &next,
        int64_t prev_position,
        int64_t next_position,
        int64_t current_position
    {
        double next_scale =
        (double)(current_position - prev_position)
        / (next_position - prev_position);
        double prev_scale =
        (double)(next_position - current_position) /
        (next_position - prev_position);

Então as frações são aplicadas às variáveis de configuração prévias e seguintes para chegar-se aos valores atuais.

        this->parameter1 =
        (float)(prev.parameter1 * prev_scale
        + next.parameter1 * next_scale);
        this->parameter2 =
        (float)(prev.parameter2 * prev_scale
        + next.parameter2 * next_scale);
        this->parameter3 =
        (int)(prev.parameter3 * prev_scale
        + next.parameter3 * next_scale);
    }

Alternativamente, você pode copiar os valores do argumento de configuração prévio se nenhuma interpolação for desejada.

Este uso do objeto de configuração é o mesmo em plugins de áudio e de vídeo. Na reprodução do vídeo, a função de interpolação é chamada para cada quadro, resultando em uma interpolação suave. Na reprodução de áudio, a função de interpolação é chamada apenas uma vez para cada fragmento de console e uma vez a cada vez que o ponto de inserção se move. Isso é bom o suficiente para atualizar o GUI enquanto se seleciona regiões na linha do tempo, mas pode não ser preciso o suficiente para uma renderização realmente suave do efeito.

Para uma renderização realmente suave do áudio, você ainda pode usar load_configuration quando atualizando o GUI. Para process_buffer, entretanto, ignore o load_configuration e escreva sua própria rotina de interpolação que carrega todos os quadros-chave em um fragmento de console e interpola cada amostra. Isso seria muito lento e difícil de se fazer um debug, resultando em uma melhoria que pode não ser audível. Porém, claro, cada país tem seus próprios malucos.

Um modo mais fácil de se conseguir uma interpolação mais suave é reduzir o fragmento de console para 1 sample. Isso teria que ser renderizado e reproduzido com o fragmento de console novamente acima de 2048, claro. Os drivers de áudio GNU/Linux não conseguem reproduzir fragmentos de 1 sample.


23.2.3 O objeto da interface de usuário

O objeto da interface de usuário em seu mínimo consiste em um apontador para uma janela e apontadores para todos as funções ("widgets") na janela. Usando as ferramentas do Cinelerra, ele consiste em um derivativo da BCWindow e um derivativo de Thread. O derivativo de Thread é declarado no cabeçalho do plugin usando
PLUGIN_THREAD_HEADER(plugin_class, thread_class, window_class)

Então ele é definido usando
PLUGIN_THREAD_OBJECT(plugin_class, thread_class, window_class)

Isso, em combinação com o macro SHOW_GUI faz todo o trabalho de instanciamento da Janela. Esse sistema de duas classes é usado em plugins tempo-real mas não em plugins não-tempo-real. Plugins não-tempo-real criam e destróem seu GUI em sua função get_parameters e não há necessidade de uma Thread.

Agora a classe de janela deve ser declarada no cabeçalho do plugin. É mais fácil implementar a janela copiando um plugin existente e renomeando os símbolos. O seguinte é um esboço do que acontece. O cabeçalho do plugin deve declarar o construtor de janela usando os argumentos apropriados.

    #include "guicast.h"
    class MyPluginWindow : public BC_Window
    {
    public:
        MyPluginWindow(MyPluginMain *plugin, int x, int y);

Isso se torna uma janela na tela, posicionada em x e y.

Ela precisa de dois métodos
int create_objects();
int close_event();
e um apontador de volta para o plugin
MyPlugin *plugin;

A definição do construtor deve conter extenções ("extents") e bandeiramentos ("flags") fazendo com que a janela seja escondida quando criada pela primeira vez. O membro create_objects põe funções ("widgets") na janela de acordo com a sintaxe do GuiCast. Um apontador para cada função que você quer sincronizar para um parâmetro de configuração é armazenado na classe de janela. Esses são atualizados na função update_gui que você definiu mais cedo para o plugin. As funções são normalmente derivativos de uma função do GuiCast e sobrescrevem funções no GuiCast para suportar eventos. Finalmente, o create_objects chama
show_window();
flush();
para fazer a janela aparecer toda de uma vez.

O membro close_event deve ser implementado usando
WINDOW_CLOSE_EVENT(window_class)

Cada função no GUI precisa detectar quando seu valor muda. No GuiCast, o método handle_event é chamado sempre que o valor mudar. No handle_event, a função então precisa chamar plugin->send_configure_change() para propagar a mudança para quaisquer cópias do plugin que estiverem processando informação.


23.3 Plugins tempo-real

Plugins tempo-real devem usar PLUGIN_CLASS_MEMBERS para definir o conjunto básico de membros em seus cabeçalhos. Todos os plugins tempo-real devem definir um
int is_realtime()

membro retornando 1. Isso faz co mque um número de métodos seja chamado durante a reprodução ao vivo e para que o plugin possa ser usável na linha do tempo.

Plugins tempo-real devem sobrescrever um membro chamado
process_buffer

Esta função leva argumentos diferentes dependendo se o plugin suporta vídeo e áudio. Veja um plugin existente para descobrir quais usos se aplicam.

As principais características da função process_buffer são um buffer para armazenar a saída, a posição de início da saída e a taxa requerida de saída. Para áudio, também há um argumento de tamanho para o número de amostras.

A posição de início do buffer de saída é a amostra de menor número na linha do tempo se a reprodução for para frente e a amostra de maior número na linha do tempo se a reprodução for ao reverso. A direção a reprodução é determinada por uma das buscas de informação descritas abaixo.

Os argumentos de posição e tamanho são todos relativos à taxa de quadros e taxa de amostragem passados ao process_buffer. Este será a taxa de informação requerida e não pode ser o mesmo que a taxa de informação do projeto.

A função process_realtime deve ser inciada chamando o load_configuration. O LOAD_CONFIGURATION_MACRO retorna 1 se a configuração houver mudado.

Após determinar a configuração do plugin, uma mídia de entrada tem que ser carregada para processamento. Chame:

    read_frame(VFrame *buffer,
        int channel,
        int64_t start_position,
        double frame_rate)
ou
    read_samples(double *buffer,
        int channel,
        int sample_rate,
        int64_t start_position,
        int64_t len)

para requisitar informação de entrada do objeto que vem antes deste plugin. A função de leitura precisa de um buffer para armazenar a informação de entrada. Isso pode ser ou um temporário que você crie no plugin ou o buffer de saída fornecido ao process_buffer se você não precisa de um temporário.

Ele também precisa de um conjunto de argumentos de posição para determinar quando você quer ler a informação de onde. A posição de início, taxa e len passados para uma função de leitura não precisam ser os mesmos que os valores recebidos pela função process_buffer. Desta forma, os plugins podem ler informação a uma taxa diferente do que a informação de saída.

O argumento de canal é apenas significante se este for um plugin multi-canal. Eles precisam ler a informação para cada trilha no valor de get_total_buffers() e processar todas as trilhas. Plugins de canais únicos devem passar 0 para channel.

Membros adicionais são implementados para manter a configuração em plugins tempo-real. Algum deles também são requeridos em plugins não-tempo-real.


23.4 Plugins não-tempo-real

Algumas referências para plugins não-tempo-real são Normalizar para áudio e Mudar taxa de quadros para vídeo.

Como os plugins tempo-real, load_defaults e save_defaults devem ser implementados. Nos plugins não-tempo-real, eles não são usados apenas para parâmetros padrão, mas para transferir valores da interface de usuário para o processador de sinal. Não é preciso que haja uma classe de configuração nos plugins não-tempo-real.

Contrário aos plugins tempo-real, o LOAD_CONFIGURATION_MACRO não pode ser usado no cabeçalho do plugin. Ao invés disso, os seguintes métodos devem ser definidos.

O plugin não-tempo-real deve conter um apontador para um objeto de defaults.
BC_Hash *defaults;
Ele também deve ter um apontador para uma MainProgressBar.
MainProgressBar *progress;

O apontador de progresso permite que plugins não-tempo-real exibam seus progressos na janela principal do Cinelerra.

O construtor para um plugin não-tempo-real não pode usar o PLUGIN_CONSTRUCTOR_MACRO mas deve chamar o load_defaults diretamente.

O destrutor, de forma semelhante, deve chamar o save_defaults e o delete defaults diretamente ao invés do PLUGIN_DESTRUCTOR_MACRO.


23.5 Plugins de áudio

O plugin mais simples de áudio é o Ganho. O objeto de processamento deve incluir o `pluginaclient.h' e ser herdado do PluginAClient. Plugins tempo-real de áudio devem definir

    int process_buffer(int64_t size,
        double **buffer,
        int64_t start_position,
        int sample_rate);
caso sejam multi-canais ou 
    int process_buffer(int64_t size,
        double *buffer,
        int64_t start_position,
        int sample_rate);

caso sejam de um único canal. Estes devem retornar 0 ao sucesso ou 1 à falha. No futuro, o valor de retorno pode abortar uma renderização falha.

A função de processamento precisa requerir amostras de entrada com

    int read_samples(double *buffer,
        int channel,
        int sample_rate,
        int64_t start_position,
        int64_t len);

Isto sempre retorna um 0. O usuário pode especificar qualquer taxa de amostragem desejada e posição de início.

Plugins de áudio não-tempo-real precisam definir

int process_loop(double *buffer, int64_t &write_length);
para cana único ou 
int process_loop(double **buffers, int64_t &write_length);

para multi-canal. Plugins não-tempo-real usam um conjunto diferente de funções read_samples para requerer a informação de entrada. Estas são fixadas à taxa de amostragem do projeto.


23.6 Plugins de vídeo

O plugin mais simples de vídeo é o Inverter. O objeto de processamento deve incluir o `pluginvclient.h' e ser herdado do PluginVClient. Plugins de vídeo tempo-real precisam definir

    int process_buffer(VFrame **frame,
        int64_t start_position,
        double frame_rate);
caso sejam multi-canais ou
    int process_buffer(VFrame *frame,
        int64_t start_position,
        double frame_rate);

caso sejam de canal único.

Plugins de vídeo não-tempo-real precisam definir

int process_loop(VFrame *buffer);
para canal único ou
int process_loop(VFrame **buffers);

para multi-canal. A quantidade de quadros gerada em um process_loop único é sempre assumida para ser 1, portanto a falta de um argumento write_length. Um retorno de 0 faz com que a renderização continue. Um retorno de 1 faz com que a renderização seja abortada.

Um conjunto de funções read_frame existem para requerer os quadros de entrada em plugins de vídeo não-tempo-real. Estes são fixados à taxa de quadros do projeto.


23.7 Plugins de transição

A transição mais simples de vídeo é a Varredura e a transição de áudio mais simples é o Fade cruzado. Estes usam um subconjunto dos membros de classe padrão de plugins tempo-real, mas até agora nenhum análogo a PLUGIN_CLASS_MEMBERS foi feito para transições.

O objeto de processamento para transições de áudio ainda deve ser herdado do PluginAClient e para transições de vídeo ainda deve ser herdado do PluginVClient.

Transições podem ou não ter um GUI. Caso elas tenham um GUI, devem também gerenciar um assunto ("thread") como plugins tempo-real. Faça isso com os mesmos macros PLUGIN_THREAD_OBJECT e PLUGIN_THREAD_HEADER como plugins tempo-real. Uma vez que há apenas um quadro-chave em uma transição, você não precisa se preocupar em atualizar o GUI do objeto de processamento como você faz com um plugin tempo-real.

Se a transição tiver um GUI, você pode usar o PLUGIN_CONSTRUCTOR_MACRO e o PLUGIN_DESTRUCTOR_MACRO para inicializar o objeto de processamento. Você também vai precisar de um objeto BC_Hash e um objeto Thread para estes macros.

Uma vez que o GUI é opcional, sobrescreva uma função chamada uses_gui() para significar se a transição terá ou não um GUI. Retorno de 1 caso tenha e 0 caso não tenha.

Transições precisam de funções load_defaults e save_defaults de forma que a primeira vez que forem jogadas à linha do tempo elas tenham configurações úteis.

Uma função read_data e save_data toma o comando após a inserção para acessar informação específica a cada instância da transição.

A diferença mais importante entre as transições e os plugins tempo-real é a adição de um método is_transition no objeto de processamento. is_transition deve retornar 1 para significar que o plugin é uma transição.

As transições processam informação em uma função process_realtime.

    int process_realtime(VFrame *input,
        VFrame *output);
    int process_realtime(int64_t size,
        double *input_ptr,
        double *output_ptr);

O argumento de entrada para o process_realtime é a informação para a próxima edição. O argumento de saída para o process_realtime é a informação da edição anterior.

Rotinas existem para determinar se você está relacionado ao início e final da transição.

Usuários devem dividir a posição fonte pela abrangência total para obter a fração da transição onde a função process_realtime está.

As transições rodam a taxa de informação requerida pelo primeiro plugin da trilha. Isso pode ser diferente da taxa de informações do projeto. Uma vez que o process_realtime não possui um argumento de taxa, use get_framerate() ou get_samplerate para obter a taxa requerida.


23.8 GUI de plugins que se atualizam durante a reprodução

Efeitos como Histograma e Escopo de vídeo precisam atualizar o GUI durante a reprodução para exibir a informação sobre o sinal. Isso é alcançado com os métodos send_render_gui e render_gui. Normalmente em process_buffer, quando o objeto de processamento quer atualizar o GUI, ele deve chamar send_render_gui. Isso deveria ser chamado apenas no process_buffer. O send_render_gui faz uma busca e eventualmente chama o render_gui na instância do GUI do plugin.

O render_gui deve ter uma seqüência como

    void MyPlugin::render_gui(void *data)
    {
        if(thread)
        {
        thread->window->lock_window();
        // update GUI here
        thread->window->unlock_window();
        }
    }

Send_render_gui e render_gui usam um argumento, um apontador void para transferir informação do objeto de processamento para o GUI. O usuário deve fazer uma digitação ("typecast") para este apontador para transformá-lo em algo útil.


23.9 Busca de informações dos plugins

Há várias buscas de informações ("queries") úteis no PluginClient que podem ser acessadas a partir do objeto de processamento. Algumas delas possuem diferentes significados nos modos tempo-real e não-tempo-real. Elas todas dão informações sobre o sistema operacional ou o porjeto que podem ser usadas para melhorar a qualidade do processamento.


23.9.1 Busca de informações de sistema


23.9.2 Busca de informações de tempo

Há duas taxas para mídia que um plugin tempo-real deve saber: a taxa do projeto e a taxa requerida. Funções são providenciadas para se obter as taxas do projeto e requerida. Além disso, fazer efeitos dependentes de tempo requere usar várias funções que dizem onde você está no efeito.


23.10 Usando OpenGL

Plugins tempo-real de vídeo suportam OpenGL. Usar o OpenGL para rotinas de plugins pode acelerar bastante a reprodução já que o trabalho é feito principalmente no hardware. Infelizmente, cada rotina de OpenGL precisa de uma contrapartida de software para renderização, dobrando a quantidade de software a ser mantida. Felizmente, ter uma rotina OpenGL significa que a versão do software não precisa ser otimizada como precisava quando software era a única forma de se fazer as coisas.

Como sempre, a melhor forma de desenhar um primeiro plugin OpenGL é copiar um existente e alterá-lo. O plugin Brilho é um plugin OpenGL simples para ser copiado. Há 3 pontos principais na renderização OpenGL e um ponto para otimizar a renderização OpenGL.


23.10.1 Obtendo informação OpenGL

O primeiro problema é fazer os plugins com OpenGL-habilitado interagirem com os plugins apenas-software. Para resolver isso, toda a informação requerida para fazer a reprodução OpenGL é armazenada no objeto VFrame que é passado para o process_buffer. Para suportar 3D, o VFrame contém um PBuffer e uma textura, além das fileiras originais do VFrame.

No modo OpenGL, o VFrame tem 3 estados correspondendo à localização de sua informação de vídeo. O estado opengl é recuperado chamando-se o get_opengl_state e é configurado chamando-se o set_opengl_state. Os estados são:

Na rotina do process_buffer do plugin, há normalmente uma chamada para o read_frame para obter informação do plugin anterior da cadeia. O read_frame leva um novo parâmetro chamado use_opengl.

O plugin passa 1 para o use_opengl caso deseje suportar a informação usando OpenGL. Ele passa 0 para o use_opengl caso possa suportar a informação somente usando software. O valor de use_opengl é passado para cima na cadeia para assegurar que um plugin que apenas use software receba informação apenas em seus apontadores de fileiras ("row pointers"). Se o use_opengl for 0, o estado opengl no VFrame é RAM.

O plugin não deve apenas saber se ele é software-apenas, mas se sua saída deve ser software-apenas. Chame get_use_opengl para determinar se a saída pode ser suportada pelo OpenGL. Caso o get_use_opengl reture 0, o plugin deve passar 0 para o use_opengl em read_frame e fazer seu processamento no software. Caso o get_use_opengl seja 1, o plugin pode decidir baseado em sua implementação se usará OpenGL.

O maior problema com o OpenGL é que todas as chamadas do gl... precisam ser rodadas a partir do mesmo assunto ("thread"). Para contornar isso, a interface do plugin possui rotinas para rodar o OpenGL em um assunto comum.

O run_opengl transfere o controle para o assunto comum OpenGL. Isso é normalmente chamado pelo plugin em process_buffer após ele haver chamado o read_frame e apenas se o get_use_opengl for 1.

Por uma série de indireções, o run_opengl eventualmente transfere o controle para uma função virtual chamada handle_opengl. O handle_opengl deve ser sobrescrito com uma função para rodar todas as rotinas OpenGL. Os conteúdos do handle_opengl devem ser fechados em #ifdef HAVE_GL ... #endif para permitir que eles sejam compiladom em sistemas que não possuam suporte a gráficos, como nódulos de renderização. O valor de retorno do handle_opengl é passado de volta do run_opengl.

O read_frame não pode ser chamado de dentro do handle_opengl. Isso criaria uma trava recursiva porque faria outros objetos chamarem o run_opengl.

Uma vez dentro do handle_opengl, o plugin possui o uso completo de todas as funções OpenGL. O VFrame oferece algumas funções para automatizar seqüências comuns de OpenGL.

O argumento do VFrame para o process_buffer é sempre disponível via a função get_output(int layer). Caso o plugin seja multi-canal, o argumento de camada ("layer") obtém uma camada específica dos buffers de saída. O PBuffer do buffer de saída é onde a saída do OpenGL deve ir caso qualquer processamento seja feito.


23.10.2 Desenhando usando OpenGL

A seqüência de comandos para desenhar na saída do PBuffer começa com fazer o vídeo em uma área de memória onde ele possa ser re-chamado para desenho:
get_output()->to_texture();
get_output()->enable_opengl();

O próximo passo é desenhar a textura com algum processamento no PBuffer. A seqüência normal de comandos para desenhar uma textura é:
get_output()->init_screen();
get_output()->bind_texture(0);
get_output()->draw_texture();

O último passo na rotina do handle_opengl, após a textura ser desenhada no PBuffer, é marcar a saída do estado opengl para SCREEN com uma chamada para VFrame::set_opengl_state. O plugin não deve ler de volta o buffer de quadro em uma textura ou apontadores de fileira caso não haja mais processamento. Os plugins devem apenas deixar a saída na textura ou RAM caso sua localização resulte de um processamento normal. Eles devem marcar o estado opengl para RAM ou TEXTURE caso o façam.

Modelos de cor em OpenGL:
O modelo de cor exposto a rotinas OpenGL será sempre de ponto flutuante uma vez que é isso que o OpenGL usa, mas ele pode ser YUV ou RGB dependendo das configurações do projeto. Caso seja YUV, ele é deslocado por 0.5 como em software. Passar modelos de cor YUV a plugins era necessário pela velocidade. A outra opção era converter YUV para RGB no primeiro passo que requeria OpenGL. Cada efeito e passo de renderização teria necessitado de uma rotina YUV para RGB. Com o YUV retido, apenas o passo de composição final precisa de uma rotina YUV para RGB.


23.10.3 Usando os sombreadores ("shaders")

Muito poucos efeitos conseguem fazer alguma coisa útil com apenas um desenho direto na textura do PBuffer. Eles normalmente precisam definir um sombreamento ("shader"). O sombreador é um programa em C que roda na placa de gráfico. Uma vez que a placa de gráficos esteja otimizada para gráficos, ela pode ser muito mais rápida do que rodá-lo na CPU.

Sombreadores são escritos na linguagem OpenGL Shading Language. O código-fonte do sombreador é contido em uma cadeia. A seqüência normal para se usar um sombreador vem após uma chamada para o enable_opengl.

char *shader_source = "...";
unsigned char shader_id = VFrame::make_shader(0, shader_source, 0);
glUseProgram(shader_id);
// Set uniform variables using glUniform commands

A compilação e o passo de se fazer liks para os sombreadores é encapsulado pelo comando VFrame::make_shader. Ele retorna um shader_id que pode ser passado para as funções OpenGL. O primeiro e último argumentos devem ser sempre 0. Um número arbitrário de cadeias de fonte pode ser colocado entre os 0s. As cadeias de fonte são concatenadas pelo make_shader em um fonte de sombreador gigante. Caso múltiplas funções principais estejam nos fontes, as funções principais são renomeadas e rodadas em ordem.

Há um número de macros úteis para sombreadores no `playback3d.h'. Todos os sombreadores até agora tem sido sombreadores de fragmento. Após o sombreador ser inicializado, desenhe a textura iniciando pelo init_screen. O programa sombreador deve ser desabilitado com outra chamada para glUseProgram(0) e 0 como argumento.

O shader_id e código fonte são armazenados na memória enquanto o Cinelerra estiver rodando. Chamadas futuras para o make_shader com o mesmo código fonte rodarão muito mais rápido.


23.10.4 Agregando plugins

Melhorias futuras de velocidade podem ser obtidas combinando rotinas OpenGL de dois plugins em uma função única handle_opengl. Isso é feito quando Quadros para campos e RGB para 601 são unidos ("attached") em ordem. Agregações de mais de dois plugins são possíveis mas muito difíceis de se fazer funcionar. A agregação é útil para o OpenGL porque cada plugin deve copiar o vídeo de uma textura para um PBuffer. Em software não há operação de cópia.

Na agregação, um plugin processa tudo dos outros plugins e os outros plugins "fall through". Os plugins "fall through" devem copiar seus parâmetros para o buffer de saída de forma que possam ser detectados pelo plugin de processamento.

O VFrame usado como o buffer de saída contém uma tabela de parâmetro para a passagem de parâmetro entre plugins e é acessado com get_output()->get_params(). Parâmetros são marcados e obtidos na tabela que chama uma atualização ("update") e obtenção ("get") como os padrões ("defaults").

Os plugins "fall through" devem determinar se o plugin de processamento está junto com chamados para next_effect_is e prev_effect_is. Estes pegam o nome do plugin processador como um argumento de cadeia e retornam 1 caso o plugin anterior ou subseqüente é o plugin processador. Caso nenhum deles retorne 1, o plugin "fall through" ainda deve chamar o read_frame para propagar a informação mas fazer um retorno após isso.

O plugin processador deve chamar o next_effect_is e prev_effect_is para determinar se ele está agregado com um plugin "fall through". Caso esteja, deve realizar as operações do plugin "fall through" em sua rotina OpenGL. Os parâmetros para o plugin "fall through" devem estar disponíveis pelo get_output()->get_params() caso o plugin "fall through" os tenha configurado.


[ << ] [ >> ]           [Top] [Contents] [Índice] [ ? ]

This document was generated by Raffaella Traniello on Maio, 30 2008 using texi2html 1.76.