slide1
Download
Skip this Video
Download Presentation
切割管道

Loading in 2 Seconds...

play fullscreen
1 / 43

切割管道 - PowerPoint PPT Presentation


  • 181 Views
  • Uploaded on

切割管道. Niklas Frykholm. 实现亚秒级的迭代时间. Bitsquid 公司. 再次回到这里. 做出改变. 检查结果. 出口. 寻找实体. 汇编数据. 载入关卡. 重启游戏. 迭代运动循环. 分钟(小时)直到一个改变能够在游戏里看见. 为什么需要更快速的迭代时间?. 生产效率 等待构建所失去的时间 质量 更多的调整 在游戏机上游戏内测试的资产 注意:本次演讲是关于优化管线 延迟 而不是 吞吐量 更新单个不正确的资源所要求的时间. 《 汉密尔顿的大冒险 》 ( Hamilton ’ s Great Adventure ).

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' 切割管道' - bien


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1
切割管道

Niklas Frykholm

实现亚秒级的迭代时间

Bitsquid公司

slide2

再次回到这里

做出改变

检查结果

出口

寻找实体

汇编数据

载入关卡

重启游戏

迭代运动循环

分钟(小时)直到一个改变能够在游戏里看见

slide3
为什么需要更快速的迭代时间?
  • 生产效率

等待构建所失去的时间

  • 质量

更多的调整

在游戏机上游戏内测试的资产

  • 注意:本次演讲是关于优化管线延迟而不是吞吐量

更新单个不正确的资源所要求的时间

krater

《玫瑰战争》

(War of the Roses)

《末日余痕》(Krater)

《星际虚空》(Starvoid)

《阿比逃亡记》高清版本

(Abe’s Oddysee HD)

slide6
雄心勃勃的目标

“立刻”看到改变(<1秒钟)

slide7

30赫兹

没有欺骗!

目标硬件 + 目标帧速率

slide8

再次回到这里

检查结果

做出改变

改变模型...

改变素材...

改变纹理...

新增刚体...

编辑脚本...

出口

发现实体

编译数据

载入关卡

重启游戏

实时编辑怎么样?

我们是否甚至需要一个管线?

slide9
实时编辑的问题
  • 游戏并不总是最好的编辑器
  • 如果游戏数据是生动的二进制图像,那么版本管理是很棘手的

协同工作和融合变化也是棘手的

  • 跨平台?在PS3上编辑? X360? iOS?
  • 适合编辑的数据格式不具备最佳的运行时间性能
slide10

快速迭代:

两全其美

快速的游戏
  • 二进制资源
  • 就地载入
  • 无需寻找时间

快速的工作流程

  • 较短的编译时间
  • 热重载
  • 立即的反馈
slide11

再次回到这里

做出改变

检查结果

出口

寻找实体

汇编数据

载入关卡

重启游戏

替换

这些

重载

加速这一项

进攻策略

尽可能快的进行编译并使用重载代替重启

slide12
分而治之
  • 重新编译和重新加载所有数据(>1 GB)永远不可能足够快
  • 我们必须使用更小的模块来工作

把游戏数据当成是一些个别资源的一个集合,这些资源每个都能够单独进行编译,接着在游戏运行时重新加载

slide13

类型:

名称:

源文件:

纹理

纹理/植物/草地

纹理/植物/草地.texture

个别的资源
  • 通过类型 + 名称进行识别
  • 二者都是唯一的字符串标识符(进行散列处理)

名字来源于一个路径,但是我们把它当做一个标识ID (只能通过平等比较)

slide14
编译资源
  • 每一个资源编译到一个特定平台的运行时间通过二进制大对象(blob)进行优化
  • 通过名字散列进行识别

(数据编译)

草地.texture

(游戏内的资源管理器)

ff379215

ff379215

slide15

ff379215

ff379215

edf123b2

2345e791

b3d42677

edf123b2

123

2345e791

b3d42677

123

加载资源
  • 资源被组合成包用来加载
  • 资源包通过一个后台线程进行流式处理。
  • 在开发过程当中,资源都被储存在由散列命名的单独文件当中。
  • 在最终版本中,资源包里的文件被捆绑在一起用于线性加载

boss_level.package

slide16

> 重新加载纹理植物/草地

重新加载资源
  • 运行游戏侦听TCP/IP端口

信息是JSON structs结构数据

  • 来自我们的工具的典型命令

激活性能抬头显示设备HUD

显示调试线

