OpenGL ES 在Android平台的应用实践(3)

       前一篇通过一个简单的案例讲述了如何进行OpenGL ES编程实现绘制一个可旋转的三角形。对于初学者,学会如何使用OpenGL ES的API就可以实现图形的绘制了,但是深入的剖析才能让我们对原理理解的更加深刻,知其然并知其所以然才是最终的取胜之道。下面我们从源码的层面来看看OpenGL ES是通过什么样的方式,最终绘制到手机的屏幕上的。

      在EGL源码分析前,先列举出源码中会涉及到的一些常量定义和结构体:

//在C++层面,EGLConfig,EGLContext,EGLDisplay,EGLSurface都是void的指针类型
typedef void *EGLConfig;
typedef void *EGLContext;
typedef void *EGLDisplay;
typedef void *EGLSurface;
//本地的绘图窗口操作类,可以从绘图缓冲队列中获取空闲缓冲区,也可以向队列中加入绘制好的缓冲区
struct ANativeWindow{    
  struct android_native_base_t common;    
  const float xdpi;    
  const float ydpi;    
  int (*dequeueBuffer)(struct ANativeWindow* window,                
  struct ANativeWindowBuffer** buffer, int* fenceFd);    
  int (*queueBuffer)(struct ANativeWindow* window,                
  struct ANativeWindowBuffer* buffer, int fenceFd);
};

#elif defined(__ANDROID__) || defined(ANDROID)
  typedef struct ANativeWindow*   EGLNativeWindowType;

typedef void*  EGLNativeDisplayType;
typedef EG  LNativeWindowType  NativeWindowType;
typedef EGLNativeDisplayType NativeDisplayType;

#define EGL_DEFAULT_DISPLAY    ((EGLNativeDisplayType)0)

以上为源码分析中涉及的常量及结构体定义,便于后面的分析先提前列出来,读者在阅读的过程中,在忘记定义的时候可以到这里反复查看。

       第一篇文章说过,EGL是OpenGL ES和本地图形绘制系统之间的桥梁,才使得OpenGL ES不依赖于本地的绘制系统,实现跨平台。OpengGL  ES不依赖图形绘制系统,那么就需要有人来承担这部分的工作,这个责任人就是EGL,那么EGL是如何把OpenGL ES与本地绘制系统联系到一起的呢?现在我们来分析分析。

      之前在介绍EGL的渲染流程时说过,EGL完成了利用本地绘制系统绘制图形的封装,使得开发渲染程序得到了简化,我们就从这个流程开始,挑选重要的步骤进行详细介绍,当然这里已经深入到C++底层进行分析,而不是之前的Java层。好了,话不多说,开始进入正题。

1.获取显示设备。

EGLDisplay eglGetDisplay(NativeDisplayType display){    
  if (display == EGL_DEFAULT_DISPLAY) {        
      EGLDisplay dpy = (EGLDisplay)1;        
      egl_display_t& d = egl_display_t::get_display(dpy);       
         d.type = display;        
         return dpy;    
  }    
  return EGL_NO_DISPLAY;
}
egl_display_t& egl_display_t::get_display(EGLDisplay dpy) {    
    return gDisplays[uintptr_t(dpy)-1U];
}
static egl_display_t gDisplays[NUM_DISPLAYS];
struct egl_display_t{    
    static egl_display_t& get_display(EGLDisplay dpy);    
    NativeDisplayType type;    
    volatile int32_t initialized;
};

        从源码可以看到,这里获取的EGLDisplay其实并不是真正的显示设备,而是一个索引值,通过这个索引值可以找到对应显示设备结构体数组gDisplays中的egl_display_t,这个结构体对应于一个显示设备。

2.创建EGL上下文环境

EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config,EGLContext /*share_list*/, const EGLint* /*attrib_list*/){    
  ogles_context_t* gl = ogles_init(sizeof(egl_context_t));    
  egl_context_t* c = static_cast<egl_context_t*>(gl->rasterizer.base);    
  c->flags = egl_context_t::NEVER_CURRENT;    
  c->dpy = dpy;    
  c->config = config;    
  c->read = 0;    
  c->draw = 0;    
  return (EGLContext)gl;
}

struct egl_context_t {    
  uint32_t      flags;    
  EGLDisplay    dpy;    
  EGLConfig     config;    
  EGLSurface    read;    
  EGLSurface    draw;
};
//与EGLContext对应的结构体,是EGL的上下文环境
struct ogles_context_t {    
  context_t   rasterizer;  
};

struct context_t {          
   void*       base;   
   GGLContext  procs;    
   state_t     state;
 };

       从代码可以看出,与EGLContext对应的底层结构是ogles_context_t,该函数将上文获取到的EGLDisplay,EGLConfig绑定到上下文中,以及初始化内部的读和绘制的EGLSurface绘图。

