概述
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帧。
最后
以上就是勤奋芹菜为你收集整理的20181220_eglSwapBuffers详解的全部内容,希望文章能够帮你解决20181220_eglSwapBuffers详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复