OpenGL ES在Android上的应用

OpenGL ES(OpenGL for Embedded System)是以手持和嵌入式设备为目标的高级3D图形应用程序编程接口(API)。OpenGL ES是当今智能手机中占据统治地位的图形API,其作用范围已经扩展到桌面,OpenGL ES支持的平台包括IOS、Android、BlackBerry、bada、Linux和Windows,它还是基于浏览器的3D图形Web标准WebGL的基础。当然本篇主要介绍Android平台上对OpenGL ES的一些支持。

一、Android图形系统

本章不详细介绍Android图形系统,有兴趣的可以点击我进行参考。

1、渲染流程

1)、View的绘制

由ViewRootImpl发起performTraversals开始View的绘制流程:

  • 测量View的宽高(Measure)
  • 设置View的宽高位置(Layout)
  • 创建显示列表,并执行绘制(Draw)
  • 绘制通过图形处理引擎来实现,生成多边形和纹理(Record、 Execute),其中引擎包括Canvas和OpenGL ES。Canvas作为一个2D图形库,它调用的API到下层其实封装了skia的实现。OpenGL ES作为一个3D图形库,当应用窗口flag等于WindowManager.LayoutParams.MEMORY_TYPE_GPU , 则表示需要用OpenGL接口来绘制UI。

2)、Surface

每个Window对应一个Surface,任何View都要画在Surface的Canvas上。图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?请看下面这个模型:

OpenGL ES在Android上的应用_第1张图片

Surface对应生产者代理对象,Surface(Native)对应生产者本地对象。那么流程就是:上层app通过Surface获取buffer,供上层绘制,绘制过程通过Canvas来完成,底层实现是skia引擎,绘制完成后数据通过Surface被queue进BufferQueue, 然后监听会通知SurfaceFlinger去消费buffer, 接着SurfaceFlinger就acquire数据拿去合成, 合成完成后会将buffer release回BufferQueue。如此循环,形成一个Buffer被循环使用的过程。另外,这个过程有这么几个状态:

  • Free:可被上层使用
  • Dequeued:出列,正在被上层使用
  • Queued:入列,已完成上层绘制,等待SurfaceFlinger合成
  • Acquired:被获取,SurfaceFlinger正持有该Buffer进行合成

所以Surface主要干两件事:获取Canvas来干绘制的活;申请buffer并把Canvas最终生产的图形、纹理数据放进去。

3)、surfaceflinger服务

surfaceflinger是Android图形系统中一个比较核心独立的Service,它接收所有Surface作为输入,创建layer(其主要的组件是一个 BufferQueue)与Surface一一对应,根据ZOrder, 透明度,大小,位置等参数,计算出每个layer在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的栅格化数据, 放到layer的framebuffer上。

4)、Layer

Layer是SurfaceFlinger 进行合成的基本操作单元,其主要的组件是一个 BufferQueue。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer。Layer 其实是一个 FrameBuffer,每个 FrameBuffer 中有两个 GraphicBuffer 记作 FrontBuffer 和 BackBuffer。

5)、Hardware Composer

Hardware Composer HAL (HWC) 在 Android 3.0 中被引入。它的主要目标是通过可用硬件确定组合缓冲区的最有效方式。

  • SurfaceFlinger 为 HWC 提供完整的 layers 的列表并询问,“你想要如何处理它?”。

  • HWComposer根据硬件性能决定是使用硬件图层合成器还是GPU合成,分别将每个layer对应标记为 overlay 或 GLES composition 来进行响应。
  • SurfaceFlinger处理需要GPU合成的layers,将结果递交给HWComposer做显示(通过Hwcomposer HAL),需要硬件图层合成器合成的layers由HWComposer自行处理(通过Hwcomposer HAL)。
  • 合成Layer时,优先选用HWComposer,在HWComposer无法解决时,SurfaceFlinger采用默认的3D合成,也即调OpenGL标准接口,将各layer绘制到fb上。

