android graphic(14)—EGL和OpenGL ES之间的关系

Posted lyf5231

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android graphic(14)—EGL和OpenGL ES之间的关系相关的知识,希望对你有一定的参考价值。


OpenGL ES

什么是OpenGL?

Open Graphics Library (OpenGL) is a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering.

OpenGL是和编程语言、平台无关的一套interface ,主要是为了rendering 2D 和 3D图形等。一般这套接口是用来和GPU进行交互的,使用GPU进行rendering 硬件加速。 说白了OpenGL就是一组函数名,类似java中的interface,并不能直接用。

什么是OpenGL ES?

Android includes support for high performance 2D and 3D graphics with the Open Graphics Library (OpenGL®), specifically, the OpenGL ES API. OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware. OpenGL ES is a flavor of the OpenGL specification intended for embedded devices.

OpenGL ES就是专为嵌入式设备设计的,当然手机也是嵌入式设备,那么OpenGL ES和OpenGL中的函数接口肯定有一些是不一样的,因为嵌入式设备和pc等的硬件处理能力还是有差距的,不然手机卡死了。

既然OpenGL ES只是一组函数接口,那么如何使用呢?我们肯定首先要去实现这些函数接口,而android提供了两种类型的实现:软件实现,硬件实现。

a, 硬件实现,前面提到这组函数接口主要是为了和GPU这个硬件进行打交道的。所以各个硬件厂商会提供相关的实现,例如高通平台的adreno解决方案;

b,软件实现,android也提供了一套OpenGL ES的软件实现,就是说不用GPU了,完全用软件实现画图的相关功能,也就是libagl,代码在frameworks\\native\\opengl\\libagl,其makefile中,

//软件实现最终编译完保存在system\\lib\\egl\\libGLES_android.so
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/egl
LOCAL_MODULE:= libGLES_android

到此,已经有了OpenGL ES的具体实现了,但是由于其实现的平台无关性,所以在android上还不能使用,必须借助EGL。

EGL

EGL - Native Platform Interface
EGL? is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system.
It handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2D and 3D rendering using other Khronos APIs.

那么什么是EGL?EGL是OpenGL ES和底层的native window system之间的接口,承上启下。

EGL is a complement to OpenGL ES. EGL is used for getting surfaces to render to using functions like eglCreateWindowSurface, and you can then draw to that surface with OpenGL ES. Its role is similar to GLX/WGL/CGL.

Whether or not EGL can give you a context that supports OpenGL ES 2.0 may vary by platform, but if the Android device supports ES 2.0 and EGL, you should be able to get such a context from EGL. Take a look at the EGL_RENDERABLE_TYPE attribute and the EGL_OPENGL_ES2_BIT when requesting an EGLConfig.

http://www.khronos.org/files/egl-1-4-quick-reference-card.pdf

在Android上,EGL完善了OpenGL ES。利用类似eglCreateWindowSurface的EGL函数可以创建surface 用来render ,有了这个surface你就能往这个surface中利用OpenGL ES函数去画图了。

例子

下面看个实际例子,一般EGL和OpenGL ES使用时都会先利用egl函数(egl开头)创建opengl本地环境,然后再利用opengl函数(gl开头)去画图。

下面是开机动画BootAnimation中的实现,首先是创建本地环境,

status_t BootAnimation::readyToRun() 


    // 创建SurfaceControl
    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::openGlobalTransaction();
    // 设置layerstack
    control->setLayer(0x40000000);
    SurfaceComposerClient::closeGlobalTransaction();

    //获取Surface
    sp<Surface> s = control->getSurface();

    // initialize opengl and egl
    const EGLint attribs[] = 
            EGL_RED_SIZE,   8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE,  8,
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    ;
    EGLint w, h, dummy;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    //调用eglGetDisplay
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    //调用eglCreateWindowSurface将Surface s转换为本地窗口,
    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    context = eglCreateContext(display, config, NULL, NULL);
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    //eglMakeCurrent后生成的surface就可以利用opengl画图了
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;

    return NO_ERROR;

egl创建好环境后,调用gl相关命令去画图,注意eglSwapBuffers(mDisplay, mSurface) 函数是非常重要的一个函数,会去触发queueBuffer和dequeueBuffer,图画就一副一副的画出来了。

