20181220_eglSwapBuffers详解

eglSwapBuffers详解

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

2.7.1 BootAnimation中的调用

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

首先看下在BootAnimation中对于这个函数的调用:

        EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);

        if (res == EGL_FALSE)

            break;

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

通过对代码的跟踪,可以知道,在bootanimationreadyToRun()函数中,会来创建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的连接。通过这个通道调用surfaceFlingercreateSurface()来完成layer的创建。这个具体不在这边展开。这里只是把时序图给出,详细请参考9.1小节。

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

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

surface继承了ANativeObjectBase模版,通过ANativeObjectBase模版,可以理解成surface类也继承了AnativeWindowRefBase

那么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_tswapBuffers的实现如下:

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的区域,然后调用subtractoldDirtyRegion中去掉了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的。但是,有一个特别的情况,就是当前dequeuebuffer已经超过了能够申请buffer的最大数,比如说1。这个时候waitForFreeSlotThenRelock()@BufferConsumerProducer就会返回一个错误值:INVALID_OPERATION,。这样的话,eglSwapbuffer就返回一个错误值return setError(EGL_BAD_CURRENT_SURFACE, EGL_FALSE);

所以,这样就有可能出现超过60帧的情况。也就是GPU可以完成超过60帧的绘制,但是最多只能显示60帧。

你可能感兴趣的:(eglswapbuffer)