您的位置:首页 > Web前端

20181220_eglSwapBuffers详解

2016-03-01 16:51 387 查看


eglSwapBuffers详解

问题来自eglSwapBuffers是否有等待,如果调用eglSwapBuffers的话,是不是会导致帧率下降?


2.7.1 BootAnimation中的调用

之所以需要了解这个api的具体实现,因为我们需要了解eglSwapBuffers是否有等待Fence。

首先看下在BootAnimation中对于这个函数的调用:
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);

if (res == EGL_FALSE)

break;
故名思意,通过这个函数来实现buffer的互换,可是这个互换的buffer是怎么来的呢?

通过对代码的跟踪,可以知道,在bootanimation的readyToRun()函数中,会来创建mSurface
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
因为我们没有GPU驱动的代码,所以,我们可以从libagl中看出一点端倪。

查看这部分代码,建议将如下几部分的sourcecode导入到sourceinsight中。

l frameworks/native/opengl/libagl

l frameworks/base/cmds/bootanimation

l frameworks/core/libpixelflinger


2.7.2 eglCreateWindowSurface

在egl.cpp中,eglCreateWindowSurface就是对应函数createWindowSurface的接口封装。
surface = eglCreateWindowSurface(display, config, s.get(), NULL);

EGLSurface eglCreateWindowSurface( EGLDisplay dpy, EGLConfig config,

NativeWindowType window,

const EGLint *attrib_list)

{

return createWindowSurface(dpy, config, window, attrib_list);

}

在介绍createWindowSurface之前,先来看看window,这个window到底是什么。

在bootanimation.cpp中有这样的一个调用逻辑:
// create the native surface

sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),

sp<Surface> s = control->getSurface();

sp<SurfaceControl> SurfaceComposerClient::createSurface(

const String8& name,

uint32_t w,

uint32_t h,

PixelFormat format,

uint32_t flags)

{

sp<SurfaceControl> sur;

if (mStatus == NO_ERROR) {

sp<IBinder> handle;

sp<IGraphicBufferProducer> gbp;

status_t err = mClient->createSurface(name, w, h, format, flags,

&handle, &gbp);

ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err));

if (err == NO_ERROR) {

sur = new SurfaceControl(this, handle, gbp);

}

}

return sur;

}

sp<Surface> SurfaceControl::getSurface() const

{

Mutex::Autolock _l(mLock);

if (mSurfaceData == 0) {

// This surface is always consumed by SurfaceFlinger, so the

// producerControlledByApp value doesn't matter; using false.

mSurfaceData = new Surface(mGraphicBufferProducer, false);

}

return mSurfaceData;

}
首先获取到SurfaceComposerClient的对象,然后通过SurfaceComposerClient->createSurface()函数创建一个SurfaceControl。在SurfaceComposerClient中有一个成员变量mClient,实际上建立了一个到surfaceflinger的连接。通过这个通道调用surfaceFlinger的createSurface()来完成layer的创建。这个具体不在这边展开。这里只是把时序图给出,详细请参考9.1小节。



getSurface返回了一个surface对象。该Surface握有App层面对于底层buffer的控制方式以及状态的管理。

好了,接下来看看surface->get()返回的到底是什么?我们知道surface继承了RefBase,所以get()实际上RefBase提供的函数。返回了surface的对象引用。

而surface继承了ANativeObjectBase模版,通过ANativeObjectBase模版,可以理解成surface类也继承了AnativeWindow和RefBase。

那么surface.get()作为AnativeWindow的类型参数传递给CreateWindowSurface也就好理解咯。

也就是window本质上就是surface。

弄明白了window,我们再来具体分析下createWindowSurface函数。
static EGLSurface createWindowSurface(EGLDisplay dpy, EGLConfig config,

NativeWindowType window, const EGLint* /*attrib_list*/)

