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 GLES w wersji 2.0 lub nowszej.
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 Vulkan.
Jeśli warstwowanie jest włączone, 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 pierwszy z nich
warstwa znajduje się bezpośrednio pod aplikacją. W przypadku każdej warstwy parametr LayerLoader
śledzi AndroidGLESLayer_Initialize
oraz
AndroidGLESLayer_GetProcAddress
punktów wejścia. 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))
AndroidGLESLayer_Initialize()
udostępnia identyfikator warstwy, której ma użyć
(layer_id
) i punktu wejścia, który można wywołać, aby wyszukać funkcje poniżej
w pobliżu. 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
przejmie adres następnego połączenia w
łańcuch, który ma 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. Decyzja należy do warstwy
aby określić, 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ń z informacjami
AndroidGLESLayer_Initialize
i get_next_layer_proc_address
udostępniają, ale
udostępnianie danych ułatwia tworzenie warstw,
Android GPU Inspector oraz
RenderDoc do obsługi
na urządzeniu z Androidem. Dzięki tym danym warstwa może wyszukiwać funkcje niezależnie,
oczekiwanie na połączenie z numerem AndroidGLESLayer_GetProcAddress
. Jeśli warstwy
są inicjowane przed wykonaniem zapytania o wszystkie punkty wejścia,
musi używać parametru get_next_layer_proc_address
. Wartość eglGetProcAddress
musi być przekazywana w łańcuchu do platformy.
Umieszczanie warstw
GLES LayerLoader
wyszukuje warstwy w następujących lokalizacjach w kolejności
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 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
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. Zachowywanie ustawień dla poszczególnych aplikacji podczas ponownego uruchamiania urządzenia, a właściwości globalne są czyszczone przy ponownym uruchomieniu.
Model zabezpieczeń na Androidzie i zasady różnią się znacznie różnią się od innych platform. Żeby wczytać warstwy zewnętrzne, musi być spełniony ten warunek:
Plik manifestu aplikacji docelowej zawiera te elementy element meta-data (ma zastosowanie tylko do 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 danych debugowania, ale mogą negatywnie wpływać na wydajność aplikacji.
Aplikacja docelowa jest uruchamiana w wersji systemu operacyjnego userdebug, która przyznaje dostęp root.
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ą 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. Pasywna warstwa oczekuje
w GLES LayerLoader
, aby zainicjować 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
Do bardziej sformalizowanych warstw, które muszą w pełni zainicjować inicjalizację na początku, lub warstw
które muszą wyszukiwać rozszerzenia nieznane procesowi wczytywania EGL, aktywna warstwa
zainicjowanie jest wymagane. Warstwę tę wykorzystuje get_next_layer_proc_address
, aby AndroidGLESLayer_Initialize
mogła odszukać funkcję. Warstwa musi w dalszym ciągu odpowiadać na
AndroidGLESLayer_GetProcAddress
żądań od wczytującego, dzięki którym platforma wie
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); } }