Слои ГЛЕС

На устройствах под управлением Android 10 (уровень API 29) и выше доступно наложение слоев OpenGL ES (GLES). Отлаживаемое приложение может загружать слои GLES из своего APK, из своего базового каталога или из выбранного APK-файла.

Использование слоя 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 принимает адрес следующего вызова в цепочке, который слой должен вызвать после завершения. Если слой только один, 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 для существующих слоёв, таких как Android GPU Inspector и RenderDoc . С этими данными слой может самостоятельно искать функции, не дожидаясь вызовов AndroidGLESLayer_GetProcAddress . Если слои решат инициализировать себя до того, как загрузчик запросит все точки входа, они должны использовать get_next_layer_proc_address . Например, eglGetProcAddress должен быть передан платформе по цепочке.

Разместите слои

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 существенно отличаются от других платформ. Для загрузки внешних слоёв необходимо выполнение одного из следующих условий:

  • Файл манифеста целевого приложения включает следующий элемент метаданных (применимо только к приложениям, предназначенным для 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, требуется инициализация активного слоя. Слой использует метод get_next_layer_proc_address , предоставляемый AndroidGLESLayer_Initialize , для поиска функции. Слой по-прежнему должен отвечать на запросы 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);
  }
}