Estrutura de sincronização

A estrutura de sincronização descreve explicitamente as dependências entre diferentes operações assíncronas no sistema gráfico Android. A estrutura fornece uma API que permite que os componentes indiquem quando os buffers são liberados. A estrutura também permite que primitivas de sincronização sejam passadas 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 executado 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 é passado para o compositor da janela junto com uma cerca que indica quando o trabalho da GPU será concluído. O compositor de janela inicia o processamento antecipadamente e passa o trabalho para o controlador de exibição. De maneira semelhante, o trabalho da CPU é feito antecipadamente. Assim que a GPU terminar, o controlador de vídeo exibirá imediatamente a imagem.

A estrutura de sincronização também permite que os implementadores aproveitem os recursos de sincronização em seus próprios componentes de hardware. Por fim, a estrutura fornece visibilidade do pipeline gráfico 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 à depuração
  • Métricas de teste aprimoradas

A estrutura de sincronização possui três tipos de objetos:

  • sync_timeline
  • sync_pt
  • sync_fence

linha_do_tempo_de_sincronização

sync_timeline é uma linha do tempo crescente monotonicamente que os fornecedores devem implementar para cada instância de driver, como um contexto GL, controlador de exibição ou blitter 2D. sync_timeline conta trabalhos enviados ao kernel para uma determinada peça de hardware. sync_timeline fornece garantias sobre 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, cronogramas e limites para simplificar a depuração.
  • Implemente os operadores timeline_value_str e pt_value_str em cronogramas para tornar a saída de depuração mais legível.
  • Implemente o preenchimento driver_data para fornecer às bibliotecas do espaço do usuário, como a biblioteca GL, acesso a dados privados da linha do tempo, se desejado. data_driver permite que os fornecedores passem informações sobre os imutáveis sync_fence e sync_pts para construir linhas de comando com base neles.
  • Não permita que o espaço do usuário crie ou sinalize explicitamente uma cerca. A criação explícita de sinais/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 ou sync_fence explicitamente. A API fornece todas as funções necessárias.

sincronização_pt

sync_pt é um valor ou ponto único em um sync_timeline . Um ponto possui três estados: ativo, sinalizado e erro. Os pontos começam no estado ativo e transitam 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 um produtor de imagem saiba que não há problema em gravar no buffer novamente.

sincronização_fence

