« 水效果Ⅲ - 抖动波shader复习与深入:Normal Map(法线贴图)Ⅰ »

SwapBuffers的等待,虚伪的FPS

 FPS在实时渲染中扮演着一个重要的角色,也许你会去笑一个不懂FPS是什么的游戏新手,但也许,这只是五十步笑一百步罢了。你能读懂SwapBuffers的深情等待吗?——ZwqXin.com

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html

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。

(更新:NND~ situation1.如果你的硬盘装有Mac OS,而你在Windows下又装了Mac Drive来浏览HFS格式硬盘分区,那么这个MacDrive进程会始终开启垂直同步,也就是wglGetSwapIntervalEXT() 在此时就算设置了也没用的,任何程序的刷新率都会限制回屏幕刷新率;所以,关闭MacDrive进程就可以了。更好是把它从开机启动程序列表中去除,需要的时候开始菜单打开就好了。可能还有其他一些程序也会有类似影响,所以发现wglSwapIntervalEXT成功但无效的时候,考虑从进程列表中做些排除来测试。)

这里安插一个问题,那么如果程序完成一帧本来就可能大于这个屏幕刷新间隔(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

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.