{

if (egl_display_t::is_valid(dpy) == EGL_FALSE)

return setError(EGL_BAD_DISPLAY, EGL_NO_SURFACE);

if (window == 0)

return setError(EGL_BAD_MATCH, EGL_NO_SURFACE);

EGLint surfaceType;

if (getConfigAttrib(dpy, config, EGL_SURFACE_TYPE, &surfaceType) == EGL_FALSE)

return EGL_FALSE;

if (!(surfaceType & EGL_WINDOW_BIT))

return setError(EGL_BAD_MATCH, EGL_NO_SURFACE);

if (static_cast<ANativeWindow*>(window)->common.magic !=

ANDROID_NATIVE_WINDOW_MAGIC) {

return setError(EGL_BAD_NATIVE_WINDOW, EGL_NO_SURFACE);

}

EGLint configID;

if (getConfigAttrib(dpy, config, EGL_CONFIG_ID, &configID) == EGL_FALSE)

return EGL_FALSE;

int32_t depthFormat;

int32_t pixelFormat;

if (getConfigFormatInfo(configID, pixelFormat, depthFormat) != NO_ERROR) {

return setError(EGL_BAD_MATCH, EGL_NO_SURFACE);

}

// FIXME: we don't have access to the pixelFormat here just yet.

// (it's possible that the surface is not fully initialized)

// maybe this should be done after the page-flip

//if (EGLint(info.format) != pixelFormat)

// return setError(EGL_BAD_MATCH, EGL_NO_SURFACE);

egl_surface_t* surface;

surface = new egl_window_surface_v2_t(dpy, config, depthFormat,

static_cast<ANativeWindow*>(window));

if (!surface->initCheck()) {

// there was a problem in the ctor, the error

// flag has been set.

delete surface;

surface = 0;

}

return surface;

}
createWindowSurface函数根据传递进来的config & window构建了egl_window_surface_v2_t,并把对象指针返回。egl_window_surface_v2_t继承了egl_surface_t,提供了一套操作buffer的接口。

有了上面铺垫后,接下来来看看eglSwapBuffer的实现。


2.7.3 eglSwapBuffers

先看看bootanimation中对应这个函数的调用:
eglSwapBuffers(mDisplay, mSurface);
eglSwapBuffers的具体实现如下:
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)

{

if (egl_display_t::is_valid(dpy) == EGL_FALSE)

return setError(EGL_BAD_DISPLAY, EGL_FALSE);

egl_surface_t* d = static_cast<egl_surface_t*>(draw);

if (!d->isValid())

return setError(EGL_BAD_SURFACE, EGL_FALSE);

if (d->dpy != dpy)

return setError(EGL_BAD_DISPLAY, EGL_FALSE);

// post the surface

d->swapBuffers();

// if it's bound to a context, update the buffer

if (d->ctx != EGL_NO_CONTEXT) {

d->bindDrawSurface((ogles_context_t*)d->ctx);

// if this surface is also the read surface of the context

// it is bound to, make sure to update the read buffer as well.

// The EGL spec is a little unclear about this.

egl_context_t* c = egl_context_t::context(d->ctx);

if (c->read == draw) {

d->bindReadSurface((ogles_context_t*)d->ctx);

}

}

return EGL_TRUE;

}
eglSwapBuffers调用了EGLSurface draw中的swapBuffers方法。我们从2.7.2中的分析可以知道,draw指向了egl_window_surface_v2_t,所以在egl_window_surface_v2_t中swapBuffers的实现如下:
EGLBoolean egl_window_surface_v2_t::swapBuffers()

