Warstwy GLES

Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym dostępna jest obsługa warstw OpenGL ES (GLES). Aplikacja z możliwością debugowania może wczytać warstwy GLES z pakietu APK, z katalogu podstawowego lub z pakietu APK wybranej warstwy.

Użycie warstwy GLES jest podobne do korzystania z warstwy walidacji interfejsu Vulkan.

Wymagania

Warstwy GLES są obsługiwane tylko w GLES w wersji 2.0 i nowszych.

Inicjowanie warstwy

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

Jeśli włączone jest warstwy, LayerLoader wyszukuje i wylicza określoną listę warstw. Lista warstw jest określana za pomocą nazw plików rozdzielonych dwukropkiem.

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

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

AndroidGLESLayer_Initialize() zawiera identyfikator warstwy używanej (layer_id) i punkt wejścia, który można wywołać, aby wyszukać funkcje poniżej warstwy. Punktu wejścia można użyć w sposób przedstawiony 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 powinna wywołać warstwa po zakończeniu. Jeśli istnieje tylko jedna warstwa, next wskazuje bezpośrednio sterownik dla większości funkcji.

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

W przypadku każdej warstwy znalezionej przez LayerLoader GLES wywołuje metodę AndroidGLESLayer_Initialize, przechodzi z list funkcji libEGL i wywołuje AndroidGLESLayer_GetProcAddress, aby uzyskać dostęp do wszystkich znanych funkcji. Wybór sposobu śledzenia kolejnego adresu zależy od warstwy. Jeśli warstwa przechwytuje funkcję, śledzi jej adres. Jeśli warstwa nie przechwytuje funkcji, AndroidGLESLayer_GetProcAddress zwraca ten sam adres funkcji, która została przekazana. LayerLoader aktualizuje listę punktów zaczepienia funkcji, aby wskazywała punkt wejścia warstwy.

Warstwy nie muszą w żaden sposób korzystać z informacji dostarczanych przez AndroidGLESLayer_Initialize i get_next_layer_proc_address, ale ich podanie ułatwia obsługę Androida w istniejących warstwach, takich jak Android GPU Inspector i RenderDoc. Dzięki tym danym warstwa może wyszukiwać funkcje niezależnie, zamiast czekać na wywołania funkcji AndroidGLESLayer_GetProcAddress. Jeśli warstwy zdecydują się inicjować się, zanim program ładujący odpytuje wszystkie punkty wejścia, muszą użyć metody get_next_layer_proc_address. Wartość eglGetProcAddress musi być przekazywana w łańcuchu do platformy.

Umieść warstwy

LayerLoader GLES wyszukuje warstwy w tych lokalizacjach według priorytetu:

1. Lokalizacja systemowa katalogu głównego

Wymaga to dostępu do 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 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 pakiet 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 pakiecie 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łączanie warstw

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

Model zabezpieczeń i zasady Androida znacznie różnią się od innych platform. Aby możliwe było wczytywanie warstw zewnętrznych, musi być spełniony jeden z tych warunków:

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

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

    Tej opcji należy użyć do profilowania aplikacji.

  • Docelową aplikację można debugować. Dzięki temu uzyskasz więcej informacji na temat debugowania, ale może ona niekorzystnie wpłynąć na działanie aplikacji.

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

Aby włączyć warstwy na aplikację:

# 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 na aplikację:

# 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ą udostępniać 2 następujące funkcje opisane w sekcji Inicjowanie modułu wczytywania EML:

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

Warstwy pasywne

W przypadku warstwy, która przechwytuje tylko kilka funkcji, optymalna jest warstwa inicjowana pasywnie. Warstwa zainicjowana pasywnie 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ą być w pełni zainicjowane z góry, lub warstw, które wymagają wyszukiwania rozszerzeń nieznanych dla modułu ładowania EGL, wymagane jest inicjowanie aktywnej warstwy. Warstwa korzysta z elementu get_next_layer_proc_address udostępnionego przez AndroidGLESLayer_Initialize, aby wyszukać funkcję. Warstwa musi nadal odpowiadać na żądania AndroidGLESLayer_GetProcAddress z modułu ładowania, aby platforma wiedziała, dokąd kierować połączenia. 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);
  }
}