android 图形系统加速学习系列 (二)

时间:2023-02-06 23:38:31

上一节介绍下android 2D&3D库加载的过程,节绍下软件实现的libagl库并重点介绍一下copybit 2D图形加速部分。


如果处理器只有2D硬件加速而没有3D硬件加速,则可以利用opengl中的libagl,实现封装在libagl里的copybit,因为相对3D API来说,这个模块的封装基本是做好的,只要去实现一个copybit HAL即可;
如果处理器2D/3D硬件加速均有,那么可以丢开 copybit,去实现openGL ES 2D/3D API 的加速功能。


【2D&3D配置】
上节已说过根据配置文件/system/lib/egl/egl.cfg决定加载软件、硬件加速库,加载相应的值赋值到如下数据结构中:
#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 {
        void (*extensions[MAX_NUMBER_OF_GL_EXTENSIONS])(void);
    } ext;
};
struct egl_connection_t
{
    void *              dso;
    gl_hooks_t *        hooks[2];
    EGLint              major;
    EGLint              minor;
    egl_t               egl;
};


那么对于上层调用者来说,如何使用OpenGLES硬件加速呢?在这里有个config配置的问题:
利用libs/EGL/egl.cpp::eglChooseConfig函数中的参数选择config
enum {
    IMPL_HARDWARE = 0,
    IMPL_SOFTWARE,
    IMPL_NUM_IMPLEMENTATIONS
};
egl_connection_t gEGLImpl[IMPL_NUM_IMPLEMENTATIONS];
即调用顺序如下:
gEGLImpl[IMPL_HARDWARE].egl.eglChooseConfig(...)
gEGLImpl[IMPL_SOFTWARE].egl.eglChooseConfig(...)


[2D硬件加速]
  frameworks/base/opengl/libagl/egl.cpp 文件中利用hardware/libhardware/hardware.c
  文件中定义的hw_get_module()函数,该函数判断获得的系统属性是否在variant_keys[]数组中定义
  通过load()函数加载相应的硬件模块;否则加载default硬件模块。


  libGLES_android.so为编译frameworks/base/opengl/libagl/目录而生成的,其专门有一个copybit.cpp文件对copybit模块进
  一步封装。libagl中通过在frameworks/base/opengl/libagl/Android.mk文件中定义:
  LIBAGL_USE_GRALLOC_COPYBITS := 1   默认是打开的
  来加载copybit模块;如果未定义LIBAGL_USE_GRALLOC_COPYBITS,则通过软件的方式而
  不使用copybit 模块来达到 2D 硬件加速。
  对于copybit函数调用及相关的流程介绍如下:
  1、使用libagl库中封装好的copybit函数使用方式
  libagl\state.cpp 中加载copybit hal动态库
  eglCreateContext [EGL初始化函数,创建上下文context] (frameworks\base\opengl\libagl\Egl.cpp)
ogles_context_t *ogles_init(size_t extra)
  ==>
    ...
#ifdef LIBAGL_USE_GRALLOC_COPYBITS
 hw_module_t const* module;
 if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) {
     struct copybit_device_t* copyBits;
     if (copybit_open(module, ?Bits) == 0) {
         c->copybits.blitEngine = copyBits;
         {
             int minLim = copyBits->get(copyBits,
                     COPYBIT_MINIFICATION_LIMIT);
             if (minLim != -EINVAL && minLim > 0) {
                 c->copybits.minScale = (1 << 16) / minLim;
             }
         }
         {
             int magLim = copyBits->get(copyBits,
                     COPYBIT_MAGNIFICATION_LIMIT);
             if (magLim != -EINVAL && magLim > 0) {
                 c->copybits.maxScale = min(32*1024-1, magLim) << 16;
             }
         }
     }
 }
#endif // LIBAGL_USE_GRALLOC_COPYBITS

void ogles_uninit(ogles_context_t* c) 
==>
#ifdef LIBAGL_USE_GRALLOC_COPYBITS
 if (c->copybits.blitEngine != NULL) {
     copybit_close((struct copybit_device_t*) c->copybits.blitEngine);
 }
