Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym dostępne jest warstwowanie OpenGL ES (GLES). Aplikacja z możliwością debugowania może wczytywać warstwy GLES z pliku APK, katalogu podstawowego lub wybranego pliku APK warstwy.
Użycie warstwy GLES jest podobne do użycia warstwy walidacji Vulkan.
Wymagania
Warstwy GLES są obsługiwane tylko w przypadku GLES w wersji 2.0 lub nowszej.
Inicjowanie warstwy
Po wypełnieniu standardowych punktów wejścia moduł ładujący EGL tworzy instancję GLES LayerLoader. Jeśli warstwy debugowania są włączone, LayerLoader skanuje określone katalogi pod kątem warstw, podobnie jak moduł ł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 LayerLoader śledzi punkty wejścia AndroidGLESLayer_Initialize i AndroidGLESLayer_GetProcAddress. Warstwy muszą udostępniać te interfejsy, aby można było je wczytać.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize() udostępnia identyfikator warstwy, której należy użyć (layer_id), oraz 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 wskazuje bezpośrednio na sterownik w przypadku większości funkcji.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
Dla każdej warstwy znalezionej przez GLES LayerLoader wywołuje on funkcję AndroidGLESLayer_Initialize, przechodzi przez 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 przechwytuje funkcji, AndroidGLESLayer_GetProcAddress zwraca ten sam adres funkcji, który został przekazany. 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 podanie tych danych ułatwia istniejącym warstwom, takim jak Android GPU Inspector i RenderDoc, obsługę Androida. Dzięki tym danym warstwa może wyszukiwać funkcje niezależnie, zamiast czekać na wywołania AndroidGLESLayer_GetProcAddress. Jeśli warstwy zdecydują się zainicjować się, zanim program ładujący wyśle zapytanie do wszystkich punktów wejścia, muszą użyć get_next_layer_proc_address. Wartość eglGetProcAddress musi zostać przekazana w łańcuchu do platformy.
Warstwy miejsc
GLES LayerLoader wyszukuje warstwy w tych lokalizacjach (w kolejności priorytetu):
1. Lokalizacja systemowa dla konta root
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 mieć możliwość debugowania 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, a właściwości globalne są wtedy czyszczone.
Model zabezpieczeń i zasady Androida znacznie różnią się od innych platform. Aby można było 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" />Użyj tej opcji, aby utworzyć profil aplikacji.
Aplikacja docelowa umożliwia debugowanie. Ta opcja zapewnia więcej informacji na potrzeby debugowania, ale może negatywnie wpłynąć na wydajność aplikacji.
Aplikacja docelowa jest uruchamiana w kompilacji userdebug systemu operacyjnego, 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 ładującego EGL:
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
Warstwy pasywne
W przypadku warstwy, która przechwytuje tylko kilka funkcji, optymalna jest warstwa inicjowana pasywnie. Warstwa zainicjowana pasywnie czeka, aż GLES LayerLoader zainicjuje potrzebną 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ą zostać w pełni zainicjowane z góry, lub warstw, które muszą wyszukiwać rozszerzenia nieznane modułowi ładującemu EGL, wymagana jest inicjalizacja aktywnej warstwy. Warstwa używa get_next_layer_proc_address, który AndroidGLESLayer_Initialize udostępnia do wyszukiwania funkcji. Warstwa musi nadal odpowiadać na żądania AndroidGLESLayer_GetProcAddress z modułu ładują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); } }