Android 10(API 수준 29) 이상을 실행하는 기기에서 OpenGL ES(GLES) 레이어링을 사용할 수 있습니다. 디버그 가능한 앱은 APK, 기본 디렉터리, 선택한 레이어 APK 중 하나에서 GLES 레이어를 로드할 수 있습니다.
GLES 레이어 사용법은 Vulkan 유효성 검사 계층 사용법과 비슷합니다.
요구사항
GLES 레이어는 GLES 2.0 이상 버전에서만 지원됩니다.
레이어 초기화
표준 진입점을 채운 이후 EGL Loader는 GLES LayerLoader
를 인스턴스화합니다. 디버그 레이어가 사용 설정되었다면 LayerLoader
는 Vulkan 로더가 하는 것처럼 지정 디렉터리에서 레이어를 검색합니다.
레이어링이 사용 설정되었다면 LayerLoader
는 지정된 레이어 목록을 검색하여 열거합니다. 레이어 목록은 콜론으로 구분된 파일 이름으로 지정됩니다.
LayerLoader
는 지정된 순서대로 레이어를 탐색하므로, 첫 번째 레이어는 애플리케이션 바로 아래에 위치합니다. LayerLoader
는 각 레이어에 대해 AndroidGLESLayer_Initialize
및 AndroidGLESLayer_GetProcAddress
진입점을 추적합니다. 레이어는 이 인터페이스를 로드 가능하도록 제공해야 합니다.
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
는 레이어가 사용할 식별자(layer_id
) 및 레이어 아래에서 함수를 조회하기 위해 호출할 수 있는 진입점을 제공합니다. 다음 코드 샘플에서와 같이 진입점을 사용할 수 있습니다.
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
는 완료 시 레이어가 호출해야 할 체인 내 다음 호출의 주소를 가져옵니다. 레이어가 하나만 있다면 next
는 대부분의 함수에 대해 드라이버를 직접 가리킵니다.
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
GLES LayerLoader
가 찾은 각 레이어에 대해 GLES LayerLoader는 AndroidGLESLayer_Initialize
를 호출하고, libEGL
의 함수 목록을 탐색하고, 알려진 모든 함수에 대해 AndroidGLESLayer_GetProcAddress
를 호출합니다. 레이어에 따라 다음 주소를 추적하는 방법이 결정됩니다. 레이어가 함수를 가로채면 함수의 주소를 추적합니다. 레이어가 함수를 가로채지 않으면 AndroidGLESLayer_GetProcAddress
는 전달된 것과 동일한 함수 주소를 반환합니다. 그런 다음, LayerLoader
는 함수 후크 목록을 업데이트하여 레이어의 진입점을 가리킵니다.
레이어는 AndroidGLESLayer_Initialize
및 get_next_layer_proc_address
가 제공하는 정보로 아무것도 할 필요가 없습니다. 그러나 데이터를 제공하면 Android GPU 검사기 및 RenderDoc과 같은 기존 레이어가 더 쉽게 Android를 지원할 수 있습니다. 이 데이터를 통해 레이어는 AndroidGLESLayer_GetProcAddress
호출을 대기하지 않고 독립적으로 함수를 조회할 수 있습니다. 로더가 모든 진입점을 쿼리하기 전에 레이어가 자체적으로 초기화하도록 선택한다면 레이어는 get_next_layer_proc_address
를 사용해야 합니다. eglGetProcAddress
는 체인을 따라 플랫폼에 전달되어야 합니다.
레이어 위치
GLES LayerLoader
는 다음 위치에서 우선순위에 따라 레이어를 검색합니다.
1. 시스템의 루트 위치
루트 액세스 권한이 필요합니다.
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. 애플리케이션의 기본 디렉터리
타겟 애플리케이션이 디버그 가능하거나 개발자에게 루트 액세스 권한이 있어야 합니다.
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. 외부 APK
타겟 애플리케이션의 ABI를 결정한 다음 로드할 레이어가 포함된 APK를 설치합니다.
adb install --abi armeabi-v7a layers.apk
4. 타겟 애플리케이션의 APK
다음 예에서는 애플리케이션 APK에서 레이어를 배치하는 방법을 보여줍니다.
$ 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
레이어 사용 설정
전역적으로 또는 앱별로 GLES 레이어를 사용 설정할 수 있습니다. 앱별 설정은 재부팅을 거쳐도 지속되는 반면, 전역 속성은 재부팅 시 지워집니다.
Android의 보안 모델 및 정책은 다른 플랫폼과 크게 다릅니다. 외부 레이어를 로드하려면 다음 중 하나가 true여야 합니다.
타겟 앱의 매니페스트 파일에는 다음 meta-data 요소가 포함됩니다(Android 11(API 수준 30) 이상을 타겟팅하는 앱에만 적용됨).
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
이 옵션을 사용하여 애플리케이션을 프로파일링해야 합니다.
타겟 앱이 디버그 가능합니다. 이 옵션은 추가 디버그 정보를 제공하지만 앱 성능에 부정적인 영향을 줄 수 있습니다.
타겟 앱이 루트 액세스 권한을 부여하는 운영체제의 userdebug 빌드에서 실행됩니다.
앱별로 레이어 사용 설정하기:
# 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>
앱별로 레이어 사용 중지하기:
# 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
전역적으로 레이어 사용 설정하기:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
레이어 만들기
레이어는 EGL Loader 초기화의 설명에 따라 다음 두 함수를 노출해야 합니다.
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
수동적 레이어
함수 몇 개만 가로채는 레이어의 경우 수동적 초기화 레이어가 최적입니다. 수동적 초기화 레이어는 GLES LayerLoader
가 필요한 함수를 초기화할 때까지 기다립니다.
다음 코드 샘플은 수동적 레이어를 만드는 방법을 보여줍니다.
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); } }
능동적 레이어
완전히 초기화해야 하는 더 형식화된 레이어나 EGL Loader에게 알려지지 않은 확장자를 찾아야 하는 레이어의 경우 능동적 레이어 초기화가 필요합니다. 이러한 레이어는 AndroidGLESLayer_Initialize
가 제공하는 get_next_layer_proc_address
를 사용하여 함수를 찾습니다. 플랫폼이 호출을 라우팅할 위치를 알 수 있도록 레이어는 여전히 로더의 AndroidGLESLayer_GetProcAddress
요청에 응답해야 합니다. 다음 코드 샘플은 능동적 레이어를 만드는 방법을 보여줍니다.
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); } }