On devices running Android 10 (API level 29) and higher, OpenGL ES (GLES) layering is available. A debuggable app can load GLES layers from its APK, from its base directory, or from a selected layer APK.
GLES layer usage is similar to Vulkan validation layer usage.
Requirements
GLES layers are supported only on GLES versions 2.0+.
Layer initialization
After populating standard entry points, the EGL Loader instantiates a GLES
LayerLoader
. If debug layers are enabled, the LayerLoader
scans specified
directories for layers, like the
Vulkan loader does.
If layering is enabled, the LayerLoader
searches for and enumerates a specified
layer list. The layer list is specified by colon separated filenames.
The LayerLoader
traverses the layers in the order you specify, so the first
layer is directly below the application. For each layer, the LayerLoader
tracks the AndroidGLESLayer_Initialize
and
AndroidGLESLayer_GetProcAddress
entry points. The layers must provide these
interfaces to be loadable.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
provides an identifier for the layer to use
(layer_id
) and an entry point that can be called to look up functions below
the layer. The entry point can be used as shown in the following code sample:
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
takes the address of the next call in the
chain that the layer should call when finished. If there is only one layer,
next
points directly to the driver for most functions.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
For each layer that the GLES LayerLoader
finds, it calls
AndroidGLESLayer_Initialize
, walks libEGL
’s function lists, and calls
AndroidGLESLayer_GetProcAddress
for all known functions. It is up to the layer
to determine how to track the next address. If the layer intercepts a function,
it tracks the function's address. If the layer doesn't intercept a function,
AndroidGLESLayer_GetProcAddress
returns the same function address it was
passed. The LayerLoader
then updates the function hook list to point to the
layer's entry point.
The layers aren't required to do anything with the information
AndroidGLESLayer_Initialize
and get_next_layer_proc_address
provide, but
providing the data makes it easier for existing layers, like
Android GPU Inspector and
RenderDoc, to support
Android. With that data, a layer can look up functions independently instead of
waiting for calls to AndroidGLESLayer_GetProcAddress
. If the layers choose to
initialize themselves before the loader has queried all the entry points, they
must use get_next_layer_proc_address
. eglGetProcAddress
must
be passed down the chain to the platform.
Place layers
The GLES LayerLoader
searches for layers in the following locations, in order
of priority:
1. System location for root
This requires root access
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. Application's base directory
Target application must be debuggable, or you must have root access:
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. External APK
Determine the ABI of your target application, then install an APK containing the layers you wish to load:
adb install --abi armeabi-v7a layers.apk
4. In the target application's APK
The following example shows how to place layers in the application APK:
$ 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
Enable layers
You can enable GLES layers either per app or globally. Per-app settings persist across reboots, while global properties are cleared on reboot.
Android's security model and policies differ significantly from other platforms. In order to load external layers, one of the following must be true:
The target app's manifest file includes the following meta-data element (only applies to apps that target Android 11 (API level 30) or higher):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
You should use this option to profile your application.
The target app is debuggable. This option gives you more debug information, but might negatively affect the performance of your app.
The target app is run on a userdebug build of the operating system which grants root access.
To enable layers per 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>
To disable layers per 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
To enable layers globally:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
Create a layer
Layers must expose the following two functions described in EGL Loader initialization:
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
Passive layers
For a layer that only intercepts a handful of functions, a
passively initialized layer is optimal. The passively initialized layer waits
for GLES LayerLoader
to initialize the function it needs.
The following code sample shows how to create a passive layer.
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); } }
Active layers
For more formalized layers that need to fully initialize up front, or layers
that need to look up extensions not known to the EGL Loader, active layer
initialization is required. The layer uses
the get_next_layer_proc_address
that AndroidGLESLayer_Initialize
provides to
look up a function. The layer must still respond to
AndroidGLESLayer_GetProcAddress
requests from the loader so the platform knows
where to route calls. The following code sample shows how to create an active
layer.
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); } }