这样设计的好处是可以充分发挥硬件性能,同时降低SurfaceFlinger和硬件平台的耦合度(方便移植),另外SurfaceFlinger能将一些合成工作委托给Hardware Composer,从而降低来自OpenGL和GPUd的负载。总结下来它有两种合成方式:

  • 离线合成:先将所有图层画到一个最终层(FrameBuffer)上,再将FrameBuffer送到LCD显示。由于合成FrameBuffer与送LCD显示一般是异步的(线下生成FrameBuffer,需要时线上的LCD去取),因此叫离线合成。毫无疑问,SurfaceFlinger采用默认的3D合成,也即调OpenGL标准接口,将各layer绘制到fb上属于离线合成。
  • 在线合成:不使用FrameBuffer,在LCD需要显示某一行的像素时,用显示控制器将所有图层与该行相关的数据取出,合成一行像素送过去。只有一个图层时,又叫Overlay技术。由于省去合成FrameBuffer时读图层,写FrameBuffer的步骤,大幅降低了内存传输量,减少了功耗,但这个需要硬件支持。毫无疑问,HWComposer合成属于在线合成。

经过上述分析,我们大概了解了整个显示数据的产生、传送、合成的过程以及相关类在这个过程中所起的作用,最后总结一张图形数据流:

OpenGL ES在Android上的应用_第2张图片

6)、Screen显示

显示屏上的内容,是从硬件帧缓冲区读取的,大致读取过程为:从Buffer的起始地址开始,从上往下,从左往右扫描整个Buffer,将内容映射到显示屏上。

下图显示的是双缓冲:一个FrontBuffer用于提供屏幕显示内容,一个BackBuffer用于后台合成下一帧图形。

OpenGL ES在Android上的应用_第3张图片

假设前一帧显示完毕,后一帧准备好了,屏幕将会开始读取下一帧的内容,也就是开始读取上图中的后缓冲区的内容.此时,前后缓冲区进行一次角色互换,之前的后缓冲区变为前缓冲区,进行图形的显示,之前的前缓冲区则变为后缓冲区,进行图形的合成。

7)、总结

总结下渲染Android应用视图的渲染流程:测量流程用来确定视图的大小、布局流程用来确定视图的位置、绘制流程最终将视图绘制在应用窗口上。Android应用程序窗口UI首先是使用Canvas通过Skia图形库API来绘制在一块画布上,实际地是通过Surface绘制在这块画布里面的一个图形缓冲区中,这个图形缓冲区最终会通过layer的形式交给SurfaceFlinger来合成,而合成后栅格化数据的操作交由HWComposer或OpenGL生成,即将这个图形缓冲区渲染到硬件帧缓冲区中,供屏幕显示。如下图:

OpenGL ES在Android上的应用_第4张图片

2、创建Surface示例

本节主要介绍如何利用Android图形系统来创建一个Surface作为后续OpenGL渲染的载体。本章内容主要仿照安卓原生系统的bootanim的方式来创建两个图层,供后面章节在这两个图层上通过opengl来绘制一些图形控件。bootanimation在源代码路径为frameworks/base/cmds/bootanimation/。

1)、等待SurfaceFlinger服务

从Android图像系统简要介绍知道,我们Surface都需要通过SurfaceFlinger这个核心服务起来了才能进行,所以需要等待它的服务。目前有两种等待方式:

  • 直接获取surfaceflinger服务:(Bootanimation通过该方式)
//frameworks/base/cmds/bootanimation/BootAnimationUtil.cpp
void waitForSurfaceFlinger() {
    // TODO: replace this with better waiting logic in future, b/35253872
    int64_t waitStartTime = elapsedRealtime();
    sp sm = defaultServiceManager();
    const String16 name("SurfaceFlinger");
    const int SERVICE_WAIT_SLEEP_MS = 100;
    const int LOG_PER_RETRIES = 10;
    int retry = 0;
    while (sm->checkService(name) == nullptr) {
        retry++;
        if ((retry % LOG_PER_RETRIES) == 0) {
            ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms", elapsedRealtime() - waitStartTime);
        }
        usleep(SERVICE_WAIT_SLEEP_MS * 1000);
    };
    int64_t totalWaited = elapsedRealtime() - waitStartTime;
    if (totalWaited > SERVICE_WAIT_SLEEP_MS) {
        ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);
    }
}
  • 直接获取属性"init.svc.surfaceflinger"值:(因为surfaceflinger是被init进程启动,因此可以直接通过init.svc.xxx属性来判断该进程状态)
