20181220_eglSwapBuffers详解
来源:互联网 发布:3d分析软件 编辑:程序博客网 时间:2024/05/19 22:05
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详解
- 详解
- 详解
- 详解
- 详解
- &,&&,|,||详解
- 详解
- Scala详解--------基础知识详解
- Spring详解-----------事务详解
- github 详解详解
- InputFilter详解、TextWatcher详解
- Spring详解-----------事务详解
- 【词汇详解】事务详解
- Session 详解
- Session 详解
- sizeof详解
- 端口详解
- Meta详解~~~
- 常用JS技巧
- iOS多线程的初步研究(七、八、九)-- dispatch对象-- dispatch队列-- dispatch源-- dispatch同步
- JDk的环境变量如何设置
- Oracle12c中多宿主环境(CDB&PDB)的数据库触发器(Database Trigger)
- 复合主键
- 20181220_eglSwapBuffers详解
- MyEclipse 常用快捷键
- 计算器较长表达式整体运算
- 人脸识别之特征脸方法(Eigenface)
- 判断用户设备的方法(只有真机有效)
- Redis服务器搭建/配置/及Jedis客户端的使用方法
- mybatis 批量插入
- 元素类型为 "resultMap" 的内容必须匹配 "(constructor?,id*,result*,association*,collection*,discriminator?)"。
- HDOJ 2802 F(N)