Sur les appareils équipés d'Android 10 (niveau d'API 29) ou version ultérieure, la superposition de couches OpenGL ES (GLES) est disponible. Une application débogable peut charger des couches GLES depuis son APK, son répertoire de base ou un APK de couches sélectionné.
Les couches GLES s'utilisent de la même manière que la couche de validation Vulkan.
Conditions requises
Les couches GLES ne sont compatibles qu'avec GLES 2.0 ou version ultérieure.
Initialisation des couches
Après avoir inséré des points d'entrée standards, le chargeur EGL instancie un LayerLoader
GLES. Si les couches de débogage sont activées, le LayerLoader
recherche des couches dans les répertoires spécifiés, comme le fait le chargeur Vulkan.
Si la superposition de couches est activée, le LayerLoader
recherche et recense la liste de couches spécifiée. Celle-ci est définie par des noms de fichiers séparés par un signe deux-points.
Le LayerLoader
traverse les couches dans l'ordre que vous spécifiez, de sorte que la première couche se trouve juste en dessous de l'application. Pour chaque couche, le LayerLoader
recense les points d'entrée AndroidGLESLayer_Initialize
et AndroidGLESLayer_GetProcAddress
. Les couches doivent fournir ces interfaces pour pouvoir être chargées.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
fournit un identifiant à utiliser par la couche (layer_id
) et un point d'entrée pouvant être appelé pour rechercher des fonctions sous celle-ci. Le point d'entrée peut être utilisé comme illustré dans l'exemple de code suivant :
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
obtient l'adresse du prochain appel de la chaîne que la couche doit appeler lorsqu'elle a terminé. S'il n'y a qu'une seule couche, next
pointe directement vers le pilote pour la plupart des fonctions.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
Pour chaque couche qu'il trouve, le LayerLoader
GLES appelle AndroidGLESLayer_Initialize
, parcourt les listes de fonctions libEGL
et appelle AndroidGLESLayer_GetProcAddress
pour toutes les fonctions connues. C'est à la couche de déterminer comment recenser l'adresse suivante. Si la couche intercepte une fonction, elle recense l'adresse de la fonction. Si la couche n'intercepte pas de fonction, AndroidGLESLayer_GetProcAddress
renvoie l'adresse de fonction qui lui a été transmise. Le LayerLoader
met ensuite à jour la liste de hooks de fonction pour qu'elle pointe vers le point d'entrée de la couche.
Les couches ne sont pas tenues de traiter les informations fournies par AndroidGLESLayer_Initialize
et get_next_layer_proc_address
, mais la transmission de ces données facilite la compatibilité avec Android pour les couches existantes comme Android GPU Inspector et RenderDoc. Avec ces données, une couche peut rechercher des fonctions de manière indépendante au lieu d'attendre que AndroidGLESLayer_GetProcAddress
soit appelé. Si les couches choisissent de s'initialiser avant que le chargeur n'ait interrogé tous les points d'entrée, elles doivent utiliser get_next_layer_proc_address
. eglGetProcAddress
doit être transmis tout au long de la chaîne jusqu'à la plate-forme.
Placer les couches
Le LayerLoader
GLES recherche des couches aux emplacements suivants, par ordre de priorité :
1. Emplacement système de la racine
Un accès racine est nécessaire.
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. Répertoire de base de l'application
L'application cible doit être débogable ou vous devez disposer d'un accès racine :
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 externe
Déterminez l'ABI de votre application cible, puis installez un APK contenant les couches que vous souhaitez charger :
adb install --abi armeabi-v7a layers.apk
4. Dans l'APK de l'application cible
L'exemple suivant montre comment placer des couches dans l'APK de l'application :
$ 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
Activer les couches
Vous pouvez activer les couches GLES pour une application ou de manière globale. Les paramètres par application sont conservés après le redémarrage, tandis que les propriétés globales sont effacées.
Le modèle de sécurité et les règles d'Android sont très différents de ceux des autres plates-formes. Le chargement de couches externes nécessite que l'une des conditions suivantes soit remplie :
Le fichier manifeste de l'application cible inclut l'élément meta-data suivant (concerne uniquement les applications qui ciblent Android 11 (niveau d'API 30) ou version ultérieure) :
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
Utilisez cette option pour effectuer le profilage de votre application.
L'application cible est débogable. Cette option vous fournit davantage d'informations de débogage, mais peut affecter les performances de votre application.
L'application cible est exécutée sur un build userdebug du système d'exploitation qui accorde un accès racine.
Pour activer les couches pour une application :
# 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>
Pour désactiver les couches pour une application :
# 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
Pour activer les couches de manière globale :
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
Créer une couche
Les couches doivent exposer les deux fonctions suivantes décrites dans Initialisation du chargeur EGL :
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
Couches passives
Si elle n'intercepte qu'une poignée de fonctions, une couche initialisée de manière passive est optimale. La couche initialisée de manière passive attend que le LayerLoader
GLES initialise la fonction dont elle a besoin.
L'exemple de code suivant montre comment créer une couche passive.
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); } }
Couches actives
Pour les couches plus formelles qui doivent s'initialiser complètement à l'avance ou celles qui doivent rechercher des extensions que le chargeur EGL ne connaît pas, une initialisation active est requise. La couche utilise get_next_layer_proc_address
fourni par AndroidGLESLayer_Initialize
pour rechercher une fonction. Elle doit tout de même répondre aux requêtes AndroidGLESLayer_GetProcAddress
du chargeur pour que la plate-forme sache où acheminer les appels. L'exemple de code suivant montre comment créer une couche active.
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); } }