GLES レイヤ

Android 10(API レベル 29)以上を搭載している Android デバイスでは、OpenGL ES(GLES)レイヤ化を利用できます。デバッグ可能アプリは、APK、ベース ディレクトリ、選択されたレイヤ APK から GLES レイヤを読み込めます。

GLES レイヤの使用方法は、Vulkan 検証レイヤと似ています。

要件

GLES レイヤがサポートされるのは、GLES バージョン 2.0 以降に限られます。

レイヤの初期化

EGL ローダは、標準のエントリ ポイントを設定した後、GLES LayerLoader をインスタンス化します。デバッグレイヤが有効になっている場合、LayerLoaderVulkan ローダと同様に、指定されたディレクトリをスキャンしてレイヤを探します。

レイヤ化が有効になっている場合、LayerLoader は指定されたレイヤリストを検索して列挙します。レイヤリストは、コロン区切りのファイル名で指定します。

LayerLoader は指定された順序でレイヤをトラバースします。したがって、最初のレイヤはアプリの直下になります。LayerLoader は、レイヤごとに AndroidGLESLayer_Initialize および AndroidGLESLayer_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 は、完了時にレイヤが呼び出す必要があるチェーン内の次の呼び出しのアドレスを受け取ります。レイヤが 1 つしかない場合、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_address を使用する必要があります。また、チェーンに沿って eglGetProcAddress をプラットフォームに渡す必要があります。

レイヤを配置する

GLES LayerLoaderは、以下の場所で優先度順にレイヤを検索します。

1. ルートに対応するシステムの場所

これにはルートアクセス権限が必要です。

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. アプリのベース ディレクトリ

ターゲット アプリがデバッグ可能であるか、ルートアクセス権限が必要です。

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" />

    このオプションを使用してアプリをプロファイリングする必要があります。

  • ターゲット アプリがデバッグ可能である。このオプションを使用すると、より詳細なデバッグ情報を得られますが、アプリのパフォーマンスに悪影響を及ぼす可能性があります。

  • ルートアクセスを許可するオペレーティング システムの 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 ローダの初期化の説明で述べた以下の 2 つの関数を公開する必要があります。

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