#endif // LIBAGL_USE_GRALLOC_COPYBITS
  如此后面使用blitEngine进行调用相关的成员函数即可
c->copybits.blitEngine = copyBits;

  操作copybit:
  libagl\texture.cpp 文件
  void glDrawTexsvOES(const GLshort* coords) 
   drawTexiOES(coords[0], coords[1], coords[2], coords[3], coords[4], c);
drawTexiOESWithCopybit(x, y, z, w, h, c)
drawTexiOESWithCopybit_impl 
return copybit(x, y, w, h, textureObject, textureObject->crop_rect, 0, c);


   libalg\array.cpp 文件
   static const arrays_prims_fct_t drawArraysPrims[] = {
      drawPrimitivesPoints,
      drawPrimitivesLines,
      drawPrimitivesLineLoop,
      drawPrimitivesLineStrip,
      drawPrimitivesTriangles,
      drawPrimitivesTriangleStrip,
      drawPrimitivesTriangleFan
   };
   void glDrawArrays(GLenum mode, GLint first, GLsizei count)
drawArraysPrims[mode](c, first, count);
drawPrimitivesTriangleFan(ogles_context_t* c, GLint first, GLsizei count)
drawTriangleFanWithCopybit
drawTriangleFanWithCopybit_impl
  以上两个函数都调用到了copybit hal模块了,但 drawTexiOESWithCopybit_impl 只是对copybit进行
  简单的封装,而 drawTriangleFanWithCopybit_impl 对copybit函数进一步的封装,较复杂。
  
  这里单独介绍一下:
  libagl/copybit.cpp
static bool copybit(GLint x, GLint y,
        GLint w, GLint h,
        EGLTextureObject* textureObject,
        const GLint* crop_rect,
        int transform,
        ogles_context_t* c)
  {
   ...
   //1、确定Texture Env Mode是否正确
    switch (tev.env) {
    case GGL_REPLACE:
      ...
        break;
    case GGL_MODULATE:
        // only cases allowed is:
        // RGB  source, color={1,1,1,a} -> can be done with GL_REPLACE
        // RGBA source, color={1,1,1,1} -> can be done with GL_REPLACE
        ...
        break;
   default:
        // Incompatible texture environment.
        LOGD_IF(DEBUG_COPYBIT, "incompatible texture environment");
        return false;
    }
  
   //2、将texture转换为copybit格式
    textureToCopyBitImage(&textureObject->surface, opFormat,textureObject->buffer, &src);

//3、支持超出硬件显示支持能力进行缩放
    if (dsdx < maxScaleInv || dsdx > minScaleInv ||
        dtdy < maxScaleInv || dtdy > minScaleInv)
    {
        // The requested scale is out of the range the hardware
        // can support.
        err = copybit->stretch(copybit,
                &tmp_dst, &src, &tmp_rect, &srect, &tmp_it);
}

//4、是否有alpha值进行分别处理
    /* and now the alpha-plane hack. This handles the "Fade" case of a
     * texture with an alpha channel.
     */
    if (alphaPlaneWorkaround) { //如果有alpha值需要倒三次
     // first make a copy of the destination buffer  将数据从目的地址考出至临时地址
     err = copybit->stretch(copybit,&tmpCbImg, &dst, &tmpCbRect, &tmpdrect, &tmp_it);
    
     // then proceed as usual, but without the alpha plane 从源地址复制至目的地址
     err = copybit->stretch(copybit, &dst, &src, &drect, &srect, &it);
    
     // finally copy back the destination on top with 1-alphaplane 从临时地址复制到目的地址,并带有alpha值
     err = copybit->stretch(copybit,&dst, &tmpCbImg, &tmpdrect, &tmpCbRect, &it);    
    }else{ //没有alpha,只需要做一次 从源地址复制至目的地址
err = copybit->stretch(copybit, &dst, &src, &drect, &srect, &it);    
    }
    
    对于alpha通道问题:
    这种情况属于整个图形区域采用相同的alpha值。 需要表现的效果为背景透明效果,前景明显
    可见。由此得出计算公式为“前景x(1-Alpha)+背景x Alpha”,
    需要三个步骤,移出背景,移入前景,带Alpha参数移入背景。
   
2、直接调用copybit hal 的blit进行调用的方式,名为“越狱”的调用方式
libagl/egl.cpp
egl_window_surface_v2_t::egl_window_surface_v2_t
==>
if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &pModule) == 0) {
        copybit_open(pModule, &blitengine);
    }
    
  egl_window_surface_v2_t::~egl_window_surface_v2_t()
  ==>
   if (blitengine) {
        copybit_close(blitengine);
    }
    
  操作copybit:
  void egl_window_surface_v2_t::copyBlt
  ==>
   copybit_device_t* const copybit = blitengine;
    if (copybit)  { //使用硬件2D加功能
     ...
        copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
        copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 255);
        copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_DISABLE);
        region_iterator it(clip);
        err = copybit->blit(copybit, &dimg, &simg, &it);
    }
    
    if (!copybit || err) { //使用软件实现blit功能,即利用memcpy实现
     ...
    
        uint8_t const * const src_bits = (uint8_t const *)src_vaddr;
        uint8_t       * const dst_bits = (uint8_t       *)dst_vaddr;


        while (cur != end) {
            const Rect& r(*cur++);
            ssize_t w = r.right - r.left;
            ssize_t h = r.bottom - r.top;
            if (w <= 0 || h<=0) continue;
            size_t size = w * bpp;
            uint8_t const * s = src_bits + (r.left + src->stride * r.top) * bpp;
            uint8_t       * d = dst_bits + (r.left + dst->stride * r.top) * bpp;
            if (dbpr==sbpr && size==sbpr) {
                size *= h;
                h = 1;
            }
            do {
                memcpy(d, s, size);
                d += dbpr;
                s += sbpr;
            } while (--h > 0);
        }    
    }