sync_fence é uma coleção de valores sync_pt que geralmente têm pais sync_timeline diferentes (como para o controlador de vídeo e 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 suas dependências. Quando uma cerca é sinalizada, é garantido que todos os comandos emitidos antes da cerca sejam concluídos porque o driver do kernel ou 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 cercas são apoiadas por um descritor de arquivo e são passadas 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 imagem separados terminam de ler um buffer. Quando a cerca é sinalizada, os produtores da imagem sabem que ambos os consumidores terminaram de consumir.

As cercas, assim como os valores sync_pt , iniciam ativas e mudam de estado com base no estado de seus pontos. Se todos os valores sync_pt forem sinalizados, o sync_fence será sinalizado. Se um sync_pt cair em estado de erro, todo sync_fence terá um estado de erro.

A associação a um sync_fence é imutável após a criação do fence. Para obter mais de um ponto em uma cerca, é realizada uma fusão onde pontos de duas cercas distintas são adicionados a uma terceira cerca. Se um desses pontos foi sinalizado na cerca de origem e o outro não, a terceira cerca também não estará sinalizada.

Para implementar a sincronização explícita, forneça o seguinte:

  • Um subsistema de 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 barreira geralmente são qualquer coisa que acesse ou se comunique com o Hardware Composer. Os arquivos principais 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
  • O fornecedor deve fornecer as barreiras de sincronização apropriadas como parâmetros para as funções validateDisplay() e presentDisplay() no HAL.
  • Duas extensões GL relacionadas ao fence ( EGL_ANDROID_native_fence_sync e EGL_ANDROID_wait_sync ) e suporte ao fence no driver gráfico.

Estudo de caso: Implementar um driver de vídeo

Para usar a API que dá suporte à função de sincronização, desenvolva um driver de vídeo que tenha uma função de buffer de exibição. Antes de existir a estrutura de sincronização, essa função recebia objetos dma-buf , colocava esses buffers no display 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 a estrutura de sincronização, a função display_buffer é mais complexa. Ao exibir um buffer, o buffer é associado a uma cerca que indica quando o buffer estará pronto. Você pode entrar na fila e iniciar o trabalho depois que a cerca for liberada.

Enfileirar-se e iniciar o trabalho depois que a cerca for liberada não bloqueia nada. Você devolve imediatamente sua própria cerca, o que garante quando o buffer estará fora da tela. À medida que você enfileira os buffers, o kernel lista as dependências com a estrutura 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 a estrutura de sincronização do espaço do kernel com partes do espaço do usuário da estrutura Android e os drivers que devem 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 o HAL que usa a API deverá fechar o descritor de arquivo.
  • Se o driver do fornecedor ou o HAL passar um descritor de arquivo que contém um sync_pt para uma função de API, o driver do fornecedor ou o HAL não deverá fechar o descritor de arquivo.
  • Para continuar usando o descritor de arquivo fence, o driver do fornecedor ou o HAL deve duplicar o descritor.

Um objeto fence é renomeado toda vez que passa pelo BufferQueue. O suporte ao fence do kernel permite que os fences tenham strings para nomes, então a estrutura de sincronização usa o nome da janela e o índice de buffer que está sendo enfileirado para nomear o fence, como SurfaceView:0 . Isso é útil na depuração para identificar a origem de um impasse à medida que os nomes aparecem na saída de /d/sync e nos relatórios de bugs.

Integração ANativeWindow

ANativeWindow reconhece cercas. dequeueBuffer , queueBuffer e cancelBuffer possuem parâmetros de fence.

Integração OpenGL ES

A integração de sincronização do OpenGL ES depende de duas extensões EGL:

  • EGL_ANDROID_native_fence_sync fornece uma maneira de agrupar ou criar descritores de arquivo fence nativos do Android em objetos EGLSyncKHR .
  • EGL_ANDROID_wait_sync permite travamentos no lado da GPU em vez do lado da CPU, fazendo com que a GPU espere por EGLSyncKHR . A extensão EGL_ANDROID_wait_sync é igual à extensão EGL_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, habilite a extensão EGL_ANDROID_wait_sync em seu driver. A extensão EGL_ANDROID_native_fence_sync consiste em um tipo de objeto EGLSyncKHR de fence nativo distinto. Como resultado, as extensões que se aplicam aos tipos de objetos EGLSyncKHR existentes 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 descritor de arquivo fence nativo correspondente que pode ser definido apenas no momento da criação e não pode ser consultado diretamente a partir de um objeto de sincronização existente. Este atributo pode ser definido para um dos dois modos:

  • Um descritor de arquivo fence válido envolve um descritor de arquivo fence nativo do Android existente em um objeto EGLSyncKHR .
  • -1 cria um descritor de arquivo fence Android nativo a partir de um objeto EGLSyncKHR .

Use a chamada de função DupNativeFenceFD() para extrair o objeto EGLSyncKHR do descritor de arquivo fence nativo do Android. Isto 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). Finalmente, destruir o objeto EGLSyncKHR fecha o atributo fence interno.

Integração do Hardware Composer

O Hardware Composer lida com três tipos de limites de sincronização:

  • As barreiras de aquisição são passadas junto com os buffers de entrada para as chamadas setLayerBuffer e setClientTarget . Eles representam uma gravação pendente no buffer e devem sinalizar antes que o SurfaceFlinger ou o HWC tente ler o buffer associado para realizar a composição.
  • As barreiras de liberação são recuperadas após a chamada para presentDisplay usando a chamada getReleaseFences . Estes representam uma leitura pendente do buffer anterior na mesma camada. Um limite de liberação sinaliza quando o HWC não está mais usando o buffer anterior porque o buffer atual substituiu o buffer anterior no display. As barreiras de liberação são devolvidas ao aplicativo junto com os buffers anteriores que serão substituídos durante a composição atual. O aplicativo deve esperar até que uma cerca de liberação seja sinalizada antes de gravar novos conteúdos no buffer que foi retornado a eles.
  • As barreiras presentes são retornadas, uma por quadro, como parte da chamada para presentDisplay . As cercas atuais representam quando a composição deste quadro foi concluída ou, alternativamente, quando o resultado da composição do quadro anterior não é mais necessário. Para exibições físicas, presentDisplay retorna cercas atuais quando o quadro atual aparece na tela. Depois que as cercas atuais forem retornadas, é seguro gravar novamente no buffer de destino do SurfaceFlinger, se aplicável. Para exibições virtuais, as barreiras presentes são retornadas quando é seguro ler o buffer de saída.