从屏幕刷新频率到Unity VSync

显示器有一个属性叫屏幕刷新频率,它是指每秒刷新屏幕的次数,单位为Hz,一般设置为60Hz。
1

什么是刷新屏幕呢?我们屏幕是由像素矩阵组成的,其(CRT)显示图像的原理是靠电子束从左到右、从上到下逐行激发屏幕内表面的荧光粉单元(像素)来实现的。电子束一次水平方向的扫描叫行扫描,一次完整的扫描就是刷新屏幕,形成的图像就是一帧。因此60Hz的刷新率也就是每秒60帧,人眼的视觉暂留需要满足每秒24帧及以上。

显示器扫描的过程中有两个重要的概念:HBlank(行消隐)和VBlank(场消隐)。HBlank是指当行扫描到最右端时需要快速返回到下一行的最左端的过程。而VBlank则是指扫描完一帧,准备开始扫描下一帧,扫描线从右下角返回到左上角的过程。这两个过程的时间间隔中,扫描线需要变得blank,以防止看到一条斜线显示在屏幕上。

游戏渲染中的VSync(垂直同步)技术就与刷新率和VBlank有关。先说说为什么需要VSync。一般情况下CPU执行游戏逻辑和GPU执行渲染的计算都会快于屏幕的刷新,也就是CPU提交渲染数据和命令到GPU、GPU填充渲染结果到显存的速度会快于屏幕刷新,因此当屏幕刷新进行的过程中很可能会出现显存内容被改变的情况,从而造成Tearing(画面撕裂)。
2

解决Tearing问题的方案就是VSync。前面提到屏幕刷新的两帧之间会有一个VBlank,这个间隔硬件就会产生一个VSync的信号,游戏可以在只有收到该信号后再去计算下一帧,这样就能避免Tearing。实际上,即使在不使用VSync且不会发生Tearing的情况下,由于屏幕刷新率的限制,CPU/GPU的帧率再快对于渲染结果来说并没有影响,反而是一种冗余计算的浪费。而关闭VSync的好处是可以更快的响应输入。

Unity在Quality Settings中有个VSync Count属性可以设置VSync,分别是Don’t Sync(关闭VSync)、Every VBlank(每个VBlank计算一帧)、Every Second VBlank(每两个VBlank计算一帧)。https://docs.unity3d.com/Manual/class-QualitySettings.html
3

VSync是需要图形API(DirectX、OpenGL)支持的,Unity对于关闭了VSync或者VSync的设置不被支持的情况采用了在CPU上同步的方案来维持目标帧率:
4

void TimeManager::Sync (float framerate)中传入目标帧率framerate,函数中通过Sleep和循环来控制每帧的耗时,从而控制帧率。

参考资料:
垂直同步
D3DPRESENT_INTERVAL
理解VSync
http://bbs.3dmgame.com/thread-3642221-1-1.html

Unity script bindings to a C++ class

Unity源码中C#到C++的绑定是通过一个描述C#和C++代码的txt文件(如UnityEngineGameObject.txt)来生成的。

这类文件放在Runtime\Export和Editor\Mono目录下,Unity源码会根据它们生成一个.cs和一个.cpp文件。.cs文件提供了C#层的接口,.cpp文件通过mono_add_internal_call实现了C#到C++的绑定。

Unity采用Embedding Mono的方式实现C#到C++的调用,Embedding Mono参考http://www.mono-project.com/docs/advanced/embedding/

其C#层的对象都继承于UnityEngine.Object,Object中有一个很重要的成员变量m_UnityRuntimeReferenceData:
m_UnityRuntimeReferenceData

ReferenceData结构:

ReferenceData

Unity通过这里的cachedPtr存储C++层与之关联的对象的内存地址。

当C#层通过Mono调用C++方法时传入对象的this引用,C++方法将获得一个MonoObject的对象指针(MonoObject*,Unity typedef MonoObject为ScriptingObject),这个对象指针指向的内存其实就是C#对象的内存,其内存布局如图:
3
4

如上图,Mono中每个C#对象的内存布局都以MonoObject开始,而Unity中每个C#对象的内存布局是紧接在MonoObject后会有一个ReferenceData。因此Unity C++层定义了一个UnityEngineObjectMemoryLayout与C#层ReferenceData相对应:
5

Runtime\Scripting\Scripting.h

MonoObject的定义:
6
https://github.com/mono/mono/blob/master/mono/metadata/object.h
http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-object.html

Unity C++层通过将C#层的对象内存地址+8bytes来取得ReferenceData结构并解析为UnityEngineObjectMemoryLayout,同时将C++层与C#绑定的对象使用cachedPtr进行存取。
7