3、还有一个地方也会调用,这样子就不需要加载libagl而调用
frameworks\base\libs\surfaceflinger\LayerBuffer.cpp
按照代码中注解,所以请谨慎使用。
enum {
   /* FIXME: this only exists to work-around some issues with
    * the video and camera frameworks. don't implement unless
    * you know what you're doing.
    */
   GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER = 0x080000001,
};
    gralloc_module_t const * module = LayerBuffer::getGrallocModule();
    if (module && module->perform) {
        int err = module->perform(module,
                GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER,
                buffers.heap->heapID(), bufferSize,
                offset, buffers.heap->base(),
                &src.img.handle);


        // we can fail here is the passed buffer is purely software
        mSupportsCopybit = (err == NO_ERROR);
    }
    
    调用点:
    void LayerBuffer::onFirstRef()
    {
     ...
   if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) {
       copybit_open(module, &mBlitEngine);
   }    
    }
    
    void LayerBuffer::BufferSource::onDraw(const Region& clip) const
if (ourBuffer->supportsCopybit()) {
...
          copybit_device_t* copybit = mLayer.mBlitEngine;
          if (copybit && err != NO_ERROR) {
              // create our EGLImageKHR the first time
              err = initTempBuffer();
              if (err == NO_ERROR) {
                  // NOTE: Assume the buffer is allocated with the proper USAGE flags
                  const NativeBuffer& dst(mTempBuffer);
                  region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
                  copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
                  copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
                  copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
                  err = copybit->stretch(copybit, &dst.img, &src.img,
                          &dst.crop, &src.crop, &clip);
                  if (err != NO_ERROR) {
                      clearTempBufferImage();
                  }
              }
          }
}

以上的几个调用频率:
drawTexiOES 99% 主要调用copybit模块的函数,即调用封装的libagl\copybit.cpp中接口函数
  egl_window_surface_v2_t::copyBlt 仅有几次调用
  LayerBuffer::BufferSource::onDraw 没有调用过,不保证以后不调用,到时调试Camera时就知道了