3.创建EGL绘图表面

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,NativeWindowType window,const EGLint *attrib_list){    
   return createWindowSurface(dpy, config, window, attrib_list);
 }
 
 static EGLSurface createWindowSurface(EGLDisplay dpy, EGLConfig config,NativeWindowType window, const EGLint* /*attrib_list*/){    
   egl_surface_t* surface;    
   surface = new egl_window_surface_v2_t(dpy, config, depthFormat, static_cast<ANativeWindow*>(window));    
   return surface;
 }
 
 struct egl_window_surface_v2_t : public egl_surface_t{    
   virtual     EGLBoolean  swapBuffers();    
   virtual     EGLBoolean  bindDrawSurface(ogles_context_t* gl);    
   ANativeWindow*   nativeWindow;    
   ANativeWindowBuffer*   buffer;    
   ANativeWindowBuffer*   previousBuffer;    
   gralloc_module_t const*    module;    
   void* bits;    
   int width;    
   int height;
 };

    从代码可以看出EGLSurface实际对应的结构体是egl_window_surface_v2_t,该结构体包含了本地窗口ANativeWindow,以及绘制时需要到的前后图像缓冲区ANativeWindowBuffer,还有一个指向图像缓冲区的void* bits,后文会介绍它们之间的关系。

4.设置当前线程为渲染的环境

EGLBoolean eglMakeCurrent(  EGLDisplay dpy, EGLSurface draw,EGLSurface read, EGLContext ctx)
{
     EGLContext current_ctx = EGL_NO_CONTEXT;
     ogles_context_t* gl = (ogles_context_t*)ctx;
     if (makeCurrent(gl) == 0) {
         if (ctx) {
             egl_context_t* c = egl_context_t::context(ctx);
             egl_surface_t* d = (egl_surface_t*)draw;
             egl_surface_t* r = (egl_surface_t*)read;
             c->draw = draw;
             c->read = read;
             if (c->flags & egl_context_t::NEVER_CURRENT) {
                 c->flags &= ~egl_context_t::NEVER_CURRENT;
                 GLint w = 0;
                 GLint h = 0;
                 if (draw) {
                     w = d->getWidth();
                     h = d->getHeight();
                 }
                 ogles_surfaceport(gl, 0, 0);
                 ogles_viewport(gl, 0, 0, w, h);
                 ogles_scissor(gl, 0, 0, w, h);
             }
             if (d) {
                 d->connect();
                 d->bindDrawSurface(gl);
             }
         }
         return EGL_TRUE;
     }
     return setError(EGL_BAD_ACCESS, EGL_FALSE);
}
static int makeCurrent(ogles_context_t* gl)
{
   ogles_context_t* current = (ogles_context_t*)getGlThreadSpecific();
   if (gl) {
       egl_context_t* c = egl_context_t::context(gl);
       if (!(c->flags & egl_context_t::IS_CURRENT)) {
           // The context is not current, make it current!
           setGlThreadSpecific(gl);
           c->flags |= egl_context_t::IS_CURRENT;
       }
   } else {
       setGlThreadSpecific(0);
   }
   return 0;
}
EGLBoolean egl_window_surface_v2_t::connect()
{
   int fenceFd = -1;
   nativeWindow->dequeueBuffer(nativeWindow, &buffer,&fenceFd);
   lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &bits);
   return EGL_TRUE;
}
EGLBoolean egl_window_surface_v2_t::bindDrawSurface(ogles_context_t* gl)
{
   GGLSurface buffer;
   buffer.version = sizeof(GGLSurface);
   buffer.width   = this->buffer->width;
   buffer.height  = this->buffer->height;
   buffer.stride  = this->buffer->stride;
   buffer.data    = (GGLubyte*)bits;
   buffer.format  = this->buffer->format;
   gl->rasterizer.procs.colorBuffer(gl, &buffer);
   return EGL_TRUE;
}
typedefstruct {
   GGLsizei    version;    // always set to sizeof(GGLSurface)
   GGLuint     width;      // width in pixels
   GGLuint     height;     // height in pixels
   GGLint      stride;     // stride in pixels
   GGLubyte*   data;       // pointer to the bits
   GGLubyte    format;     // pixel format
} GGLSurface;
//gl->rasterizer.procs.colorBuffer(gl, &buffer);中的colorBuffer()函数与此对应
static void ggl_colorBuffer(void* con, const GGLSurface* surface)
{
         GGL_CONTEXT(c, con);
         ggl_set_surface(c, &(c->state.buffers.color), surface);
}
structstate_t {
   framebuffer_t   buffers;
};
struct framebuffer_t {
  surface_t   color;
  surface_t  depth;
  surface_t  stencil;
};
structsurface_t {
  union{
    GGLSurface          s;
    struct{
      GGLuint         width;
      GGLuint         height;
      GGLint          stride;
      GGLubyte*       data;
      GGLubyte        format;
      GGLubyte        dirty;
    };
  };
};
void ggl_set_surface(context_t* c, surface_t* dst, const GGLSurface* src)
{
  dst->width = src->width;
  dst->height = src->height;
  dst->stride = src->stride;
  dst->data = src->data;
  dst->format = src->format;
  dst->dirty = 1;
}

      首先通过函数makeCurrent()判断前面创建的上下文环境ogles_context_t是不是当前线程的线程本地,如果不是,将ogles_context_t设置为当前线程的线程本地,我们知道线程本地是线程独有的,所以只有设置了上下文环境的线程才具有了渲染的能力。

     接下来将EGLDisplay 和EGLSurface关联到上下文中,然后判断上下文环境的标志位是否包含egl_context_t::NEVER_CURRENT,如果包含说明这个上下文环境从来没有初始化过,进行初始化视图,裁剪区域等。然后调用egl_window_surface_v2_t::connect() 函数,通过ANativeWindow::dequeueBuffer()从图形缓冲区队列获取一块空闲的缓冲区到buffer,并将buffer和bits绑定,这时,buffer和bits指向同一个图形缓冲区。接下来调用egl_window_surface_v2_t::bindDrawSurface()函数,将图形缓冲区buffer中的值赋值给GGLSurface结构体中相应的值,并将bits和GGLSurface中的data进行绑定,而GGLSurface最终和帧缓冲区framebuffer_t 中颜色缓冲区color进行了绑定,此时surface_t中的data和GGLSurface中的data进行了绑定,所以,最终帧缓冲区中的颜色缓冲区和本地的图像缓冲区进行了绑定,通过OpenGL ES API绘制到帧缓冲区里颜色缓冲区中的图像,也就绘制到了本地图形系统中的图形缓冲区中。

