O framework de sincronização descreve explicitamente as dependências entre diferentes operações assíncronas no sistema gráfico do Android. Ele fornece uma API que permite que os componentes indiquem quando os buffers são liberados. O framework também permite que primitivos de sincronização sejam transmitidos entre drivers do kernel para o espaço do usuário e entre os próprios processos do espaço do usuário.
Por exemplo, um aplicativo pode enfileirar o trabalho a ser realizado na GPU. A GPU começa a desenhar essa imagem. Embora a imagem ainda não tenha sido desenhada na memória, o ponteiro do buffer é transmitido ao compositor da janela junto com uma barreira que indica quando o trabalho da GPU será concluído. O compositor da janela começa a processar com antecedência e transmite o trabalho ao controlador de exibição. Da mesma forma, o trabalho da CPU é feito com antecedência. Quando a GPU termina, o controlador de exibição mostra a imagem imediatamente.
O framework de sincronização também permite que os implementadores usem recursos de sincronização nos próprios componentes de hardware. Por fim, o framework oferece visibilidade no pipeline gráfico para ajudar na depuração.
Sincronização explícita
A sincronização explícita permite que os produtores e consumidores de buffers gráficos sinalizem quando terminam de usar um buffer. A sincronização explícita é implementada no espaço do kernel.
Os benefícios da sincronização explícita incluem:
- Menos variação de comportamento entre dispositivos
- Melhor suporte à depuração
- Métricas de teste aprimoradas
O framework de sincronização tem três tipos de objeto:
sync_timelinesync_ptsync_fence
sync_timeline
sync_timeline é uma linha do tempo monotonicamente crescente que os fornecedores precisam implementar para cada instância de driver, como um contexto GL, um controlador de exibição ou um blitter 2D. sync_timeline conta os jobs enviados ao kernel para uma determinada peça de hardware.
sync_timeline garante a ordem das operações e permite implementações específicas de hardware.
Siga estas diretrizes ao implementar sync_timeline:
- Forneça nomes úteis para todos os drivers, linhas do tempo e barreiras para simplificar a depuração.
- Implemente os operadores
timeline_value_strept_value_strnas linhas do tempo para tornar a saída de depuração mais legível. - Implemente o preenchimento
driver_datapara dar às bibliotecas do espaço do usuário, como a biblioteca GL, acesso a dados particulares da linha do tempo, se necessário.data_driverpermite que os fornecedores transmitam informações sobre imutáveissync_fenceesync_ptspara criar linhas de comando com base nelas. - Não permita que o espaço do usuário crie ou sinalize explicitamente uma barreira. A criação explícita de sinais/barreiras resulta em um ataque de negação de serviço que interrompe a funcionalidade do pipeline.
- Não acesse elementos
sync_timeline,sync_ptousync_fenceexplicitamente. A API fornece todas as funções necessárias.
sync_pt
sync_pt é um único valor ou ponto em uma sync_timeline. Um ponto tem três estados: ativo, sinalizado e erro. Os pontos começam no estado ativo e fazem a transição para os estados sinalizado ou erro. Por exemplo, quando um consumidor de imagens não precisa mais de um buffer, um sync_pt é sinalizado para que um produtor de imagens saiba que é possível gravar no buffer novamente.
sync_fence
sync_fence é uma coleção de valores sync_pt que geralmente têm pais sync_timeline diferentes (como para o controlador de exibição e a GPU). sync_fence, sync_pt e sync_timeline são os principais primitivos que os drivers e o espaço do usuário usam para comunicar as dependências. Quando uma barreira é sinalizada, todos os comandos emitidos antes dela são concluídos porque o driver do kernel ou o bloco de hardware executa os comandos em ordem.
O framework de sincronização permite que vários consumidores ou produtores sinalizem quando terminam de usar um buffer, comunicando as informações de dependência com um parâmetro de função. As barreiras são apoiadas por um descritor do arquivo e são transmitidas do espaço do kernel para o espaço do usuário. Por exemplo, uma barreira pode conter dois valores sync_pt que indicam quando dois consumidores de imagens separados terminam de ler um buffer. Quando a barreira é sinalizada, os produtores de imagens sabem que os dois consumidores terminaram de consumir.
As barreiras, como os valores sync_pt, começam ativas e mudam de estado com base no estado dos pontos. Se todos os valores sync_pt forem sinalizados, a sync_fence será sinalizada. Se um sync_pt cair em um estado de erro, toda a sync_fence terá um estado de erro.
A participação em uma sync_fence é imutável após a criação da barreira. Para ter mais de um ponto em uma barreira, uma mesclagem é realizada em que pontos de duas barreiras distintas são adicionados a uma terceira.
Se um desses pontos foi sinalizado na barreira de origem e o outro não, a terceira barreira também não estará em um estado sinalizado.
Para implementar a sincronização explícita, forneça o seguinte:
- Um subsistema de espaço do kernel que implementa o framework de sincronização para um driver de hardware específico. Os drivers que precisam reconhecer barreiras são geralmente qualquer coisa que acesse ou se comunique com o Hardware Composer (HWC).
Os principais arquivos incluem:
- Implementação principal:
kernel/common/include/linux/sync.hkernel/common/drivers/base/sync.c
- Documentação em
kernel/common/Documentation/sync.txt - Biblioteca para se comunicar com o espaço do kernel em
platform/system/core/libsync
- Implementação principal:
- O fornecedor precisa fornecer as barreiras de sincronização adequadas como parâmetros para as funções
validateDisplay()epresentDisplay()na camada de abstração de hardware (HAL). - Duas extensões GL relacionadas a barreiras (
EGL_ANDROID_native_fence_synceEGL_ANDROID_wait_sync) e suporte a barreiras no driver gráfico.
Estudo de caso: implementar um driver de exibição
Para usar a API que oferece suporte à função de sincronização, desenvolva um driver de exibição que tenha uma função de buffer de exibição. Antes da existência do framework de sincronização, essa função recebia objetos dma-buf, colocava esses buffers na tela e bloqueava enquanto o buffer estava visível. Por exemplo:
/* * assumes buffer is ready to be displayed. returns when buffer is no longer on * screen. */ void display_buffer(struct dma_buf *buffer);
Com o framework de sincronização, a função display_buffer é mais complexa. Ao colocar um buffer na tela, ele é associado a uma barreira que indica quando o buffer estará pronto. É possível enfileirar e iniciar o trabalho depois que a barreira for limpa.
Enfileirar e iniciar o trabalho depois que a barreira for limpa não bloqueia nada. Você retorna imediatamente sua própria barreira, que sinaliza quando o buffer será removido da tela. Ao enfileirar buffers, o kernel lista as dependências com o framework de sincronização:
/* * displays buffer when fence is signaled. returns immediately with a fence * that signals when buffer is no longer displayed. */ struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence *fence);
Integração de sincronização
Esta seção explica como integrar o framework de sincronização do espaço do kernel com partes do espaço do usuário do framework do Android e os drivers que precisam se comunicar entre si. Os objetos do espaço do kernel são representados como descritores de arquivo no espaço do usuário.
Convenções de integração
Siga as convenções de interface da HAL do Android:
- Se a API fornecer um descritor de arquivo que se refere a um
sync_pt, o driver do fornecedor ou a HAL que usa a API precisará fechar o descritor de arquivo. - Se o driver do fornecedor ou a HAL transmitir um descritor do arquivo que contenha
um
sync_ptpara uma função de API, o driver do fornecedor ou a HAL não poderá fechar o descritor do arquivo. - Para continuar usando o descritor do arquivo de barreira, o driver do fornecedor ou o HAL precisa duplicar o descritor.
Um objeto de barreira é renomeado sempre que passa pelo BufferQueue.
O suporte a barreiras do kernel permite que as barreiras tenham strings para nomes. Assim, o framework de sincronização usa o nome da janela e o índice do buffer que está sendo enfileirado para nomear a barreira, como SurfaceView:0. Isso é útil na depuração para identificar a origem de um deadlock, já que os nomes aparecem na saída de /d/sync e nos relatórios de bugs.
Integração do ANativeWindow
O ANativeWindow reconhece barreiras. dequeueBuffer,
queueBuffer e cancelBuffer têm parâmetros de barreira.
Integração do OpenGL ES
A integração de sincronização do OpenGL ES depende de duas extensões EGL:
EGL_ANDROID_native_fence_syncoferece uma maneira de encapsular ou criar descritores de arquivo de barreira nativa do Android emEGLSyncKHRobjetos.EGL_ANDROID_wait_syncpermite bloqueios do lado da GPU em vez do lado da CPU, fazendo com que a GPU espere porEGLSyncKHR. AEGL_ANDROID_wait_syncextensão é a mesma que aEGL_KHR_wait_syncextensão.
Para usar essas extensões de forma independente, implemente a extensão EGL_ANDROID_native_fence_sync com o suporte do kernel associado. Em seguida, ative a extensão EGL_ANDROID_wait_sync no driver. A extensão EGL_ANDROID_native_fence_sync consiste em um tipo de objeto EGLSyncKHR de barreira nativa distinta. Como resultado, as extensões que se aplicam aos tipos de objeto EGLSyncKHR atuais não se aplicam necessariamente aos objetos EGL_ANDROID_native_fence, evitando interações indesejadas.
A extensão EGL_ANDROID_native_fence_sync emprega um atributo de descritor do arquivo de barreira nativa correspondente que só pode ser definido no momento da criação e não pode ser consultado diretamente de um objeto de sincronização existente. Esse atributo pode ser definido como um destes dois modos:
- Um descritor do arquivo de barreira válido encapsula um descritor do arquivo de barreira nativa do Android em um objeto
EGLSyncKHR. - -1 cria um descritor do arquivo de barreira nativa do Android a partir de um
EGLSyncKHRobjeto.
Use a chamada de função DupNativeFenceFD() para extrair o objeto EGLSyncKHR do descritor do arquivo de barreira nativa do Android.
Isso tem o mesmo resultado que consultar o atributo definido, mas segue a convenção de que o destinatário fecha a barreira (daí a operação duplicada). Por fim, destruir o objeto EGLSyncKHR fecha o atributo de barreira interna.
Integração do Hardware Composer
O HWC processa três tipos de barreiras de sincronização:
- Barreiras de aquisição são transmitidas com buffers de entrada para
as chamadas
setLayerBufferesetClientTarget. Elas representam uma gravação pendente no buffer e precisam sinalizar antes que o SurfaceFlinger ou o HWC tentem ler do buffer associado para realizar a composição. - Barreiras de liberação são recuperadas após a chamada para
presentDisplayusando a chamadagetReleaseFences. Elas representam uma leitura pendente do buffer anterior na mesma camada. Uma barreira de liberação sinaliza quando o HWC não está mais usando o buffer anterior porque o buffer atual substituiu o anterior na tela. As barreiras de liberação são transmitidas de volta ao app junto com os buffers anteriores que serão substituídos durante a composição atual. O app precisa esperar até que uma barreira de liberação sinalize antes de gravar novos conteúdos no buffer que foi retornado a ele. - Barreiras atuais são retornadas, uma por frame, como parte de
a chamada para
presentDisplay. As barreiras atuais representam quando a composição desse frame foi concluída ou, alternativamente, quando o resultado da composição do frame anterior não é mais necessário. Para telas físicas,presentDisplayretorna barreiras atuais quando o frame atual aparece na tela. Depois que as barreiras atuais são retornadas, é seguro gravar no buffer de destino do SurfaceFlinger novamente, se aplicável. Para telas virtuais, as barreiras atuais são retornadas quando é seguro ler do buffer de saída.