Warstwy GLES

Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym dostępne są warstwy OpenGL ES (GLES). Aplikacja z możliwością debugowania może wczytywać warstwy GLES z pakietu APK, z katalogu podstawowego lub z pliku APK wybranej warstwy.

Użycie warstwy GLES jest podobne do użycia warstwy walidacji Vulkan.

Wymagania

Warstwy GLES są obsługiwane tylko w GLES w wersji 2.0 lub nowszej.

Inicjowanie warstwy

Po wypełnieniu standardowych punktów wejścia moduł ładowania EGL tworzy instancję GLES LayerLoader. Jeśli włączone są warstwy debugowania, LayerLoader skanuje określone katalogi pod kątem warstw, podobnie jak robi to wczytujący Vulkan.

Jeśli jest włączone tworzenie warstw, LayerLoader wyszukuje i wylicza określoną listę warstw. Lista warstw jest określana przez nazwy plików rozdzielone dwukropkiem.

LayerLoader przemierza warstwy w określonej przez Ciebie kolejności, więc pierwsza warstwa znajduje się bezpośrednio pod aplikacją. W każdej warstwie LayerLoader śledzi punkty wejścia AndroidGLESLayer_Initialize i AndroidGLESLayer_GetProcAddress. Warstwy muszą udostępniać te interfejsy, aby można było je wczytywać.

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

Funkcja AndroidGLESLayer_Initialize() udostępnia identyfikator warstwy, który ma być używany (layer_id) i punkt wejścia, który można wywołać, aby wyszukać funkcje znajdujące się poniżej warstwy. Punktu wejścia można użyć w sposób pokazany w tym przykładowym kodzie:

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

AndroidGLESLayer_GetProcAddress pobiera adres następnego wywołania w łańcuchu, które ma wywołać warstwa po zakończeniu. Jeśli istnieje tylko jedna warstwa, next wskazuje bezpośrednio sterownik większości funkcji.

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

W przypadku każdej warstwy, którą znajdzie LayerLoader GLES, wywołuje AndroidGLESLayer_Initialize, przechodzi listy funkcji libEGL i wywołuje AndroidGLESLayer_GetProcAddress dla wszystkich znanych funkcji. Od warstwy zależy sposób śledzenia kolejnego adresu. Jeśli warstwa przechwytuje funkcję, śledzi jej adres. Jeśli warstwa nie przechwytuje funkcji, AndroidGLESLayer_GetProcAddress zwraca ten sam adres funkcji, który została przekazana. Następnie LayerLoader aktualizuje listę punktów zaczepienia funkcji, aby wskazywała punkt wejścia warstwy.

Warstwy nie muszą nic robić z informacjami dostarczanymi przez AndroidGLESLayer_Initialize i get_next_layer_proc_address, ale udostępnienie tych danych ułatwia obsługę Androida istniejącym warstwom, takim jak Android GPU Inspector i RenderDoc. Dzięki tym danym warstwa może wyszukiwać funkcje niezależnie, zamiast czekać na wywołania AndroidGLESLayer_GetProcAddress. Jeśli warstwy decydują się na inicjowanie, zanim moduł wczytujący odpytuje wszystkie punkty wejścia, musi używać funkcji get_next_layer_proc_address. Parametr eglGetProcAddress musi być przekazywany w dół łańcucha na platformę.

Umieść warstwy

GLES LayerLoader wyszukuje warstwy w następujących lokalizacjach, w kolejności według priorytetu:

1. Lokalizacja systemowa katalogu głównego

Wymaga dostępu na poziomie roota

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. Katalog podstawowy aplikacji

Aplikacja docelowa musi umożliwiać debugowanie lub musisz mieć dostęp na poziomie roota:

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. Zewnętrzny plik APK

Określ interfejs ABI aplikacji docelowej, a następnie zainstaluj plik APK zawierający warstwy, które chcesz wczytać:

adb install --abi armeabi-v7a layers.apk

4. W pliku APK aplikacji docelowej

Poniższy przykład pokazuje, jak umieścić warstwy w pliku APK aplikacji:

$ 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

Włącz warstwy

Warstwy GLES możesz włączyć dla poszczególnych aplikacji lub globalnie. Ustawienia dla poszczególnych aplikacji są zachowywane przy ponownym uruchomieniu, a właściwości globalne są czyszczone przy ponownym uruchomieniu.

Model zabezpieczeń Androida i zasady znacznie różnią się od tych na innych platformach. Aby wczytywać warstwy zewnętrzne, musi być spełniony jeden z tych warunków:

  • Plik manifestu aplikacji docelowej zawiera ten element metadanych (dotyczy tylko aplikacji kierowanych na Androida 11 (poziom interfejsu API 30) lub nowszego):

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

    Ta opcja służy do profilowania aplikacji.

  • Aplikację docelową można debugować. Ta opcja zapewnia więcej informacji na potrzeby debugowania, ale może negatywnie wpłynąć na wydajność aplikacji.

  • Aplikacja docelowa jest uruchomiona w kompilacji userdebug systemu operacyjnego, która przyznaje dostęp na poziomie roota.

Aby włączyć warstwy dla poszczególnych aplikacji:

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

Aby wyłączyć warstwy dla poszczególnych aplikacji:

# 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

Aby włączyć warstwy globalnie:

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

Tworzenie warstwy

Warstwy muszą wyeksponować 2 funkcje opisane w sekcji Inicjowanie interfejsu EGL Loader:

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

Warstwy pasywne

W przypadku warstwy, która przechwytuje tylko kilka funkcji, optymalna jest warstwa zainicjowana pasywnie. Pasywnie zainicjowana warstwa czeka na zainicjowanie potrzebnej funkcji GLES LayerLoader.

Poniższy przykładowy kod pokazuje, jak utworzyć warstwę pasywną.

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

Aktywne warstwy

W przypadku bardziej sformalizowanych warstw, które muszą zostać w pełni zainicjowane z góry, lub warstw, które wymagają wyszukiwania rozszerzeń nieznanych modułowi ładowania EGL, wymagane jest inicjowanie aktywnej warstwy. Warstwa używa funkcji get_next_layer_proc_address udostępnianej przez AndroidGLESLayer_Initialize do wyszukiwania funkcji. Warstwa musi nadal odpowiadać na żądania AndroidGLESLayer_GetProcAddress z modułu wczytującego, aby platforma wiedziała, gdzie kierować wywołania. Poniższy przykładowy kod pokazuje, jak utworzyć aktywną warstwę.

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