GLES layers

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+.

EGL Loader 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.

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 GAPID 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.

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)
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_GetProcAddres(
    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_GetProcAddres(
    const char *funcName, EGLFuncPointer next) {
    return (void*)glesLayer_GetLayerProcAddress(funcName, next);
  }
}