bool BootAnimation::android()

    initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
    initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

    // clear screen
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    //调用eglSwapBuffers会去触发queuebuffer,dequeuebuffer,
    //queuebuffer将画好的buffer交给surfaceflinger处理,
    //dequeuebuffer新创建一个buffer用来画图
    eglSwapBuffers(mDisplay, mSurface);

    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const GLint xc = (mWidth  - mAndroid[0].w) / 2;
    const GLint yc = (mHeight - mAndroid[0].h) / 2;
    const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);

    glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
            updateRect.height());

    // Blend state
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const nsecs_t startTime = systemTime();
    do 
        nsecs_t now = systemTime();
        double time = now - startTime;
        float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
        GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
        GLint x = xc - offset;

        glDisable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);

        glEnable(GL_SCISSOR_TEST);
        glDisable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
        glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
        glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);

        glEnable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
        glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);

        EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
        if (res == EGL_FALSE)
            break;

        // 12fps: don't animate too fast to preserve CPU
        const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
        if (sleepTime > 0)
            usleep(sleepTime);

        checkExit();
     while (!exitPending());

    glDeleteTextures(1, &mAndroid[0].name);
    glDeleteTextures(1, &mAndroid[1].name);
    return false;

EGL加载OpenGL ES库

从上面的例子中,我们发现通过egl*函数调用后,直接就能调用gl*函数去画图了,为何?难道在egl*函数调用过程中,已经将opengl相关实现的库加载了吗?

涉及的库

首先,由于涉及的库较多,先列出来(高通平台,原生的只有前4个),

//算是android中的egl库,用来加载具体的实现
system\\lib\\libEGL.so
//opengl具体实现的wrapper
system\\lib\\libGLESv1_CM.so
system\\lib\\libGLESv2.so
//opengl软件实现,即agl
system\\lib\\egl\\libGLES_android.so
//egl的实现
system\\vendor\\lib\\egl\\libEGL_adreno.so
//opengl硬件实现
system\\vendor\\lib\\egl\\libGLESv1_CM_adreno.so
system\\vendor\\lib\\egl\\libGLESv2_adreno.so

库的加载

继续以bootanimation为例,在bootanimation的makefile中,使用了libEGL 库,

 // frameworks\\base\\cmds\\bootanimation\\Android.mk
 LOCAL_SHARED_LIBRARIES := \\
    libcutils \\
    liblog \\
    libandroidfw \\
    libutils \\
    libbinder \\
    libui \\
    libskia \\
    libEGL \\
    libGLESv1_CM \\
    libgui
 LOCAL_MODULE:= bootanimation

frameworks\\native\\opengl\\libs\\Android.mk中,定义了libEGL.so,makefile中并未指定特殊的存放path,所以最终生成的库保存在system\\lib\\libEGL.so这个库是用来加载具体的egl和opengl实现的,起到桥梁作用,需要和EGL实现的库区分开。

//frameworks\\native\\opengl\\libs\\Android.mk
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=          \\
    EGL/egl_tls.cpp        \\
    EGL/egl_cache.cpp      \\
    EGL/egl_display.cpp    \\
    EGL/egl_object.cpp     \\
    EGL/egl.cpp            \\
    EGL/eglApi.cpp         \\
    EGL/trace.cpp              \\
    EGL/getProcAddress.cpp.arm \\
    EGL/Loader.cpp         \\
#

LOCAL_MODULE:= libEGL

include $(BUILD_SHARED_LIBRARY)

接下来看eglGetDisplay(),这个函数就是在system\\lib\\libEGL.so中实现的。

EGLDisplay eglGetDisplay(EGLNativeDisplayType display)

    clearError();

    uint32_t index = uint32_t(display);
    if (index >= NUM_DISPLAYS) 
        return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
    
    //从函数名上看是加载相关实现
    if (egl_init_drivers() == EGL_FALSE) 
        return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
    

    EGLDisplay dpy = egl_display_t::getFromNativeDisplay(display);
    return dpy;


EGLBoolean egl_init_drivers() 
    EGLBoolean res;
    pthread_mutex_lock(&sInitDriverMutex);
    res = egl_init_drivers_locked();
    pthread_mutex_unlock(&sInitDriverMutex);
    return res;


static EGLBoolean egl_init_drivers_locked() 
    if (sEarlyInitState) 
        // initialized by static ctor. should be set here.
        return EGL_FALSE;
    

    // get our driver loader
    Loader& loader(Loader::getInstance());

    // dynamically load our EGL implementation
    egl_connection_t* cnx = &gEGLImpl;
    if (cnx->dso == 0) 
    //gHooks保存的是gl开头的函数实现
        cnx->hooks[egl_connection_t::GLESv1_INDEX] =
                &gHooks[egl_connection_t::GLESv1_INDEX];
        cnx->hooks[egl_connection_t::GLESv2_INDEX] =
                &gHooks[egl_connection_t::GLESv2_INDEX];
        cnx->dso = loader.open(cnx);
    

    return cnx->dso ? EGL_TRUE : EGL_FALSE;
egl_connection_t gEGLImpl;
gl_hooks_t gHooks[2];

struct egl_connection_t 
    enum 
        GLESv1_INDEX = 0,
        GLESv2_INDEX = 1
    ;

    inline egl_connection_t() : dso(0)  
    void *              dso;
    gl_hooks_t *        hooks[2];
    EGLint              major;
    EGLint              minor;
    //保存egl 实现
    egl_t               egl;

    void*               libGles1;
    void*               libGles2;
