GLES 层

在运行 Android 10 (API 级别 29) 及更高版本的设备上,可以使用 OpenGL ES (GLES) 分层。可调试应用可以从其 APK、其基本目录或选定的层 APK 加载 GLES 层。

GLES 层的用法与 Vulkan 验证层 的用法类似。

要求

仅在 GLES 2.0+ 版本上支持 GLES 层。

层初始化

填充标准入口点后,EGL Loader 会实例化一个 GLES LayerLoader。如果启用了调试层,LayerLoader 会扫描指定的目录以查找层,就像 Vulkan 加载器一样。

如果启用了分层功能,LayerLoader 会搜索并枚举指定的层列表。层列表由以冒号分隔的文件名指定。

LayerLoader 按照您指定的顺序遍历各个层,因此第一个层直接位于应用下方。对于每个层,LayerLoader 会跟踪 AndroidGLESLayer_InitializeAndroidGLESLayer_GetProcAddress 入口点。这些层必须提供这些接口才能加载。

typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*);
void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))

AndroidGLESLayer_Initialize() 提供一个供该层使用的标识符 (layer_id) 以及一个可用于查找该层下方函数的入口点。该入口点可以按照以下代码示例所示的方式使用

const char* func = "eglFoo";
void* gpa = get_next_layer_proc_address(layer_id, func);

AndroidGLESLayer_GetProcAddress 接受链中下一调用的地址,该层完成时应调用此地址。如果只有一个层,则对于大多数函数,next 直接指向驱动程序。

typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)

对于 GLES LayerLoader 找到的每个层,它都会调用 AndroidGLESLayer_Initialize,遍历 libEGL 的函数列表,并针对所有已知函数调用 AndroidGLESLayer_GetProcAddress。如何跟踪下一地址取决于该层。如果该层拦截某个函数,它会跟踪该函数的地址。如果该层不拦截某个函数,AndroidGLESLayer_GetProcAddress 会返回传递给它的函数地址。然后,LayerLoader 会更新函数钩子列表以指向该层的入口点。

各层无需对 AndroidGLESLayer_Initializeget_next_layer_proc_address 提供的信息进行任何处理,但提供这些数据可让 Android GPU InspectorRenderDoc 等现有层更轻松地支持 Android。有了这些数据,层就可以独立查找函数,而无需等待对 AndroidGLESLayer_GetProcAddress 的调用。如果各层选择在加载器查询所有入口点之前自行初始化,则必须使用 get_next_layer_proc_addresseglGetProcAddress 必须沿着链传递给平台。

放置层

GLES LayerLoader 会按照以下优先级顺序在以下位置搜索层

1. 系统位置 (root)

这需要 root 访问权限

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. 应用的基本目录

目标应用必须是可调试的,或者您必须拥有 root 访问权限

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

确定目标应用的 ABI,然后安装包含您要加载的层的 APK

adb install --abi armeabi-v7a layers.apk

4. 在目标应用的 APK 中

以下示例显示了如何将层放置在应用 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

启用层

您可以按应用或全局启用 GLES 层。按应用的设置在重新启动后会保留,而全局属性会在重新启动时清除。

Android 的安全模型和政策与其他平台有很大差异。为了加载外部层,必须满足以下条件之一

  • 目标应用的清单文件包含以下 meta-data 元素(仅适用于以 Android 11 (API 级别 30) 或更高版本为目标平台的应用)

    <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />

    您应使用此选项对您的应用进行性能分析。

  • 目标应用是可调试的。此选项可为您提供更多调试信息,但可能会对您的应用性能产生负面影响。

  • 目标应用在具有 root 访问权限的 userdebug 版本的操作系统上运行。

按应用启用层

# 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>

按应用停用层

# 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

全局启用层

# This attempts to load layers for all applications, including native
# executables
adb shell setprop debug.gles.layers <layer1:layer2:layerN>

创建层

层必须公开 EGL Loader 初始化 中描述的以下两个函数

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

被动层

对于仅拦截少数函数的层,被动初始化的层是最佳选择。被动初始化的层会等待 GLES LayerLoader 初始化其所需的函数。

以下代码示例显示了如何创建被动层。

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);
  }
}

主动层

对于需要完全提前初始化的更正式的层,或需要查找 EGL Loader 未知的扩展的层,需要进行主动层初始化。该层使用 AndroidGLESLayer_Initialize 提供的 get_next_layer_proc_address 来查找函数。该层仍必须响应来自加载器的 AndroidGLESLayer_GetProcAddress 请求,以便平台知道将调用路由到何处。以下代码示例显示了如何创建主动层。

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);
  }
}