Lapisan GLES

Pada perangkat yang menjalankan Android 10 (level API 29) dan yang lebih tinggi, pelapisan OpenGL ES (GLES) tersedia. Aplikasi yang dapat di-debug bisa memuat lapisan GLES dari APK-nya, direktori dasarnya, atau APK lapisan yang dipilih.

Penggunaan lapisan GLES mirip dengan penggunaan lapisan validasi Vulkan.

Persyaratan

Lapisan GLES hanya didukung pada GLES versi 2.0 ke atas.

Inisialisasi lapisan

Setelah mengisi titik masuk standar, Loader EGL akan membuat instance LayerLoader GLES. Jika lapisan debug diaktifkan, LayerLoader akan memindai direktori yang ditentukan untuk lapisan, seperti yang dilakukan oleh loader Vulkan.

Jika pelapisan diaktifkan, LayerLoader akan menelusuri dan mengenumerasi daftar lapisan yang ditentukan. Daftar lapisan ditentukan oleh nama file yang dipisahkan titik dua.

LayerLoader akan melewati lapisan dalam urutan yang Anda tentukan sehingga lapisan pertama akan berada langsung di bawah aplikasi. Untuk setiap lapisan, LayerLoader akan melacak titik masuk AndroidGLESLayer_Initialize dan AndroidGLESLayer_GetProcAddress. Lapisan tersebut harus menyediakan antarmuka berikut ini agar dapat dimuat.

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

AndroidGLESLayer_Initialize() menyediakan ID untuk lapisan yang akan digunakan (layer_id) dan titik masuk yang dapat dipanggil untuk mencari fungsi di bawah lapisan tersebut. Titik masuk dapat digunakan seperti yang ditunjukkan dalam contoh kode berikut:

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

AndroidGLESLayer_GetProcAddress mengambil alamat panggilan berikutnya dalam rangkaian yang harus dipanggil oleh lapisan tersebut setelah selesai. Jika hanya ada satu lapisan, next akan mengarah langsung ke driver untuk sebagian besar fungsi.

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

Untuk setiap lapisan yang ditemukan, LayerLoader GLES akan memanggil AndroidGLESLayer_Initialize, menelusuri daftar fungsi libEGL, dan memanggil AndroidGLESLayer_GetProcAddress untuk semua fungsi yang dikenal. Penentuan cara untuk melacak alamat berikutnya bergantung pada lapisan tersebut. Jika mengintersep fungsi, lapisan akan melacak alamat fungsi tersebut. Jika lapisan tidak mengintersep fungsi, AndroidGLESLayer_GetProcAddress akan menampilkan alamat fungsi yang sama dengan yang semula dilewatkan. Selanjutnya, LayerLoader akan memperbarui daftar hook fungsi agar mengarah ke titik entri lapisan.

Lapisan tidak perlu memproses informasi yang diberikan oleh AndroidGLESLayer_Initialize dan get_next_layer_proc_address, tetapi penyediaan data ini akan memudahkan lapisan yang ada, seperti GAPID dan RenderDoc, mendukung Android. Dengan data tersebut, lapisan dapat mencari fungsi secara mandiri, tanpa perlu menunggu panggilan ke AndroidGLESLayer_GetProcAddress. Jika lapisan memilih untuk menginisialisasi sendiri sebelum loader mengkueri semua titik masuk, lapisan harus menggunakan get_next_layer_proc_address. eglGetProcAddress harus diturunkan dari rantai ke platform.

Menempatkan lapisan

LayerLoader GLES menelusuri lapisan di lokasi berikut sesuai urutan prioritas:

1. Lokasi sistem untuk root

Akses root diperlukan

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. Direktori dasar aplikasi

Aplikasi target harus dapat di-debug, atau Anda harus memiliki akses 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 eksternal

Tentukan ABI aplikasi target Anda, lalu instal APK yang berisi lapisan yang ingin Anda muat:

adb install --abi armeabi-v7a layers.apk

4. Dalam APK aplikasi target

Contoh berikut menunjukkan cara menempatkan lapisan dalam APK aplikasi:

$ 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

Mengaktifkan lapisan

Anda dapat mengaktifkan lapisan GLES per aplikasi maupun secara global. Setelan per aplikasi tetap dipertahankan meskipun perangkat di-reboot, sedangkan properti global akan dihapus saat reboot.

Model keamanan dan kebijakan Android berbeda signifikan dengan platform lain. Untuk memuat lapisan eksternal, salah satu hal berikut harus terpenuhi:

  • File manifes aplikasi target mencakup elemen meta-data berikut (hanya berlaku untuk aplikasi yang menargetkan Android 11 (API level 30) atau yang lebih baru):

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

    Anda harus menggunakan opsi ini untuk membuat profil aplikasi.

  • Aplikasi target dapat di-debug. Opsi ini memberi Anda informasi debug lainnya, tetapi mungkin akan berdampak buruk terhadap performa aplikasi Anda.

  • Aplikasi target dijalankan pada build userdebug sistem operasi yang memberikan izin akses root.

Untuk mengaktifkan lapisan per aplikasi:

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

Untuk menonaktifkan lapisan per aplikasi:

# 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

Untuk mengaktifkan lapisan secara global:

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

Membuat lapisan

Lapisan harus menampilkan dua fungsi berikut yang dijelaskan dalam inisialisasi Loader EGL:

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

Lapisan pasif

Untuk lapisan yang hanya mengintersep beberapa fungsi, lapisan yang diinisialisasi secara pasif telah optimal. Lapisan yang diinisialisasi secara pasif akan menunggu LayerLoader GLES menginisialisasi fungsi yang diperlukan.

Contoh kode berikut menunjukkan cara membuat lapisan pasif.

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

Lapisan aktif

Untuk lapisan yang lebih formal dan perlu diinisialisasi sepenuhnya di awal, atau lapisan yang perlu mencari ekstensi yang tidak diketahui oleh Loader EGL, inisialisasi lapisan aktif wajib dilakukan. Lapisan tersebut menggunakan get_next_layer_proc_address yang disediakan oleh AndroidGLESLayer_Initialize untuk mencari fungsi. Lapisan masih harus merespons permintaan AndroidGLESLayer_GetProcAddress dari loader agar platform mengetahui tujuan perutean panggilan. Contoh kode berikut ini menunjukkan cara membuat lapisan aktif.

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