Warstwy GLES

Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym dostępna jest warstwa OpenGL ES (GLES). Aplikacja z możliwością debugowania może wczytywać warstwy GLES z pliku APK, z katalogu głównego lub z wybranego pliku APK warstwy.

Korzystanie z warstwy GLES jest podobne do korzystania z warstwy weryfikacji Vulkan.

Wymagania

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

Inicjowanie warstwy

Po wypełnieniu standardowych punktów wejścia moduł EGL Loader tworzy instancję GLES LayerLoader. Jeśli warstwy debugowania są włączone, LayerLoader skanuje określone katalogi w poszukiwaniu warstw, tak jak program ładujący Vulkan.

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

LayerLoader przechodzi przez warstwy w określonej przez Ciebie kolejności, więc pierwsza warstwa znajduje się bezpośrednio pod aplikacją. W przypadku każdej warstwy element LayerLoader śledzi punkty wejścia AndroidGLESLayer_InitializeAndroidGLESLayer_GetProcAddress. Aby można było wczytać warstwy, muszą one udostępniać te interfejsy:

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

AndroidGLESLayer_Initialize() udostępnia identyfikator warstwy do użycia (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 pokazany w tym przykładowym kodzie:

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

AndroidGLESLayer_GetProcAddress przyjmuje adres następnego wywołania w łańcuchu, które warstwa powinna wywołać po zakończeniu. Jeśli jest tylko jedna warstwa,next w przypadku większości funkcji wskazuje ona bezpośrednio kierowcę.

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

W przypadku każdej warstwy, którą znajdzie GLES LayerLoader, wywołuje funkcję AndroidGLESLayer_Initialize, przegląda listy funkcji libEGL i wywołuje funkcję AndroidGLESLayer_GetProcAddress dla wszystkich znanych funkcji. To warstwa decyduje, jak śledzić następny adres. Jeśli warstwa przechwyci funkcję, śledzi jej adres. Jeśli warstwa nie przechwyci funkcji,AndroidGLESLayer_GetProcAddress zwróci ten sam adres funkcji, który został jej przekazany. LayerLoader aktualizuje listę zaczepów funkcji, aby wskazywała punkt wejścia warstwy.

Warstwy nie muszą nic robić z informacjami, które AndroidGLESLayer_Initializeget_next_layer_proc_address udostępniają, ale udostępnianie danych ułatwia istniejącym warstwom, takim jak Android GPU InspectorRenderDoc, obsługę Androida. Na podstawie tych danych warstwa może wyszukiwać funkcje niezależnie, zamiast czekać na wywołania funkcji AndroidGLESLayer_GetProcAddress. Jeśli warstwy zdecydują się zainicjować przed tym, jak moduł wczytujący zapyta wszystkie punkty wejścia, muszą użyć funkcji get_next_layer_proc_address. eglGetProcAddress musi zostać przekazany w dół łańcucha do platformy.

Umieszczanie warstw

GLES LayerLoader szuka warstw w tych lokalizacjach w kolejności priorytetu:

1. Lokalizacja systemowa dla użytkownika root

Wymaga 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 być debugowalna lub musisz mieć dostęp do 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łączanie warstw

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

Model zabezpieczeń i zasady Androida znacznie różnią się od innych platform. Aby wczytać warstwy zewnętrzne, 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 interfejsu API 30) lub nowszego):

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

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

  • Aplikacja docelowa może być debugowana. Ta opcja zapewnia więcej informacji do debugowania, ale może negatywnie wpłynąć na wydajność aplikacji.

  • Docelowa aplikacja jest uruchamiana w wersji systemu operacyjnego userdebug, która przyznaje dostęp do roota.

Aby włączyć warstwy w poszczególnych aplikacjach:

# 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 w poszczególnych aplikacjach:

# 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ć te 2 funkcje opisane w sekcji Inicjowanie modułu wczytującego EGL:

AndroidGLESLayer_Initialize
AndroidGLESLayer_GetProcAddress

Warstwy pasywne

W przypadku warstwy, która przechwytuje tylko kilka funkcji, optymalna jest warstwa zainicjowana pasywnie. Warstwa zainicjowana pasywnie czeka, aż GLESLayerLoader zainicjuje potrzebną jej funkcję.

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 muszą wyszukiwać rozszerzenia nieznane programowi EGL Loader, wymagana jest inicjacja aktywnej warstwy. Warstwa używa get_next_layer_proc_address, które AndroidGLESLayer_Initialize udostępnia, aby wyszukać funkcję. Warstwa musi nadal odpowiadać na żądania AndroidGLESLayer_GetProcAddress z programu wczytującego, aby platforma wiedziała, gdzie 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);
  }
}