LuaREPL命令 (读取-评价-打印-循环)

重新加载资源

  • 也用于我们所有工具的可视化

> 重新加载纹理植物/草地

slide17

ff379215

ff379215

ff379215

ff379215

ff379215

ff379215

重新加载资源(详情)
  • 载入新的资源
  • 基于类型通知游戏系统

指针指向旧的和新的资源

  • 游戏系统决定做什么

删除实例(声音)

停止和启动实例(粒子)

保持实例,对它进行更新(纹理)

  • 破坏/卸载旧的资源

O

slide18
示例:资源的重新加载

if (type == unit_type) {

for (unsigned j=0; j<app().worlds().size(); ++j) app().worlds()[j].reload_units(old_resource, new_resource);

}

void World::reload_units(UnitResource *old_ur, UnitResource *new_ur){ for (unsigned i=0; i<_units.size(); ++i) { if (_units[i]->resource() == old_ur) _units[i]->reload(new_ur); }}

void Unit::reload(const UnitResource *ur){ Matrix4x4 m = _scene_graph.world(0); destroy_objects(); _resource = ur; create_objects(m);}

slide19
疑难问题
  • 将数据配置到游戏机
  • 处理大型资源
  • 编译很慢的资源
  • 重载代码
slide20

文件伺服器

问题:配置到游戏机
  • 将数据配置到游戏机上可能会很缓慢

文件传输程序并不适用亚秒级的迭代

  • 解决方法:运行电脑上的文件伺服器 –游戏机从那里读取文件

透明的文件系统后端

获得<路径>

<数据>

slide21
问题:大型资源
  • 非常大的资源(>100 MB)永远无法快速地进行编译和加载
  • 找到合适的资源颗粒度

不要把所有的几何体放到单个文件中

把几何实体放到到不同的文件中

让关卡对象参照它所使用的实体

slide22
问题:很慢的资源
  • 冗长的编译使得快速迭代不可能实现

光照贴图,navmeshes文件,等等。

  • 将烘焙(baking)从编译中独立出来

烘焙始终是一个明确的步骤:“现在只做光照贴图”(编辑按钮)

烘焙过的数据被保存在资源数据里并检查到存储库

接着像往常一样编译(从原始的纹理到压缩平台)

slide23
问题:重载代码
  • 要加载的最棘手的资源
  • 四种代码

着色器(Cg着色器、高级着色器语言HLSL)

流程(可视化脚本)

Lua脚本语言

C++语言

  • 流程和着色器当做正常的资源

只是是二进制数据

流程脚本

slide24

原始版本

move

move

set_velocity

set_velocity

Actor = Actor or class()

function Actor:move(dt)

self.pos = self.pos + dt

end

实时重载的Lua语言

确保在重载时,变化能够被应用到现有的执行者(Actor)类别。

如果没有这个,重载将创建一个新的执行者类别并且现有的执行者对象将不会看到代码改变。

self.pos = self.pos + dt

Actor

my_actor

self.pos = self.pos + dt

更新

Actor = Actor or class()

function Actor:move(dt)

self.pos = self.pos + self.v * dt

end

Actor

my_actor

self.pos = self.pos + self.v * dt

slide25
重载代码: C++
  • 工具支持“重启可执行程序( Restart Exe)”

可执行程序文件进行重新加载,但如果你仍然在相同的位置看到相同的对象,那么只要使用新的引擎代码

状态通过工具保持

  • 无法达到<1秒的目标,但还是非常有用的

小型的可执行程序大小会有帮助

slide26

再次回到这里

做出改变

检查结果

出口

重新载入

编译数据

快速的编译
slide27
小技巧:使用分析程序,Luke

你的工具也想要一些表现出沉迷热爱的东西

slide28

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

渐进式编译
  • 找到上一次编译以来所有被修改过的资源数据
  • 确定依赖于这些文件的运行时间数据
  • 对需要的部分进行重新编译
  • 重要的是这个过程是坚如磐石的

信任很难获得却很容易失去

“最安全的做法是进行一次完全编译”

slide29

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

挑战:依赖关系
  • 基本着色器资源(base.shader_source)包括常见着色器资源(common.shader_source)

如果common.shader_source改变就需要重新进行编译

  • 没有读取每个文件我们要如何知道是否改变了?
  • 解决方法:一个编译数据库

从前一次运行储存信息

在启动时打开,在关闭时保存更新

  • 当一个文件进行编译时,储存它的依赖关系到数据库当中。