5.交换前后缓冲区,将绘制好的后缓冲区数据绘制到屏幕上。

EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)
{
   egl_surface_t* d = static_cast<egl_surface_t*>(draw);
   d->swapBuffers();
   return EGL_TRUE;
}
EGLBoolean egl_window_surface_v2_t::swapBuffers()
{
   previousBuffer = buffer;
   nativeWindow->queueBuffer(nativeWindow, buffer, -1);
   nativeWindow->dequeueBuffer(nativeWindow, &buffer, &fenceFd)
   return EGL_TRUE;
}
int FramebufferNativeWindow::queueBuffer(ANativeWindow* window,ANativeWindowBuffer* buffer, int fenceFd)
{
   FramebufferNativeWindow* self = getSelf(window);
   framebuffer_device_t* fb = self->fbDev;
   buffer_handle_t handle = static_cast<NativeBuffer*>(buffer)->handle;
   int res = fb->post(fb, handle);
   self->front = static_cast<NativeBuffer*>(buffer);
   return res;
}
int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,ANativeWindowBuffer** buffer, int* fenceFd)
{
   FramebufferNativeWindow* self = getSelf(window);
   int index = self->mBufferHead++;
   *buffer = self->buffers[index].get();
   return 0;
}
typedef struct framebuffer_device_t {
   struct hw_device_t common;
   const uint32_t  width;
   const uint32_t  height;
   const int       stride;
   int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);
} framebuffer_device_t;
static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)
{
   fb_context_t* ctx = (fb_context_t*)dev;
   private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);
   private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);
   if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
       const size_t offset = hnd->base - m->framebuffer->base;
       m->info.activate = FB_ACTIVATE_VBL;
       m->info.yoffset = offset / m->finfo.line_length;
       if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {
           ALOGE("FBIOPUT_VSCREENINFO failed");
           m->base.unlock(&m->base, buffer);
           return -errno;
       }
       m->currentBuffer = buffer;
   } else {
       void* fb_vaddr;
       void* buffer_vaddr;
       m->base.lock(&m->base, m->framebuffer,
               GRALLOC_USAGE_SW_WRITE_RARELY,
               0, 0, m->info.xres, m->info.yres,
          &fb_vaddr);
       m->base.lock(&m->base, buffer,
               GRALLOC_USAGE_SW_READ_RARELY,
               0, 0, m->info.xres, m->info.yres,
          &buffer_vaddr);

       memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);
       m->base.unlock(&m->base, buffer);
       m->base.unlock(&m->base, m->framebuffer);
   }
   return 0;
}

    调用egl_surface_t::swapBuffers()函数,通过ANativeWindow::queueBuffer()函数将前面绘制好的图形缓冲区加入到队列中,并通过ANativeWindow::dequeueBuffer()重新从队列中获取一块新的空闲缓冲区。ANativeWindow::queueBuffer()函数通过framebuffer_device_t 设备驱动程序中的fb_post()函数实现真正的渲染,在fb_post()函数中,通过判断缓冲区结构体private_handle_t的flags是否包含private_handle_t::PRIV_FLAGS_FRAMEBUFFER,如果包含说明该图形缓冲区是在帧缓冲区中进行分配的,则通过ioctl()函数进行渲染,否则说明图形缓冲区是在内存中分配的,需要将图形缓冲区中的数据复制到帧缓冲区后进行渲染。

      当绘制完后缓冲区中的图像时,通过 eglSwapBuffers()将后缓冲区与前缓冲区进行交换,循环往复,就实现了图像的绘制与渲染。

      到此,我们分析完成了绘制渲染的所有流程和细节。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值