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