GLES 層

在搭載 Android 10 (API 級別 29) 以上版本的裝置中,可以使用 OpenGL ES (GLES) 分層功能。可進行偵錯的應用程式可以從 APK、基本目錄或所選圖層 APK 載入 GLES 圖層。

GLES 層的使用方式與 Vulkan 驗證層類似。

相關規定

GLES 圖層僅適用於 GLES 2.0 以上版本。

圖層初始化

填入標準進入點後,EGL 載入器會將 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() 提供要使用的圖層 ID (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 檢查器RenderDoc) 更輕鬆地支援 Android。圖層可以利用這些資料單獨查詢函式,不必等待呼叫 AndroidGLESLayer_GetProcAddress。如果圖層選擇在載入器查詢所有進入點之前自行初始化,則必須使用 get_next_layer_proc_addresseglGetProcAddress 必須向下傳遞至平台。

放置圖層

GLES LayerLoader 會按照優先順序搜尋下列位置中的圖層:

1. 系統位置的根層級

這需要 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 的安全性模型和政策與其他平台有極大差異。如要載入外部層,必須符合下列其中一項條件:

  • 目標應用程式的資訊清單檔案包含下列中繼資料元素 (僅適用於指定 Android 11 (API 級別 30) 以上版本的應用程式):

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

    建議您使用這個選項剖析應用程式。

  • 目標應用程式可進行偵錯。這個選項可讓您取得更多偵錯資訊,但可能會降低應用程式的效能。

  • 目標應用程式是在會授予 Root 存取權的作業系統使用者偵錯版本上執行。

如何啟用個別應用程式的圖層:

# 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 載入器初始化中所述的兩個函式:

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 載入器未知擴充功能的圖層,則必須採用主動初始化圖層。圖層會使用 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);
  }
}