;
// 还能这么用,以前真是没用过
// 将EGL/egl_entries.in文件include进来,都是些egl entry
struct egl_t 
    #include "EGL/egl_entries.in"
;

//将entries.in文件include进来
struct gl_hooks_t 
    struct gl_t 
        #include "entries.in"
     gl;
    struct gl_ext_t 
        __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
     ext;
;

EGL/egl_entries.in文件中都是如下的一条一条entry,以egl开头,都是函数声明,

EGL_ENTRY(EGLDisplay, eglGetDisplay, NativeDisplayType)

entries.in文件中,都是以gl开头的entry,都是函数声明,

GL_ENTRY(void, glActiveShaderProgramEXT, GLuint pipeline, GLuint program)

从下面的宏可以看出,转换完成后,上面的entry都是返回值,函数名,函数参数的型式。

#undef GL_ENTRY
#undef EGL_ENTRY
#define GL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);
#define EGL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);

所以,

struct egl_t 
    EGLDisplay eglGetDisplay(NativeDisplayType );
    .......
;

    struct gl_t 
     void    glActiveShaderProgramEXT(GLuint pipeline, GLuint program);
     gl;

下面看cnx->dso = loader.open(cnx),

void* Loader::open(egl_connection_t* cnx)

    void* dso;
    driver_t* hnd = 0;

    //首先,kind为GLES,mask为EGL,GLESv1_CM ,GLESv2
    //在/vendor/lib/egl和/system/lib/egl下寻找libGLES.so或者libGLES_*.so
    //这里只能找到system\\lib\\egl\\libGLES_android.so,opengl的软件实现
    //但是代码中直接continue了,不使用软件实现
    //所以dso返回空
    dso = load_driver("GLES", cnx, EGL | GLESv1_CM | GLESv2);
    if (dso) 
        hnd = new driver_t(dso);
     else 
        // Always load EGL first
        // kind为EGL,mask为EGL
        // 可以找到system\\vendor\\lib\\egl\\libEGL_adreno.so
        // 然后填充egl相关函数
        dso = load_driver("EGL", cnx, EGL);
        if (dso) 
            hnd = new driver_t(dso);
            //找到system\\vendor\\lib\\egl\\libGLESv1_CM_adreno.so库,
            //填充gl相关函数
            hnd->set( load_driver("GLESv1_CM", cnx, GLESv1_CM), GLESv1_CM );
             //找到system\\vendor\\lib\\egl\\libGLESv2_adreno.so库,
            //填充gl相关函数
            hnd->set( load_driver("GLESv2",    cnx, GLESv2),    GLESv2 );
        
    

    LOG_ALWAYS_FATAL_IF(!hnd, "couldn't find an OpenGL ES implementation");
    //上面是opengle的实现,下面的cnx->libGles2和libGles1是gles实现的wrapper
    // 只是打开/system/lib/libGLESv2.so和/system/lib/libGLESv1_CM.so这两个库,将地址返回给cnx
    cnx->libGles2 = load_wrapper("/system/lib/libGLESv2.so");
    cnx->libGles1 = load_wrapper("/system/lib/libGLESv1_CM.so");
    LOG_ALWAYS_FATAL_IF(!cnx->libGles2 || !cnx->libGles1,
            "couldn't load system OpenGL ES wrapper libraries");

    return (void*)hnd;
