En dispositivos con Android 10 (nivel de API 29) y versiones posteriores, están disponibles las capas de OpenGL ES (GLES). Una app depurable puede cargar capas de GLES desde su APK, desde su directorio base o desde un APK de capas seleccionado.
El uso de capas de GLES es similar al uso de la capa de validación de Vulkan.
Requisitos
Las capas de GLES son solo compatibles con la versión de GLES 2.0 y versiones posteriores.
Inicialización de la capa
Después de propagar los puntos de entrada estándar, EGL Loader instancia un objeto LayerLoader
de GLES. Si las capas de depuración están habilitadas, el objeto LayerLoader
analiza los directorios especificados en busca de capas, como lo hace el cargador de Vulkan.
Si las capas están habilitadas, LayerLoader
busca las capas especificadas y las enumera en una lista con nombres de archivos separados por dos puntos.
LayerLoader
desvía las capas en el orden que tú especifiques, por lo que la primera capa aparece directamente debajo de la aplicación. Por cada capa, LayerLoader
hace un seguimiento de los puntos de entrada AndroidGLESLayer_Initialize
y AndroidGLESLayer_GetProcAddress
. Las capas deben proporcionar esas interfaces para que puedan cargarse.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
proporciona un identificador para que lo utilice la capa (layer_id
) y un punto de entrada que puede llamarse para buscar las funciones que se encuentran debajo de la capa. El punto de entrada se puede usar en la siguiente muestra de código:
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
toma la dirección de la próxima llamada en la cadena a la que la capa debería llamar al finalizar. Si existe solo una capa, next
apunta directamente al controlador de la mayoría de las funciones.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
Por cada capa que LayerLoader
de GLES encuentra, llama a AndroidGLESLayer_Initialize
, recorre la lista de funciones de libEGL
y llama a AndroidGLESLayer_GetProcAddress
para todas las funciones conocidas. La capa es la que debe determinar cómo hacer un seguimiento de la próxima dirección. Si la capa intercepta una función, hace un seguimiento de la dirección de la función. Si la capa no intercepta una función, AndroidGLESLayer_GetProcAddress
muestra la misma dirección de función que le pasaron. Luego, LayerLoader
actualiza la lista de hooks de funciones para que apunte al punto de entrada de la capa.
No es necesario que las capas realicen una acción con la información que AndroidGLESLayer_Initialize
y get_next_layer_proc_address
proporcionan, pero proporcionar los datos facilita que las capas existentes, como el Inspector de GPU de Android y RenderDoc, admitan Android. Con esos datos, una capa puede buscar funciones independientemente, en lugar de esperar llamadas a AndroidGLESLayer_GetProcAddress
. Si las capas eligen inicializarse antes de que el cargador haya buscado todos los puntos de entrada, deben usar get_next_layer_proc_address
. eglGetProcAddress
debe pasar por la cadena hacia la plataforma.
Cómo colocar las capas
LayerLoader
de GLES busca capas en las siguientes ubicaciones, en orden de prioridad:
1. Ubicación del sistema para la raíz
Esta acción requiere acceso de raíz:
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. Directorio base de la aplicación
La aplicación de destino debe poder depurarse, o bien debes tener acceso a la raíz:
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
Determina la ABI de tu aplicación objetivo, luego instala un APK que contiene las capas que quieres cargar:
adb install --abi armeabi-v7a layers.apk
4. En el APK de la aplicación objetivo
En el siguiente ejemplo, se muestra cómo colocar capas en el APK de la aplicación:
$ 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
Cómo habilitar las capas
Puedes habilitar esas capas por app o de forma global. La configuración por app persiste después de los reinicios, mientras que las propiedades globales se borran.
El modelo de seguridad y las políticas de Android difieren significativamente de los de otras plataformas. Para cargar capas externas, una de las siguientes opciones debe ser verdadera:
El archivo de manifiesto de la app de destino incluye el siguiente elemento de metadatos (solo se aplica a apps que se orientan a Android 11 [nivel de API 30] o versiones posteriores):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
Debes usar esta opción para generar el perfil de tu aplicación.
La app de destino es depurable. Esta opción te brinda más información de depuración, pero puede afectar negativamente el rendimiento de la app.
La app de destino se ejecuta en una compilación userdebug del sistema operativo que otorga acceso de raíz.
Para habilitar las capas por app, haz lo siguiente:
# 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 inhabilitar las capas por app, haz lo siguiente:
# 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 habilitar las capas globalmente, haz lo siguiente:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
Cómo crear una capa
Las capas deben exponer las siguientes dos funciones descritas en Inicialización de EGL Loader:
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
Capas pasivas
Para una capa que solo intercepta algunas funciones, una capa inicializada en forma pasiva resulta óptima. La capa inicializada en forma pasiva espera a que LayerLoader
de GLES inicialice la función que necesita.
En el siguiente ejemplo de código, se muestra cómo crear una capa pasiva.
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); } }
Capas activas
Es necesario utilizar la inicialización de capas activas si se requieren capas más formalizadas que tienen que inicializarse directamente o capas que tienen que buscar extensiones no conocidas para EGL Loader. La capa usa el objeto get_next_layer_proc_address
que AndroidGLESLayer_Initialize
proporciona para buscar una función. La capa debe responder a las solicitudes de AndroidGLESLayer_GetProcAddress
desde el cargador, de manera que la plataforma sepa dónde se enrutarán las llamadas. En el siguiente ejemplo de código, se muestra cómo crear una capa activa.
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); } }