前一篇通过一个简单的案例讲述了如何进行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()将后缓冲区与前缓冲区进行交换,循环往复,就实现了图像的绘制与渲染。
到此,我们分析完成了绘制渲染的所有流程和细节。