void *Loader::load_driver(const char* kind,
        egl_connection_t* cnx, uint32_t mask)

    class MatchFile 
    public:
        static String8 find(const char* kind) 
            String8 result;
            String8 pattern;
            pattern.appendFormat("lib%s", kind);
            //在下面路径中寻找
            const char* const searchPaths[] = 
                    "/vendor/lib/egl",
                    "/system/lib/egl"
            ;

            // first, we search for the exact name of the GLES userspace
            // driver in both locations.
            // i.e.:
            //      libGLES.so, or:
            //      libEGL.so, libGLESv1_CM.so, libGLESv2.so

            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) 
                if (find(result, pattern, searchPaths[i], true)) 
                    return result;
                
            

            // for compatibility with the old "egl.cfg" naming convention
            // we look for files that match:
            //      libGLES_*.so, or:
            //      libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so

            pattern.append("_");
            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) 
                if (find(result, pattern, searchPaths[i], false)) 
                    return result;
                
            

            // we didn't find the driver. gah.
            result.clear();
            return result;
        

    private:
        static bool find(String8& result,
                const String8& pattern, const char* const search, bool exact) 

            // in the emulator case, we just return the hardcoded name
            // of the software renderer.
            if (checkGlesEmulationStatus() == 0) 
                ALOGD("Emulator without GPU support detected. "
                      "Fallback to software renderer.");
                result.setTo("/system/lib/egl/libGLES_android.so");
                return true;
            

            if (exact) 
                String8 absolutePath;
                absolutePath.appendFormat("%s/%s.so", search, pattern.string());
                if (!access(absolutePath.string(), R_OK)) 
                    result = absolutePath;
                    return true;
                
                return false;
            

            DIR* d = opendir(search);
            if (d != NULL) 
                struct dirent cur;
                struct dirent* e;
                while (readdir_r(d, &cur, &e) == 0 && e) 
                    if (e->d_type == DT_DIR) 
                        continue;
                    
                    //system\\lib\\egl\\libGLES_android.so直接跳过软件实现
                    //太慢,但是在某个项目中没有gpu,也没有overlay,
                    //只能用agl去合成layer。
                    if (!strcmp(e->d_name, "libGLES_android.so")) 
                        // always skip the software renderer
                        continue;
                    
                    if (strstr(e->d_name, pattern.string()) == e->d_name) 
                        if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) 
                            result.clear();
                            result.appendFormat("%s/%s", search, e->d_name);
                            closedir(d);
                            return true;
                        
                    
                
                closedir(d);
            
            return false;
        
    ;


    String8 absolutePath = MatchFile::find(kind);
    if (absolutePath.isEmpty()) 
        // this happens often, we don't want to log an error
        return 0;
    
    const char* const driver_absolute_path = absolutePath.string();
    //打开找到的库
    void* dso = dlopen(driver_absolute_path, RTLD_NOW | RTLD_LOCAL);
    if (dso == 0) 
        const char* err = dlerror();
        ALOGE("load_driver(%s): %s", driver_absolute_path, err?err:"unknown");
        return 0;
    

    ALOGD("loaded %s", driver_absolute_path);

    //mask为EGL
    if (mask & EGL) 
     //首先在库中找到eglGetProcAddress这个函数
        getProcAddress = (getProcAddressType)dlsym(dso, "eglGetProcAddress");

        ALOGE_IF(!getProcAddress,
                "can't find eglGetProcAddress() in %s", driver_absolute_path);

        egl_t* egl = &cnx->egl;
        __eglMustCastToProperFunctionPointerType* curr =
            (__eglMustCastToProperFunctionPointerType*)egl;
        //egl_names就是egl的entry
        char const * const * api = egl_names;
        while (*api) 
            char const * name = *api;
            //寻找entry中的函数
            __eglMustCastToProperFunctionPointerType f =
                (__eglMustCastToProperFunctionPointerType)dlsym(dso, name);
            //如果未找到,用eglGetProcAddress再去找一下
            if (f == NULL) 
                // couldn't find the entry-point, use eglGetProcAddress()
                f = getProcAddress(name);
                //依然未找到,则置为0
                if (f == NULL) 
                    f = (__eglMustCastToProperFunctionPointerType)0;
                
            
            *curr++ = f;
            api++;
        
    

    if (mask & GLESv1_CM) 
        init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv1_INDEX]->gl,
            getProcAddress);
    

    if (mask & GLESv2) 
      init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv2_INDEX]->gl,
            getProcAddress);
    

    return dso;

小结

a, 需要使用opengl函数的进程,首先加载system\\lib\\libEGL.so共享库,调用库中的eglGetDisplay()函数来加载具体opengl的实现;

b, 如果系统中只有opengl的软件实现,也就是只找到system\\lib\\egl\\libGLES_android.so,那么egl和opengl的实现都在libGLES_android.so库中。虽然4.4已经不使用这个库了(代码中直接忽略),但是我接触过一个平台,硬件需求低,无gpu,无overlay,只能通过agl去合成layer,所以还是有使用价值的;

c, 如果系统中有GPU,同时找到了egl的实现库system\\vendor\\lib\\egl\\libEGL_adreno.so,opengl的实现库system\\vendor\\lib\\egl\\libGLESv1_CM_adreno.so,system\\vendor\\lib\\egl\\libGLESv2_adreno.so,这就使用的是opengl的硬件实现。

d, 使用opengl函数时,首先利用egl的函数去构建本地环境,然后再使用opengl绘图。

e, 关于opengl在android中主要有2个用途:上层使用opengl render,即绘图;下层surfaceflinger中使用opengl去做layer的合成,后续将继续分析layer的合成,其中还将牵扯到fence。

以上是关于android graphic(14)—EGL和OpenGL ES之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

Android 模拟器 EGL14 属性 EGL_RECORDABLE_ANDROID 不起作用

EGL_emulation 无法建立与主机的连接 - android

Android Native:CMake链接错误:GL函数的未定义引用 - 即使包含和链接了EGL和GLESv3

Android OpenGL 开发---EGL 的使用

GLSurfaceView 源码分析 & EGL 创建过程

GLSurfaceView 源码分析 & EGL 创建过程