SwapBuffers的等待,虚伪的FPS(转)
FPS在实时渲染中扮演着一个重要的角色,也许你会去笑一个不懂FPS是什么的游戏新手,但也许,这只是五十步笑一百步罢了。你能读懂SwapBuffers的深情等待吗?——ZwqXin.com
frames per second(FPS,
帧率),作为渲染效率的一种衡量,反映的是整个程序在当前的一个渲染状态下平均每秒所能容纳的“渲染循环”执行次数,也表征了平均每个“渲染循环”(帧)
所用的时间。就表面来看,很多人觉得只要在一个每帧运行一次的函数内设置一个计数器,这样计算出一秒内的记数,便可作为该1s瞬间的FPS了;同样也很容
易能求出平均FPS。
我一直都是这么做的。但是我一直以来有个难解的地方:为什么我的程序的FPS基本不会大于75呢?恩,以前做的DEMO,如果是有计算FPS的,都是这
样:场景复杂的时候它可以降低到1或者直接0掉,但是对于简单的场景它永远只有72左右的FPS;再简单,也是72左右;再再简单,还是72左右……可以
说是“封顶”了。今天我(终于?)想要弄清楚是什么原因了,有以下可能:1.所用的程序框架的某处给它限FPS了;2.初始化过程中沾染了些什么会约束渲
染效率的东西;3.渲染循环体内有一个“每个程序都会用到的函数”是“每个程序的瓶颈”,4.FPS计算的算法有问题;5.其他(啥?)。
于是我把整个渲染流程核查一次,先把消息处理和循环部分弄成一般WIN32的形式,貌似无关……然后又检查像素格式,直接把最佳像素格式设置为10,(某
些程序DEMO中设计者会轮巡所有像素格式找出最佳匹配的,于是我也容易知道对我最“友好”的像素格式是10号……)貌似也没啥。另外,检查FPS计算的
式子,没发现异常。我又把渲染函数RenderGLScene内的东西一个个注释……才发现,只有当我注释了SwapBuffers时,屏幕显示死掉了,
但FPS上到了3000以上……对比72的FPS我还是比较相信这个——所以,是双缓冲的关系么。在像素设置中改像素描述器的
PFD_DOUBLEBUFFER为PFD_SWAP_COPY、PFD_SWAP_EXCHANGE之类的,这下FPS也是很高,但是屏幕猛闪~ – –
。google吧,步小心发现了一个东西:vsync。诶?甘熟噶?
在学Irrlicht引擎的时候,建立DEVICE的时候有一个参数选项:vsync,vertical
syncronisation,API解释得不清楚,就知道跟屏幕有关……屏幕?屏幕刷新率?我突然想起我做图像处理的WIN32程序时的一个窘
况:release出来的程序在人家面前演示,发现图片/视频出不来,后来明白是屏幕刷新率的问题——屏幕刷新率太高会把WIN32循环对屏幕窗口所做的
东西“消灭”掉。那么,这里呢?好,我去看文章了:vertical syncronisation。
垂直同步。原来,程序每一帧的函数全部执行完之后,还要等屏幕刷新了才去执行下一帧啊!一查屏幕刷新率——难怪是72左右呀,正因为偶电脑上设置的屏幕刷
新率是72!恩,这里头肯定有个像sleep那样有等待功能的函数做帮凶——噢,亲爱的SwapBuffers,是你呀~
曾经有人说SwapBuffers比glut库的glutSwapBuffers效率低不少(见这里),但这里不是这个问题。无论是
SwapBuffers还是glutSwapBuffers还是wglSwapBuffers,它们位于渲染体的末端,都有这么个“功能”:当显卡的垂直
同步功能vsyn被置为TRUE时,每一帧渲染完后来到SwapBuffers,都要悲情地等待下一次屏幕刷新的时刻到来,再交换前后缓冲并开始执行下一
帧……上面那篇文章用到了WGL_EXT_swap_control扩展,利用wglSwapIntervalEXT来设置“等待的间隔”。间隔是指一个
屏幕刷新的周期,譬如屏幕刷新率是72Hz,那么一个周期就是(1/72)s,
wglSwapIntervalEXT(1)表明要让SwapBuffers类函数开始执行后要等到下一个屏幕刷新时才返回——然后继续下一帧的执行。
wglSwapIntervalEXT(2)就是等下个再下个屏幕刷新了。这么说,wglSwapIntervalEXT(0),哈,就是不用等——关闭
垂直同步Vsync。
这里安插一个问题,那么如果程序完成一帧本来就可能大于这个屏幕刷新间隔(1/72s)呢?譬如设开始时刻屏幕刚刷新而某帧也同时开始,均设为0,那么程
序的函数集将在第一次刷新之后才完结,进入SwapBuffers。如果没理解错,按照该扩展的spec[EXT_swap_control]所说的话,
之后这个SwapBuffers要一直等待到第二次刷新才返回并交换前后缓冲,开始第二帧——即相当于一帧的“理论的完结”需要两个刷新周期(2/72s
=
1/36s),就是说一帧用时稍微比屏幕刷新周期长一点点,也会导致FPS减小一半……而实际上这种不连续性在我以前的“低帧率”程序中没有体现过。究竟
是怎样的呢?留个疑问。
在我的程序中应用此拓展,在程序初始化阶段就关闭Vsync。哈,很高的FPS。文章vertical syncronisation末尾也提供了该作者写的helper类,很容易理解和应用。好了,这样……等待的结束?还没呢——虚伪的FPS。
是的,这个FPS很虚伪。不是说它太高了(因为我本身对这种大数值的没概念),而是它本身就不是FPS,它不符合我们所想的FPS概念(本文开始处)。更确切点,以上所说的“帧”的概念与FPS中的帧的概念不一样。
我们调用函数控制GPU做事,并不是CPU上这种步步执行-返回-下句的形式。我们是按顺序把函数一个一个(作为地址)输送到显卡某个缓存区内,积累一定
量后再让显卡按此顺序执行。所以,代码的执行完成并不代表显卡上相应的硬件功能执行完成。(其实要是显卡看一个执行一个,流水线不就坏掉了么。)也就这个
原因,可以认为两者是不同步的,有延误等等。那么之前讨论中提及的“帧”就是前者的完成时间,而我们最想要的应该是后者的完成时间——包括CPU向GPU
传递函数所用时间和GPU上实际执行函数功能的用时,可以想象两者应该有很大部分的用时是互相重合的,且后者无论如何还是会在函数全部传递完成后仍然需要
额外时间来完成。这部分额外时间取决于之前传入的是哪类函数、GPU具体执行的是什么功能——如果直接关掉垂直同步Vsync得出FPS,这个FPS就没
有包含这些额外的处理时间,仅仅最多能表示两个U之间数据传输的速度。
所以要让FPS反映真实的每帧——包括数据传输和实际执行、绘图——的用时的话,不能用此法。在glut 教學 – 計算
frame rate 的正確方法 ——
程序设计俱乐部一文中,作者ma_hty(白老鼠(Gary))提供了一种方法,在计算帧率时,用glFinish代替SwapBuffers。它没有
SwapBuffers那种等待的耐心,强制告诉程序和GPU:要结束了,快把剩余的所有函数指令执行完,然后换下一帧。这样的一帧正是“帧”的正确含
义。但是这样将失去双缓冲,屏幕是不会正常显示绘制的东西的,所以不可能用做实时的FPS计算,只用于某时刻用一下,指示当前的真实FPS。
转载自ZwqXin http://www.zwqxin.com/
原文地址:http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html