O framework de sincronização descreve explicitamente as dependências entre diferentes operações assíncronas no sistema gráfico do Android. O framework fornece uma API que permite que os componentes indiquem quando os buffers são liberados. A estrutura 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 de janela começa o processamento com antecedência e transfere o trabalho para o controlador de tela. Da mesma forma, o trabalho da CPU é feito com antecedência. Quando a GPU termina, o controlador de tela mostra a imagem imediatamente.
O framework de sincronização também permite que os implementadores aproveitem recursos de sincronização nos próprios componentes de hardware. Por fim, o framework oferece visibilidade no pipeline de gráficos para ajudar na depuração.
Sincronização explícita
A sincronização explícita permite que produtores e consumidores de buffers gráficos sinalizem quando terminarem 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 para depuração
- Métricas de teste aprimoradas
O framework de sincronização tem três tipos de objetos:
sync_timeline
sync_pt
sync_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,
controlador de tela ou blitter 2D. sync_timeline
conta
trabalhos enviados ao kernel para um hardware específico.
O sync_timeline
oferece garantias sobre a ordem das operações
e permite implementações específicas de hardware.
Siga estas diretrizes ao implementar sync_timeline
:
- Dê nomes úteis a todos os drivers, linhas do tempo e barreiras para simplificar a depuração.
- Implemente os operadores
timeline_value_str
ept_value_str
nas linhas do tempo para tornar a saída de depuração mais legível. - Implemente o preenchimento
driver_data
para dar às bibliotecas do espaço do usuário, como a biblioteca GL, acesso aos dados particulares da linha do tempo, se quiser. Odata_driver
permite que os fornecedores transmitam informações sobre osync_fence
e osync_pts
imutáveis para criar linhas de comando com base neles. - Não permitir que o espaço do usuário crie ou sinalize explicitamente uma restrição. A criação explícita de indicadores/barreiras resulta em um ataque de negação de serviço que interrompe a funcionalidade do pipeline.
- Não acesse elementos
sync_timeline
,sync_pt
ousync_fence
explicitamente. A API fornece todas as funções necessárias.
sync_pt
sync_pt
é um único valor ou ponto em um 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 de 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 diferentes sync_timeline
principais (como para o controlador de tela e a GPU). sync_fence
, sync_pt
e
sync_timeline
são as principais primitivas que drivers e espaço do usuário
usam para comunicar suas dependências. Quando uma barreira é sinalizada, todos os comandos emitidos antes dela têm garantia de conclusão porque o driver do kernel ou o bloco de hardware executa os comandos em ordem.
A estrutura de sincronização permite que vários consumidores ou produtores sinalizem quando terminarem 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 de arquivo e são transmitidas do espaço do kernel para o espaço do usuário. Por exemplo, uma cerca pode conter dois valores sync_pt
que significam quando dois consumidores de imagens separados terminam de ler um buffer. Quando a cerca é sinalizada, os produtores de imagens sabem que os dois
consumidores terminaram de consumir.
As barreiras, assim como os valores sync_pt
, começam ativas e mudam de estado com base no estado dos pontos delas. Se todos os valores de sync_pt
forem sinalizados, o sync_fence
também será. Se um sync_pt
entrar em um estado de erro, todo o sync_fence
terá um estado de erro.
A associação a um sync_fence
é imutável depois que a cerca é criada. Para ter mais de um ponto em uma cerca, uma fusão é realizada quando pontos de duas cercas distintas são adicionados a uma terceira.
Se um desses pontos for sinalizado na cerca de origem e o outro não, a terceira cerca também não estará em um estado sinalizado.
Para implementar a sincronização explícita, forneça o seguinte:
- Um subsistema do espaço do kernel que implementa a estrutura de sincronização
para um driver de hardware específico. Os drivers que precisam estar cientes da cerca são geralmente qualquer coisa que acesse ou se comunique com o Hardware Composer.
Os principais arquivos incluem:
- Implementação principal:
kernel/common/include/linux/sync.h
kernel/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 HAL. - Duas extensões GL relacionadas a barreiras (
EGL_ANDROID_native_fence_sync
eEGL_ANDROID_wait_sync
) e suporte a barreiras no driver de gráficos.
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 tela com 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 em exibição, ele é associado
a uma barreira que indica quando o buffer estará pronto. Você pode enfileirar
e iniciar o trabalho depois que a restrição for removida.
Enfileirar e iniciar o trabalho depois que a cerca é liberada não bloqueia nada. Você retorna imediatamente sua própria cerca, o que garante quando o buffer estará fora da tela. À medida que você enfileira buffers, o kernel lista 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 Android e os drivers que precisam se comunicar entre si. 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 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 precisa fechar o descritor de arquivo. - Se o driver do fornecedor ou a HAL transmitir um descritor de arquivo que contenha
um
sync_pt
para uma função de API, o driver do fornecedor ou a HAL não poderá fechar o descritor de arquivo. - Para continuar usando o descritor de arquivo de cerca, o driver do fornecedor ou a HAL precisa duplicar o descritor.
Um objeto de cerca é renomeado sempre que passa pelo BufferQueue.
O suporte a barreiras do kernel permite que elas 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 restrição.
Integração do OpenGL ES
A integração de sincronização do OpenGL ES depende de duas extensões EGL:
- O
EGL_ANDROID_native_fence_sync
oferece uma maneira de encapsular ou criar descritores de arquivo de cerca nativos do Android em objetosEGLSyncKHR
. EGL_ANDROID_wait_sync
permite interrupções do lado da GPU em vez do lado da CPU, fazendo com que a GPU espere porEGLSyncKHR
. A extensãoEGL_ANDROID_wait_sync
é igual à extensãoEGL_KHR_wait_sync
.
Para usar essas extensões de forma independente, implemente a extensão
EGL_ANDROID_native_fence_sync
junto 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 isolamento nativo distinto. Como resultado, as extensões que se aplicam a tipos de objetos EGLSyncKHR
não necessariamente se aplicam a objetos EGL_ANDROID_native_fence
, evitando interações indesejadas.
A extensão EGL_ANDROID_native_fence_sync
usa um atributo de descritor de arquivo de
cerca nativo 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 dos dois modos:
- Um descritor de arquivo de bloqueio válido envolve um descritor de arquivo de bloqueio nativo do Android em um objeto
EGLSyncKHR
. - -1 cria um descritor de arquivo de limite nativo do Android de um objeto
EGLSyncKHR
.
Use a chamada de função DupNativeFenceFD()
para extrair o objeto
EGLSyncKHR
do descritor de arquivo de limite nativo do Android.
Isso tem o mesmo resultado que consultar o atributo "set", mas segue a convenção de que o destinatário fecha a cerca (daí a operação duplicada). Por fim, destruir o objeto EGLSyncKHR
fecha
o atributo de cerca interna.
Integração do Hardware Composer
O Hardware Composer processa três tipos de barreiras de sincronização:
- As aquisições de barreiras são transmitidas com buffers de entrada para as chamadas
setLayerBuffer
esetClientTarget
. Eles 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. - As restrições de lançamento são recuperadas após a chamada para
presentDisplay
usando a chamadagetReleaseFences
. Eles representam uma leitura pendente do buffer anterior na mesma camada. Uma barreira de lançamento sinaliza quando o HWC não está mais usando o buffer anterior porque o buffer atual substituiu o anterior na tela. As barreiras de lançamento 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 um indicador de limite de lançamento seja sinalizado antes de gravar novos conteúdos no buffer que foi retornado a ele. - As cercas atuais são retornadas, uma por frame, como parte
da chamada para
presentDisplay
. As barreiras de apresentação representam quando a composição deste frame foi concluída ou, alternativamente, quando o resultado da composição do frame anterior não é mais necessário. Para telas físicas,presentDisplay
retorna barreiras presentes quando o frame atual aparece na tela. Depois que as barreiras atuais são retornadas, é seguro gravar novamente no buffer de destino do SurfaceFlinger, se aplicável. Para telas virtuais, as barreiras de apresentação são retornadas quando é seguro ler do buffer de saída.