下面再介绍一下硬件copy hal接口的实现:
目前的copybit提供了如下的接口:
//Set a copybit parameter.
int (*set_parameter)(struct copybit_device_t *dev, int name, int value);

//Get a static copybit information.
int (*get)(struct copybit_device_t *dev, int name);

    /**
     * Execute the bit blit copy operation  最重要的一个函数
     *
     * @param dev from open
     * @param dst is the destination image
     * @param src is the source image
     * @param region the clip region
     *
     * @return 0 if successful
     */
    int (*blit)(struct copybit_device_t *dev,
                struct copybit_image_t const *dst,
                struct copybit_image_t const *src,
                struct copybit_region_t const *region);



//Execute the stretch bit blit copy operation,可由blit函数进行实现
    int (*stretch)(struct copybit_device_t *dev,
                   struct copybit_image_t const *dst,
                   struct copybit_image_t const *src,
                   struct copybit_rect_t const *dst_rect,
                   struct copybit_rect_t const *src_rect,
                   struct copybit_region_t const *region);

具体实现应该没有什么大问题,注意几个小点即可以了:
1、并不是所有硬件都支持这么多格式,而且android上层使用大端序,即RGBA8888对应于ARM的ABGR8888
       所以对于framebuffer.cpp(gralloc模块)及3D OpenGl库中颜色格式设定需要注意,否则会反掉。
       
    2、利用COPYBIT_PLANE_ALPHA(plane alpha)设定全局alpha值,而本身颜色中的alpha利用blit进行合成
       对于常说的SRC_OVER在上层libagl\copybit.cpp中进行了实现,下层只需要实现SRC_COPY情况即可。
    
    3、原始MSM做法针对copybit调用进行了优化:
    static int stretch_copybit(
        struct copybit_device_t *dev,
        struct copybit_image_t const *dst,
        struct copybit_image_t const *src,
        struct copybit_rect_t const *dst_rect,
        struct copybit_rect_t const *src_rect,
        struct copybit_region_t const *region) 
{
...
        const uint32_t maxCount = sizeof(list.req)/sizeof(list.req[0]);
        const struct copybit_rect_t bounds = { 0, 0, dst->w, dst->h };
        struct copybit_rect_t clip;
        list.count = 0;
        status = 0;
        
        // 通过一个while循环,积攒12个region,一次调用硬件驱动ioctl函数,将数据传入驱动,进行stretch操作
        while ((status == 0) && region->next(region, &clip)) {
            intersect(&clip, &bounds, &clip);
            mdp_blit_req* req = &list.req[list.count];
            set_infos(ctx, req);
            set_image(&req->dst, dst);
            set_image(&req->src, src);
            set_rects(ctx, req, dst_rect, src_rect, &clip);


            if (req->src_rect.w<=0 || req->src_rect.h<=0)
                continue;


            if (req->dst_rect.w<=0 || req->dst_rect.h<=0)
                continue;


            if (++list.count == maxCount) {
                status = msm_copybit(ctx, &list);
                list.count = 0;
            }
        }
        
        //没有next区域则直接调用硬件驱动ioctl函数进行输出
        if ((status == 0) && list.count) {
            status = msm_copybit(ctx, &list);
        }
}
    
    /** copy the bits */
static int msm_copybit(struct copybit_context_t *dev, void const *list) 
{
   int err = ioctl(dev->mFD, MSMFB_BLIT,
                   (struct mdp_blit_req_list const*)list);


}
利用ioctl进行用户层拷贝数据到内核层,这是会对效率有所影响。


【硬件2D&3D同时存在处理】

1、修改 surfaceflinger 中的 Android.mk,这个mk中用libGLES_android替换掉libEGL即可

2、在 frameworks/base/opengl/libagl/Android.mk 中定义:
    LIBAGL_USE_GRALLOC_COPYBITS := 1
    来加载copybit模块;如果未定义LIBAGL_USE_GRALLOC_COPYBITS,则通过软件的方式而不使用
    copybit 模块来达到2D硬件加速