通过跟踪 open_file()自动确定它们

slide30
挑战:二进制版本
  • 如果纹理资源的二进制格式改变了,那么每一个纹理都需要进行编译
  • 解决方法:重用数据库:

在数据库中储存每一个编译资源的二进制版本

在数据编译器中再度检查当前版本

如果有一个不匹配就重新编译

  • 我们使用相同的代码库(甚至是相同的可执行程序exe文件)在数据编译器和运行时间上,所以二进制版本总是在sync系统中。
slide31

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

仍然有许多开销用于编译单个文件

触摸盘, ~2秒

遍历整个源代码数来检查修改时间

触摸盘,与项目规模成正比,5-20秒

读取和保存数据库,~1秒

与修改过的文件数量成正比

好吧,这是需要解决的必要工作

slide32

启动Exe文件

编译伺服器

扫描资源

依赖关系

重新编译

关闭

启动 & 关闭
  • 启动和关闭编译器过程需要花费数秒的时间
  • 解决方法:重新使用这一流程!

作为伺服器运行

通过TCP/IP端口接收编译器请求

source = project

dest = project_win32

platform = win32

result = success

result = failure

error = Animation ”run” used by

state ”run_state” not found

slide33

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

扫描资源
  • 缓慢:检查每个项目文件的修改时间(mtime)
  • 脆弱:取决于日期

如果一个备份副本受到了恢复,我们可以有mtime(file) < mtime(dest)

在编写的dest很糟糕时就会崩溃

信任很重要:我们从未想要强制进行一次完全编译

foreach (file in source)

dest = destination_file(file)

if mtime(file) > mtime(dest)

compile(file)

slide34

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

想法:明确的编译列表
  • 工具发送一个它想要重新编译的文件列表
  • 工具持续跟踪那些应改变的文件

纹理编辑器知道所有用户改变过的纹理

  • 快速
  • 脆弱:在工具之外不起作用

svn/git/hg更新

在Photoshop里编辑纹理

在文本编辑器里编辑Lua文件

slide35

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

解决方法:目录观察程序
  • 在伺服器启动时进行一次完整的扫描
  • 在最初的扫描之后,使用目录观察程序检测变化

ReadDirectoryChangesW(...) 命令

  • 不需要进一步的扫描
  • 使用数据库来避免脆弱性

从上一次成功的编译里储存修改时间(mtime)到数据库里

如果扫描时修改时间或者文件大小改变了–重新编译

如果目录观测程序通知我们有改变发生–重新编译

slide36

source = project

dest = project_win32

platform = win32

require ”stuff”

function f()

print(”f”)

end

3. 请求到达编译伺服器

1. 文件改变了

C

2. 用户按下编译按钮

4. 伺服器获知改变的文件

目录观察程序争用条件

我们不知道收到时间会有多长

slide37

source = project

dest = project_win32

platform = win32

require ”stuff”

function f()

print(”f”)

end

3. 请求到达编译伺服器。伺服器创建一个临时文件

1. 文件改变了

C

5. 伺服器获知新的临时文件

2. 用户按下编译按钮

4. 伺服器获知改变的文件

争用条件技巧

使用临时文件作为“栅栏”

slide38

启动Exe文件

扫描资源

依赖关系

重新编译

关闭

依赖关系
  • 因为我们没有破坏进程,我们可以把依赖关系数据库保存在内存里

只需要在伺服器启动时从磁盘读取

  • 我们可以保存数据库到磁盘作为背景进程

当我们要求进行一次重新编译时,我们不需要等待保存数据库

当编译器处于空闲状态时,数据库就会在稍后进行保存

slide39
最后的进程

启动Exe文件

  • 磁盘访问只会在处理请求是下列项时发生:

编译修改过的文件

创建目录观察程序“栅栏”文件

  • 否则一切都发生在内存当中

读取数据库

扫描资源

启动观察程序

启动伺服器

分析清秋

查找修改

依赖关系

编译

发送回复

保存数据库

关闭

slide40
结果

高兴的内容创造者!!!

slide42
通用规则
  • 考虑资源颗粒度

为个别编译/重载选择合理的大小

  • TCP/IP端口是你的朋友

倾向于通过网络访问磁盘来进行工作

把进程当成伺服器运行来避免启动时间

  • 使用数据库 + 目录观察应用程序来跟踪文件的系统状态

数据库也可以在编译器运行之间缓存其他文件

保存在内存中,反映到背景磁盘上

ad