A criação de camadas com o OpenGL ES (GLES) está disponível em dispositivos com o Android 10 (API de nível 29) e versões mais recentes. Um app depurável pode carregar camadas GLES pelo APK dele, do diretório básico ou do APK de uma camada selecionada.
O uso de camadas GLES é semelhante ao uso de Camadas de validação da Vulkan.
Requisitos
Só há compatibilidade com camadas GLES a partir da versão 2.0.
Inicialização de camadas
Depois de preencher os pontos de entrada padrão, o EGL Loader instancia um LayerLoader
do GLES. Assim como no carregador da Vulkan, se camadas de depuração forem ativadas, o LayerLoader
verificará a existência de camadas nos diretórios especificados.
Se a criação de camadas estiver ativada, o LayerLoader
pesquisará e enumerará uma lista especificada de camadas. A lista de camadas é especificada por nomes de arquivos separados por dois-pontos (:).
O LayerLoader
transfere as camadas na ordem que você especifica. Assim, a primeira camada fica logo abaixo do aplicativo. Para cada camada, o LayerLoader
rastreia os pontos de entrada AndroidGLESLayer_Initialize
e AndroidGLESLayer_GetProcAddress
. As camadas precisam disponibilizar essas interfaces para serem carregadas.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
oferece um identificador a ser usado pela camada (layer_id
) e um ponto de entrada que pode ser chamado para procurar funções abaixo da camada. O ponto de entrada pode ser usado na forma apresentada na amostra de código a seguir:
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
usa o endereço da chamada seguinte na cadeia que a camada precisa chamar ao concluir esse processo. Para a maior parte das funções, se houver somente uma camada, next
apontará diretamente para o driver.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
Para cada camada encontrada pelo LayerLoader
do GLES, ele chama AndroidGLESLayer_Initialize
, movimenta listas de funções de libEGL
e chama AndroidGLESLayer_GetProcAddress
para todas as funções conhecidas. Cabe à camada determinar como o próximo endereço será rastreado. Se a camada interceptar uma função, ela rastreará o endereço da função. Se a camada não interceptar uma função, AndroidGLESLayer_GetProcAddress
retornará o mesmo endereço de função que foi transmitido. Então, o LayerLoader
atualizará a lista de hooks de função para apontar para o
ponto de entrada da camada.
As camadas não precisam fazer nada com as informações fornecidas por
AndroidGLESLayer_Initialize
e get_next_layer_proc_address
, mas
a disponibilidade dos dados facilita a compatibilidade das camadas existentes, como
Android GPU Inspector e
RenderDoc (links em inglês),
com o Android. Com esses dados, a camada pode pesquisar funções de maneira independente, em vez de
esperar chamadas para AndroidGLESLayer_GetProcAddress
. Se as camadas forem inicializadas antes de o carregador ter consultado todos os pontos de entrada, elas precisarão usar get_next_layer_proc_address
. O eglGetProcAddress
precisa ser transmitido da cadeia para a plataforma.
Posicionar camadas
O LayerLoader
do GLES procura camadas nos seguintes locais, em ordem de prioridade:
1. Local do sistema correspondente à raiz
Isso requer acesso à raiz.
adb root adb disable-verity adb reboot adb root adb shell setenforce 0 adb shell mkdir -p /data/local/debug/gles adb push <layer>.so /data/local/debug/gles/
2. Diretório básico do aplicativo
O aplicativo de destino precisa ser depurável. Caso contrário, você precisa ter acesso à raiz:
adb push libGLTrace.so /data/local/tmp adb shell run-as com.android.gl2jni cp /data/local/tmp/libGLTrace.so . adb shell run-as com.android.gl2jni ls | grep libGLTrace libGLTrace.so
3. APK externo
Defina a ABI do seu aplicativo de destino e instale um APK que contenha as camadas que você quer carregar:
adb install --abi armeabi-v7a layers.apk
4. No APK do aplicativo de destino
O exemplo a seguir mostra como posicionar camadas no APK do aplicativo:
$ jar tf GLES_layers.apk lib/arm64-v8a/libGLES_glesLayer1.so lib/arm64-v8a/libGLES_glesLayer2.so lib/arm64-v8a/libGLES_glesLayer3.so lib/armeabi-v7a/libGLES_glesLayer1.so lib/armeabi-v7a/libGLES_glesLayer2.so lib/armeabi-v7a/libGLES_glesLayer3.so resources.arsc AndroidManifest.xml META-INF/CERT.SF META-INF/CERT.RSA META-INF/MANIFEST.MF
Ativar camadas
Você pode ativar as camadas GLES por app ou de forma global. As configurações por app persistem em reinicializações, enquanto as propriedades globais são apagadas na reinicialização.
As políticas e o modelo de segurança do Android são muito diferentes dos que são usados por outras plataformas. Para carregar camadas externas, uma das seguintes condições precisa ser verdadeira:
O arquivo de manifesto do app de destino inclui o seguinte elemento de metadados (aplica-se apenas a apps direcionados ao Android 11 (API de nível 30) ou versões mais recentes):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
Use essa opção para caracterizar o perfil do aplicativo.
O app de destino é depurável. Essa opção fornece mais informações de depuração, mas pode afetar negativamente o desempenho do app.
O app de destino é executado em um build userbug do sistema operacional que concede acesso raiz.
Para ativar camadas por app:
# Enable layers adb shell settings put global enable_gpu_debug_layers 1 # Specify target application adb shell settings put global gpu_debug_app <package_name> # Specify layer list (from top to bottom) # Layers are identified by their filenames, such as "libGLLayer.so" adb shell settings put global gpu_debug_layers_gles <layer1:layer2:layerN> # Specify packages to search for layers adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
Para desativar camadas por app:
# Delete the global setting that enables layers adb shell settings delete global enable_gpu_debug_layers # Delete the global setting that selects target application adb shell settings delete global gpu_debug_app # Delete the global setting that specifies layer list adb shell settings delete global gpu_debug_layers_gles # Delete the global setting that specifies layer packages adb shell settings delete global gpu_debug_layer_app
Para ativar camadas de forma global:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
Criar uma camada
As camadas precisam expor as duas funções a seguir, descritas em Inicialização do EGL Loader:
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
Camadas passivas
Uma camada inicializada de forma passiva é ideal para uma camada que intercepta apenas algumas funções. A camada inicializada de forma passiva aguarda até que o LayerLoader
do GLES inicialize a função necessária.
A amostra de código a seguir demonstra como criar uma camada passiva.
namespace { std::unordered_map<std::string, EGLFuncPointer> funcMap; EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig ( EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) { EGLFuncPointer entry = funcMap["eglChooseConfig"]; typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry); return next(dpy, attrib_list, configs, config_size, num_config); } EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) { #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \ return (EGLFuncPointer)glesLayer_##func; } GETPROCADDR(eglChooseConfig); // Don't return anything for unrecognized functions return nullptr; } EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { // This function is purposefully empty, since this layer does not proactively // look up any entrypoints } EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress( const char* funcName, EGLFuncPointer next) { EGLFuncPointer entry = eglGPA(funcName); if (entry != nullptr) { funcMap[std::string(funcName)] = next; return entry; } return next; } } // namespace extern "C" { __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address); } __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress( const char *funcName, EGLFuncPointer next) { return (void*)glesLayer_GetLayerProcAddress(funcName, next); } }
Camadas ativas
Para camadas mais formalizadas que precisam ser totalmente inicializadas de imediato ou para camadas que precisam pesquisar extensões não conhecidas pelo EGL Loader, a inicialização ativa da camada é necessária. A camada usa o get_next_layer_proc_address
que AndroidGLESLayer_Initialize
fornece para procurar uma função. Ela ainda precisará responder às solicitações AndroidGLESLayer_GetProcAddress
do carregador para que a plataforma saiba para onde encaminhar chamadas. A amostra de código a seguir demonstra como criar uma camada ativa.
namespace { std::unordered_map<std::string, EGLFuncPointer> funcMap; EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig ( EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) { EGLFuncPointer entry = funcMap["eglChooseConfig"]; typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry); return next(dpy, attrib_list, configs, config_size, num_config); } EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) { #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \ return (EGLFuncPointer)glesLayer_##func; } GETPROCADDR(eglChooseConfig); // Don't return anything for unrecognized functions return nullptr; } EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { // Note: This is where the layer would populate its function map with all the // functions it cares about const char* func = “eglChooseConfig”; funcMap[func] = get_next_layer_proc_address(layer_id, func); } EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress( const char* funcName, EGLFuncPointer next) { EGLFuncPointer entry = eglGPA(funcName); if (entry != nullptr) { return entry; } return next; } } // namespace extern "C" { __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address); } __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress( const char *funcName, EGLFuncPointer next) { return (void*)glesLayer_GetLayerProcAddress(funcName, next); } }