Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym dostępna jest możliwość tworzenia warstw OpenGL ES (GLES). Aplikacja z możliwością debugowania może wczytywać warstwy GLES z pliku APK, z katalogu podstawowego lub z wybranego pliku APK warstwy.
Sposób korzystania z poziomu GLES jest podobny do korzystania z poziomu walidacji Vulkana.
Wymagania
Warstwy GLES są obsługiwane tylko w wersjach GLES 2.0 i nowszych.
Inicjowanie warstwy
Po wypełnieniu standardowych punktów wejścia EGL Loader tworzy instancję GLESLayerLoader
. Jeśli warstwy debugowania są włączone, LayerLoader
skanuje określone katalogi pod kątem warstw, tak jak ładownik Vulkana.
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.
Funkcja LayerLoader
przemierza warstwy w określonej kolejności, więc pierwsza warstwa znajduje się bezpośrednio pod aplikacją. W przypadku każdego poziomu element LayerLoader
śledzi punkty wejścia AndroidGLESLayer_Initialize
i AndroidGLESLayer_GetProcAddress
. Warstwy muszą udostępniać te interfejsy, aby można je było wczytać.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
Parametr AndroidGLESLayer_Initialize()
zawiera identyfikator, który ma być używany przez warstwę (layer_id
), i punkt wejścia, który można wywołać, aby wyszukać funkcje znajdujące się poniżej warstwy. Punkt wejścia może być użyty 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 powinna wywołać warstwa po zakończeniu. Jeśli jest tylko jedna warstwa,
next
wskazuje bezpośrednio na funkcję w przypadku większości funkcji.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
W przypadku każdego poziomu, który znajdzie GLES LayerLoader
, wywołuje funkcję AndroidGLESLayer_Initialize
, przechodzi przez listy funkcji libEGL
i wywołuje funkcję AndroidGLESLayer_GetProcAddress
dla wszystkich znanych funkcji. To od warstwy zależy, 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. Następnie LayerLoader
aktualizuje listę funkcji, aby wskazywała punkt wejścia warstwy.
Warstwy nie muszą wykonywać żadnych działań na podstawie informacji udostępnianych przez AndroidGLESLayer_Initialize
i get_next_layer_proc_address
, ale udostępnienie tych danych ułatwia obsługę Androida przez istniejące warstwy, takie jak Android GPU Inspector i RenderDoc. Dzięki tym danym warstwa może samodzielnie wyszukiwać funkcji zamiast czekać na wywołania funkcji 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
. Wartość eglGetProcAddress
musi być przekazywana w dół łańcucha do platformy.
Umieszczanie warstw
GLES LayerLoader
szuka warstw w tych lokalizacjach (w kolejności priorytetów):
1. Lokalizacja systemowa katalogu głównego
Wymaga to uprawnień 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ć dostępna do debugowania 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 plik APK zawierający warstwy, które chcesz wczytać:
adb install --abi armeabi-v7a layers.apk
4. W pliku APK aplikacji docelowej
Ten przykład pokazuje, jak umieszczać 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 poszczególnych aplikacji są zachowywane po ponownym uruchomieniu, podczas gdy właściwości globalne są usuwane.
Model i zasady zabezpieczeń Androida różnią się znacznie od tych na innych platformach. 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 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" />
Tej opcji należy używać do profilowania aplikacji.
Aplikacja docelowa jest dostępna do debugowania. Ta opcja zapewnia więcej informacji debugujących, ale może negatywnie wpływać na wydajność aplikacji.
Aplikacja docelowa jest uruchomiona w kompilacji userdebug systemu operacyjnego, która przyznaje dostęp na poziomie roota.
Aby włączyć warstwy na potrzeby 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ą udostępniać 2 funkcje opisane w inicjalizacji ładowarki 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 na zainicjowanie funkcji, której potrzebuje 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 muszą wyszukiwać rozszerzenia nieznane ładowarce EGL, wymagana jest inicjalizacja aktywnej warstwy. Warstwę tę wykorzystuje get_next_layer_proc_address
, aby AndroidGLESLayer_Initialize
mogła odszukać funkcję. Warstwa musi nadal odpowiadać na żądania AndroidGLESLayer_GetProcAddress
wysyłane z programu 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); } }