void waitForSurfaceFlinger() {
    char property[PROPERTY_VALUE_MAX] = {0};
    while (!property_get("init.svc.surfaceflinger", property, nullptr)) {
        if (strcmp(property, "runing", strlen("runing"))) {
            break;
        }
        usleep(SERVICE_WAIT_SLEEP_MS * 1000);
    }
}

2)、创建图层Surface

void createSurface() {
    SurfaceComposerClient::Transaction t;
    sp session = new SurfaceComposerClient();
    sp token = SurfaceComposerClient::getInternalDisplayToken();
    // 保存当前显示屏信息
    DisplayInfo dinfo;
    // 获取当前显示屏信息
    SurfaceComposerClient::getDisplayInfo(token, &dinfo);
    // 当前显示屏宽
    int surfaceW = dinfo.w;
    // 当前显示屏高
    int surfaceH = dinfo.h;
    // 当前图层旋转180°
    int orient = 2;
    Rect destRect = Rect(surfaceW, surfaceH);
    Rect viewPort = destRect;
    // 设置后面创建图层的宽高旋转信息 其中参数orient表示旋转枚举
    t.setDisplayProjection(token, orient, viewPort, destRect);
    // 创建第一个native图层
    sp controlPlayer = session->createSurface(string8("SHEN_Bootvideo_Player"), surfaceW, surfaceH, PIXEL_FORMAT_BGRA_8888);
    // 创建第二个native图层
    sp controlUI = session->createSurface(string8("SHEN_Bootvideo_UI"), surfaceW, surfaceH, PIXEL_FORMAT_BGRA_8888);
    // 设置图层上下位置
    t.setLayer(controlPlayer, 0x03FFFFFF).apply();
    t.setLayer(controlUI , 0x04000000).apply();
}

3)、获取IGraphicBufferProdcer

在使用Android原生的mediaplayer一些服务的时候,可能涉及到视频层与图形层之间挖洞操作,这个时候就需要得到新创建图层的IGraphicBufferProducer了。如下代码使用了自动锁详情请点击:

// mediaplayer拿到IGraphicBufferProducer之后可以作为setVideoSurfaceTexture接口的参数
sp surfacPlayer;
sp GetSideBand() {
    Mutex::AutoLock autoL(g_mtxSideBand);
    g_cdtSideBand.wait(g_mtxSideBand);
    return surfacPlayer->getIGraphicBufferProducer();
}
void initSurfacePlayer() {
    Mutex::AutoLock autoL(g_mtxSideBand);
    surfacPlayer = controlPlayer->getSurface();
    g_cdtSideBand.signal();
}

4)、设置缓冲数量

在Bootvideo中使用opengl渲染的时候出现如下错误: E  BufferQueueProducer: [HiBootVideo_UI#0]    attempting to exceed the max dequeued buffer count(2)

OpenGL ES通过eglSwapBuffers通知gpu交换数据帧绘制整个界面的时候缓冲满了,gpu渲染就是通过这个Buffer以消费者的设计方式,在绘制的很慢或者刷新的很快的时候就会出现缓冲数量不够。这个时候可以通过libnativewindow.so的某个接口手动为当前Surface设置缓冲数量,如下代码:

//Android.bp
//头文件引入路径:frameworks/native/libs/nativewindow/include/system/window.h
//动态库引入:libnativewindow.so
#include 
void initSurfaceUI() {
    sp surfacUI = controlUI->getSurface();
    //设置6个缓冲
    native_window_set_buffer_count(surfaceUI.get(), 6);
}

参考:ANativeWindow 和 Surface

你可能感兴趣的:(opengles,surface)