Android 10(API レベル 29)以上を搭載している Android デバイスでは、OpenGL ES(GLES)レイヤ化を利用できます。デバッグ可能アプリは、APK、ベース ディレクトリ、選択されたレイヤ APK から GLES レイヤを読み込めます。
GLES レイヤの使用方法は、Vulkan 検証レイヤと似ています。
要件
GLES レイヤがサポートされるのは、GLES バージョン 2.0 以降に限られます。
レイヤの初期化
EGL ローダは、標準のエントリ ポイントを設定した後、GLES LayerLoader
をインスタンス化します。デバッグレイヤが有効になっている場合、LayerLoader
は Vulkan ローダと同様に、指定されたディレクトリをスキャンしてレイヤを探します。
レイヤ化が有効になっている場合、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_Initialize
と get_next_layer_proc_address
が提供する情報について何かをする必要はありませんが、Android GPU Inspector や RenderDoc のような既存のレイヤは、データを提供されることで簡単に 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); } }