关于ToolRotate——Android屏幕方向旋转的根源


这篇文章是对开源项目http://code.google.com/p/flying-on-android/中ToolRotate.cpp的补充说明。


这个程序源于Android平台中的OpenGL库出现的一个bug,我们的板子屏幕分辨率是800*600,当程序竖屏状态时(600*800),屏幕上的横线会出现锯齿。打开"设置"这一类有列表的程序时,可以很显示地看到列表之间的分隔线中间有一段比两边的向下错了一个像素。

 

Layer.cpp中的mTextures是个长度为2的Texture数组.而两个Texture里面的数据其实是两个GraphicBuffer,分别代表前后台buffer。绘制时,调用OpenGL把前台Texture的渲染到屏幕上。当Texture的宽高分别为600*800时,屏幕就会出现锯齿(这个可以通过Layer.cpp中的onDraw方法中打印需要渲染的Texture的width/height值来确定)。其它尺寸的Layer则尚未发现这个问题。比如,在我的板子上面,状态栏这个Layer的高度为25,输入法为400多,它们都没有出现锯齿。

 

Texture的width/height只是它的两个int32_t属性。而里面的GraphicBuffer数据其实只是一个void*指针,指向一块儿存放像素数据的内存。OpenGL就是根据width/height的指导把GraphicBuffer的数据渲染到屏幕上去的。

 

假如一个APP根据重力方向对屏幕进行旋转的话,流程是这样的:
系统计算传感器的输出数据,当需要旋转时,会调用Surfaceflinger层的API来旋转Surface,具体方法可以看ToolRotate.cpp的源码。由于一个Client端的Surface对应一个Server端的Layer,所以,改变Surface的方法,其实就是变的Layer中mTextures代表的那两个Texture的宽高。Surface宽高改变后,绘制在这个Surface上面的View树得到通知,根据Surface新的宽高重新onMeasure,onLayout,onDraw。

 

补充于2011.7.18

/framework/base/opengl/libagl属于opengles默认的软实现代码,编译出来的库是libGLES_android.so
/framework/base/opengl/libs目录下有三个目录:GLES_CM和GLES2分别是OpenGLES1.x和2.0的接口。EGL包含一个加载器,加载OpenGL时,加载器会根据配置选择使用厂商的实现还是libagl里面的默认实现。下面的代码摘自/frameworks/base/opengl/libs/EGL/Loader.cpp

Loader::Loader()
{
    char line[256];
    char tag[256];
    FILE* cfg = fopen("/system/lib/egl/egl.cfg", "r");
    if (cfg == NULL) {
        // default config
        LOGD("egl.cfg not found, using default config");
        gConfig.add( entry_t(0, 0, "android") );
    } else {
        while (fgets(line, 256, cfg)) {
            int dpy;
            int impl;
            if (sscanf(line, "%u %u %s", &dpy, &impl, tag) == 3) {
                //LOGD(">>> %u %u %s", dpy, impl, tag);
                gConfig.add( entry_t(dpy, impl, tag) );
            }
        }
        fclose(cfg);
    }
}
通过这里我们可以看出来,只要修改配置文件/system/lib/egl/egl.cfg就可以为系统定制OpenGL库了


补充于2011.7.19