{

if (!buffer) {

return setError(EGL_BAD_ACCESS, EGL_FALSE);

}

/*

* Handle eglSetSwapRectangleANDROID()

* We copyback from the front buffer

*/

// 首先通过andSelf()函数,算出在buffer中的dirtyRegion的区域,然后调用subtract将oldDirtyRegion中去掉了dirtyRegion区域,然后见这块区域从previousBuffer拷贝到当前的buffer中。

if (!dirtyRegion.isEmpty()) {

dirtyRegion.andSelf(Rect(buffer->width, buffer->height));

if (previousBuffer) {

// This was const Region copyBack, but that causes an

// internal compile error on simulator builds

/*const*/ Region copyBack(Region::subtract(oldDirtyRegion, dirtyRegion));

if (!copyBack.isEmpty()) {

void* prevBits;

if (lock(previousBuffer,

GRALLOC_USAGE_SW_READ_OFTEN, &prevBits) == NO_ERROR) {

// copy from previousBuffer to buffer

copyBlt(buffer, bits, previousBuffer, prevBits, copyBack);

unlock(previousBuffer);

}

}

}

oldDirtyRegion = dirtyRegion;

}

// 减少引用

if (previousBuffer) {

previousBuffer->common.decRef(&previousBuffer->common);

previousBuffer = 0;

}

unlock(buffer);

// 完成buffer内容的填充,然后将previousBuffer指向buffer,同时queue
buffer

previousBuffer = buffer;

nativeWindow->queueBuffer(nativeWindow, buffer, -1);

buffer = 0;

// dequeue a new buffer

// 然后dequeue一个新的buffer,并等待fence

int fenceFd = -1;

if (nativeWindow->dequeueBuffer(nativeWindow, &buffer, &fenceFd) == NO_ERROR) {

sp<Fence> fence(new Fence(fenceFd));

// 等待fence超时,就把buffer
cancel掉。

if (fence->wait(Fence::TIMEOUT_NEVER)) {

nativeWindow->cancelBuffer(nativeWindow, buffer, fenceFd);

return setError(EGL_BAD_ALLOC, EGL_FALSE);

}

// reallocate the depth-buffer if needed

if ((width != buffer->width) || (height != buffer->height)) {

// TODO: we probably should reset the swap rect here

// if the window size has changed

width = buffer->width;

height = buffer->height;

if (depth.data) {

free(depth.data);

depth.width = width;

depth.height = height;

depth.stride = buffer->stride;

depth.data = (GGLubyte*)malloc(depth.stride*depth.height*2);

if (depth.data == 0) {

setError(EGL_BAD_ALLOC, EGL_FALSE);

return EGL_FALSE;

}

}

}

// keep a reference on the buffer

buffer->common.incRef(&buffer->common);

// finally pin the buffer down

if (lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN |

GRALLOC_USAGE_SW_WRITE_OFTEN, &bits) != NO_ERROR) {

ALOGE("eglSwapBuffers() failed to lock buffer %p (%ux%u)",

buffer, buffer->width, buffer->height);

return setError(EGL_BAD_ACCESS, EGL_FALSE);

// FIXME: we should make sure we're not accessing the buffer anymore

}

} else {

return setError(EGL_BAD_CURRENT_SURFACE, EGL_FALSE);

}

return EGL_TRUE;

}


2.7.4 小节

大概再总结下整个过程:

1)首先计算非dirty区域,然后将非dirty区域数据从上一个buffer拷贝到当前buffer;

2)完成buffer内容的填充,然后将previousBuffer指向buffer,同时queue
buffer。

3)Dequeue一块新的buffer,并等待fence。如果等待超时,就将buffer
cancel掉。

4)按需重新计算buffer

5)Lock buffer,这样就实现page flip,也就是swapbuffer

可以知道,在Dequeue buffer的时候,是有在等待fence的。即便是等待超时,也是需要一个Vsync时间的。

其实在queue buffer的实现函数中,也有等待fence的过程。只有获取到fence之后,调用fb_post进行图形显示。只是这部分是在surfacelinger端。所以不block
ui线程。

回到文章开头的问题,调用eglSwapBuffers是需要等待fence的。但是,有一个特别的情况,就是当前dequeue的buffer已经超过了能够申请buffer的最大数,比如说1。这个时候waitForFreeSlotThenRelock()@BufferConsumerProducer就会返回一个错误值:INVALID_OPERATION,。这样的话,eglSwapbuffer就返回一个错误值return
setError(EGL_BAD_CURRENT_SURFACE, EGL_FALSE);

所以,这样就有可能出现超过60帧的情况。也就是GPU可以完成超过60帧的绘制,但是最多只能显示60帧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: