O framework de sincronização descreve explicitamente as dependências entre diferentes operações assíncronas no sistema gráfico do Android. O framework oferece uma API que permite que os componentes indiquem quando os buffers são liberados. O framework também permite que primitivas de sincronização sejam transmitidas 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 renderizada na memória, o ponteiro do buffer é transmitido para o compositor da janela com uma cerca que indica quando o trabalho da GPU será concluído. O compositor de janelas 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 aproveitem recursos de sincronização nos próprios componentes de hardware. Por fim, o framework fornece visibilidade do 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 os dispositivos
- Melhor suporte à depuração
- Métricas de teste aprimoradas
O framework de sincronização tem três tipos de objeto:
sync_timeline
sync_pt
sync_fence
sync_timeline
sync_timeline
é uma linha do tempo crescente monotonicamente que
os fornecedores precisam implementar para cada instância do driver, como um contexto GL,
controlador de exibição ou blitter 2D. sync_timeline
conta os jobs enviados ao kernel para uma parte específica de hardware.
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 para todos os drivers, linhas do tempo e cercas para simplificar a depuração.
- Implemente os operadores
timeline_value_str
ept_value_str
em linhas do tempo para tornar a saída de depuração mais legível. - Implemente o preenchimento
driver_data
para dar às bibliotecas de espaço do usuário, como a biblioteca GL, acesso a dados da linha do tempo particulares, se quiser.data_driver
permite que os fornecedores transmitam informações sobresync_fence
esync_pts
imutáveis para criar linhas de comando com base neles. - Não permita que o espaço do usuário crie ou sinalize um limite explicitamente. A criação explícita de indicadores/cercas resulta em um ataque de negação de serviço que interrompe a funcionalidade do pipeline.
- Não acesse os elementos
sync_timeline
,sync_pt
ousync_fence
explicitamente. A API oferece todas as funções necessárias.
sincronia_pt
sync_pt
é um valor ou ponto único 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 sinalizados ou de erro. Por exemplo, quando um consumidor
de imagem não precisa mais de um buffer, um sync_pt
é sinalizado
para que o produtor de imagem saiba que está tudo bem gravar no buffer novamente.
sinc_fence
sync_fence
é uma coleção de valores sync_pt
que geralmente
têm diferentes pais sync_timeline
, como 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 cerca é sinalizada, todos
os comandos emitidos antes dela são garantidos como concluídos, porque o
driver do kernel ou o bloco de hardware executa 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 cercas 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, um limite pode conter dois
valores sync_pt
que significam quando dois consumidores de imagem diferentes terminam
a leitura de um buffer. Quando a cerca é sinalizada, os produtores de imagens sabem que os
consumidores terminaram de consumir.
As cercas, 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, o
sync_fence
também será. Se uma sync_pt
entrar em
estado de erro, toda a sync_fence
terá um estado de erro.
A associação a um sync_fence
é imutável depois que o limite é
criado. Para ter mais de um ponto em uma cerca, é realizada uma mesclagem, em que pontos de duas cercas distintas são adicionados a uma terceira.
Se um desses pontos foi 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 o framework de sincronização
para um driver de hardware específico. Os drivers que precisam estar cientes do isolamento geralmente são
qualquer coisa que acessa ou se comunica 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 cercas de sincronização
adequadas como parâmetros para as funções
validateDisplay()
epresentDisplay()
no HAL. - Duas extensões GL relacionadas a cercas (
EGL_ANDROID_native_fence_sync
eEGL_ANDROID_wait_sync
) e suporte a cercas no driver gráfico.
Estudo de caso: implementar um driver de exibição
Para usar a API compatível com a função de sincronização,
desenvolva um driver de exibição que tenha uma função de buffer de exibição. Antes 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 um limite que indica quando ele estará pronto. Você pode colocar na fila
e iniciar o trabalho depois que a cerca for removida.
A fila e a inicialização do trabalho após a limpeza da cerca não bloqueiam nada. Você retorna imediatamente sua própria cerca, o que garante quando o buffer será removido da tela. À medida que você enfileira buffers, o kernel vai listar 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 da interface HAL do Android:
- Se a API fornecer um descritor de arquivo que se refira 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 o HAL transmitir um descritor de arquivo que contenha
uma
sync_pt
para uma função da API, o driver do fornecedor ou o HAL não poderão fechar o descritor de arquivo. - Para continuar usando o descritor de arquivo de cerca, o driver do fornecedor ou o HAL precisam duplicar o descritor.
Um objeto de cerca é renomeado toda vez que passa pela BufferQueue.
O suporte a cercas do kernel permite que as cercas tenham strings para nomes. Portanto, o framework
de sincronização usa o nome da janela e o índice de buffer que está na fila para nomear
a cerca, 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 com ANativeWindow
A ANativeWindow reconhece a cerca. dequeueBuffer
,
queueBuffer
e cancelBuffer
têm parâmetros de cerca.
Integração do OpenGL ES
A integração de sincronização do OpenGL ES depende de duas extensões do EGL:
EGL_ANDROID_native_fence_sync
oferece uma maneira de agrupar ou criar descritores de arquivo de cerca nativa do Android em objetosEGLSyncKHR
.- O
EGL_ANDROID_wait_sync
permite interrupções do lado da GPU em vez de interrupções 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
com o suporte
de 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 limite nativo distinto. Como resultado, as extensões que se aplicam a tipos de objeto 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 correspondente de descritor de arquivo de cerca nativa que pode ser definido apenas no momento da criação e
não pode ser consultado diretamente em um objeto de sincronização. Esse atributo
pode ser definido em um destes dois modos:
- Um descritor de arquivo de limite válido envolve um descritor
de arquivo de limite nativo do Android em um objeto
EGLSyncKHR
. - -1 cria um descritor de arquivo de cerca nativo do Android usando um
objeto
EGLSyncKHR
.
Use a chamada de função DupNativeFenceFD()
para extrair o
objeto EGLSyncKHR
do descritor de arquivo de cerca nativo do Android.
Isso tem o mesmo resultado que consultar o atributo definido, mas adere à
convenção de que o destinatário fecha a cerca (daí a operação
duplicada). Por fim, a destruição do objeto EGLSyncKHR
fecha
o atributo de cerca interna.
Integração do Hardware Composer
O Hardware Composer processa três tipos de cercas de sincronização:
- Adquirir limites são transmitidos com buffers de entrada para as chamadas
setLayerBuffer
esetClientTarget
. Elas representam uma gravação pendente no buffer e precisam sinalizar antes que o SurfaceFlinger ou o HWC tente ler do buffer associado para realizar a composição. - Os limites de lançamento são recuperados após a chamada para
presentDisplay
usando a chamadagetReleaseFences
. Elas representam uma leitura pendente do buffer anterior na mesma camada. Uma cerca 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 cercas de liberação são transmitidas de volta ao app com os buffers anteriores que serão substituídos durante a composição atual. O app precisa aguardar até que um limite de lançamento sinalize antes de gravar novos conteúdos no buffer que foi retornado a ele. - As cercas presentes são retornadas, uma por frame, como parte
da chamada para
presentDisplay
. As cercas presentes representam quando a composição desse frame foi concluída ou, de outra forma, quando o resultado da composição do frame anterior não é mais necessário. Para telas físicas,presentDisplay
retorna cercas presentes quando o frame atual aparece na tela. Depois que as cercas atuais forem retornadas, será possível gravar novamente no buffer de destino do SurfaceFlinger, se aplicável. Para telas virtuais, as cercas presentes são retornadas quando é seguro ler do buffer de saída.