/system/lib/egl/egl.cfg文件里面的内容是下面这个样子的:
0,0,android
0,1,xxx
其中,0代表软实现使用的.so库。1代表硬实现使用的.so库。具体可以参考下面代码:

    // get our driver loader
    Loader& loader(Loader::getInstance());
    
    // dynamically load all our EGL implementations for all displays
    // and retrieve the corresponding EGLDisplay
    // if that fails, don't use this driver.
    // TODO: currently we only deal with EGL_DEFAULT_DISPLAY
    egl_connection_t* cnx;
    egl_display_t* d = &gDisplay[0];

    cnx = &gEGLImpl[IMPL_SOFTWARE];
    if (cnx->dso == 0) {
        cnx->hooks[GLESv1_INDEX] = &gHooks[GLESv1_INDEX][IMPL_SOFTWARE];
        cnx->hooks[GLESv2_INDEX] = &gHooks[GLESv2_INDEX][IMPL_SOFTWARE];
        cnx->dso = loader.open(EGL_DEFAULT_DISPLAY, 0, cnx);
        if (cnx->dso) {
            EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
            LOGE_IF(dpy==EGL_NO_DISPLAY, "No EGLDisplay for software EGL!");
            d->disp[IMPL_SOFTWARE].dpy = dpy; 
            if (dpy == EGL_NO_DISPLAY) {
                loader.close(cnx->dso);
                cnx->dso = NULL;
            }
        }
    }

    cnx = &gEGLImpl[IMPL_HARDWARE];
    if (cnx->dso == 0) {
        char value[PROPERTY_VALUE_MAX];
        property_get("debug.egl.hw", value, "1");
        if (atoi(value) != 0) {
            cnx->hooks[GLESv1_INDEX] = &gHooks[GLESv1_INDEX][IMPL_HARDWARE];
            cnx->hooks[GLESv2_INDEX] = &gHooks[GLESv2_INDEX][IMPL_HARDWARE];
            cnx->dso = loader.open(EGL_DEFAULT_DISPLAY, 1, cnx);
            if (cnx->dso) {
                EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
                LOGE_IF(dpy==EGL_NO_DISPLAY, "No EGLDisplay for hardware EGL!");
                d->disp[IMPL_HARDWARE].dpy = dpy; 
                if (dpy == EGL_NO_DISPLAY) {
                    loader.close(cnx->dso);
                    cnx->dso = NULL;
                }
            }
        } else {
            LOGD("3D hardware acceleration is disabled");
        }
    }

    if (!gEGLImpl[IMPL_SOFTWARE].dso && !gEGLImpl[IMPL_HARDWARE].dso) {
        return EGL_FALSE;
    }

    return EGL_TRUE;
软实现的库和硬件实现的库分别放在gEGLImpl[IMPL_SOFTWARE],gEGLImpl[IMPL_HARDWARE]里面。

补充于2011.7.20

再深入看一下加载.so库的loader.open()函数。这是它的核心代码:

        dso = load_driver("GLES", tag, cnx, EGL | GLESv1_CM | GLESv2);
        if (dso) {
            hnd = new driver_t(dso);
        } else {
            // Always load EGL first
            dso = load_driver("EGL", tag, cnx, EGL);
            if (dso) {
                hnd = new driver_t(dso);

                // TODO: make this more automated
                hnd->set( load_driver("GLESv1_CM", tag, cnx, GLESv1_CM), GLESv1_CM );

                hnd->set( load_driver("GLESv2", tag, cnx, GLESv2), GLESv2 );
            }
        }

先尝试加载libGLES_tag.so,加载不成功的话,就依次加载三个库:libEGL_xxx.so,libGLESv1_CM.so,libGLESv2.so。加载库都是使用的load_driver函数。这个函数在/system/lib/egl/目录下寻找要加载的库,然后根据egl_names和gl_names两个数组里的名字列表在库中搜索相应函数,并把搜索到的函数指针存放起来:
egl的函数指针存入cnx->egl;GLESv1的指针存入cnx->hooks[GLESv1_INDEX]->gl;GLESv2的指针存入cnx->hooks[GLESv2_INDEX]->gl。

有一个很有意思的地方,就是egl_names,gl_names,cnx->egl,cnx->hooks[GLESv1_INDEX]->gl,cnx->hooks[GLESv2_INDEX]->gl这几个结构体的定义:

#undef GL_ENTRY
#undef EGL_ENTRY
#define GL_ENTRY(_r, _api, ...) #_api,
#define EGL_ENTRY(_r, _api, ...) #_api,

char const * const gl_names[] = {
    #include "entries.in"
    NULL
};

char const * const egl_names[] = {
    #include "egl_entries.in"
    NULL
};

#undef GL_ENTRY
#undef EGL_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 {
    #include "EGL/egl_entries.in"
};

struct gl_hooks_t {
    struct gl_t {
        #include "entries.in"
    } gl;
    struct gl_ext_t {
        __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
    } ext;
};
#undef GL_ENTRY
#undef EGL_ENTRY

先定义一个宏,再把函数名以宏的格式写入文件。然后在代码中包含文件,最后编译时展开。有很好的灵活性。


libagl目录为OpenGLES的默认软实现。lib/GLES_CM和lib/GLES2两个目录下分别是1.x和2.0的实现,都调用了libagl里面的实现。lib/EGL目录下是做库函数加载的。假设我是ABC公司,我没有使用libagl库,而是做了自己的OpenGLES实现,使用了自己的硬件加速。那么我的库就是libGLESv1_CM_ABC.so,libGLESv2_ABC.so,如果还想部分OpenGLES函数利用系统的实现(软实现)的话,配置文件就这样写:
0,0,android
0,1,ABC
如果完全利用我自己的实现的话,就这样写:
0,1,ABC

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值