游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享以数据包流省略游戏加载画面的方法

发布时间:2012-08-04 13:40:35 Tags:,,,

作者:Niklas Frykholm

今天我要谈的是资源管理的一个特别的方面:流(streaming)。

流的优点有二:

1、免除加载画面。

2、显示了比内存能装入的还多的细节/种类。

今年是2012年,如果玛雅人的预言成真,那么这就是所有人被迫看加载画面的最后一年。因为我没有直接参与游戏制作,而只是制作引擎,所以我可以大胆地泛泛而谈。

loading-screen(from gameaxis.com)

loading-screen(from gameaxis.com)

快速小结

在Bitsquid引擎中,资源(resource)是指根据名称和类型能特定识别的小块数据。例如:

类型        名称

unit       units/beings/player

texture    vegetation/grass/tall_grass_01

wav        music/soft_jazz

资源文件是由我们的工具制作的,是人类可读的文件,以JSON式的格式书写。在运行时间内使用以前,资源文件必须经过编译。数据编译器将各种资源编译成平台特定的优化后的二进制大对象(以下称为blob):

streaming(from gamasutra)

streaming(from gamasutra)

为了便于装卸,资源被分类成数据包(package)。一个数据包(本身是资源)是一系列资源,可以同时装卸。

如果游戏非常小,整个游戏都可以装成一个数据包。而对于大游戏,一个数据包可能包含某个特定关卡所需的所有资源,或一套用于多个关卡的场景。由游戏程序决定何时装卸这些数据包。

至于游戏的最终释放,数据包被转换成数据束(bundle)。一个数据束包含所有在数据包中编译好的资源,而数据包被连结成一个单独文件,而这个文件由流压缩文件过滤器压缩而成。引擎一次装载整个数据束,无需搜索。这对于光纤媒介来说是很重要的,但它也确实加速了硬盘驱动的性能。

streaming(from gamasutra)

streaming(from gamasutra)

免除加载画面

为了免除加载画面,我们可以使用数据包流。我指的是在后台下载新数据包的能力,而引擎仍然做其他事。这意味着当玩家接近游戏中的一个新区域时,引擎就可以开始在后台下载那个区域的数据,同时玩家仍然在游戏。当玩家达到那个区域,数据已经存在内存里了,所以玩家不需要等待加载画面。

在Bitsquid引擎中,数据包总是在后台下载,所以数据包流是默认开户的。游戏程序可以告诉引擎何时开始下载数据包。下载是由一个独立的后台线程处理的,游戏程序可以测试引擎的每一帧,以决定下载是否完成。当下载完成,引擎就可以开始使用新数据。

在加载时间内,引擎应该做什么是由游戏程序决定的。游戏程序可以选择拖延引擎、显示加载画面或做其他更有趣的事。

对于成功的流,组织数据包的方式有很多,并且引擎允许你使用任何一种你偏好的方式。不同的方式可能对不同的游戏才能生效。线性进程的游戏可以把各个“阶段”归为一个数据包,在当前数据接近完成时再激活另一个数据包的下载。对于开放世界的游戏,各个章节的地图和其他“特殊的”场所的额外数据包可以归为一个数据包。具有随机战斗的游戏可以对玩家遇到的不同敌人形成单独的数据包。

事实上,引擎并没有固有的流模式,所以无法给设计师太多权力和灵活性。但有权力也伴随着更大的责任。设计师必须忍受巨大的压力,即正确地设置数据包和决定何时装卸数据包。

也许在将来,我们会选择一两种流模式作为“标准的方案”,然后为其提供一些功能性的便利。当然,如果有必要,你仍然可以选择“手动”模式。

显示更多细节

对于显示比内在能装入的还多的细节,我们使用了一种我也找不到更好的词来称呼的东西:资源流(resource streaming)。资源流指的就是,对于某种资源,当我们下载它的数据包时,我们不会将所有资源下载到内存中。我们只是分部分地把资源留在内存中,当需要时再一点一点地流进或流出。

流资源的最简单的例子也许就是视频文件。视频文件可以包含大量百万字节的数据,我们不想把所有这些数据作为一个大数据束下载到内存中。相反地,我们想在视频播放时,一帧一帧地将数据流进内存中。

记住我在小结中说的,各种资源被编译成平台特定的blob。其实,我说谎了。真相是,各种资源被编译成两种blob:

streaming(from gamasutra)

streaming(from gamasutra)

第一种是内存驻留blob(memory-resident blob)。这个我们已经知道了。它是一束一束地由后台线程下载进内存的。

第二种是流blob(streaming blob)。它包含的数据是不直接进入内存的,而是通过流管理器获取。流管理器负责在必要时将数据从内存中移进和移出。

并非所数据类型经过编译后都产生流blob。事实上,大多数都不产生。只有用资源流(如视频)输出流数据的类型才产生流blob。

由特定的内容类型的数据编译器决定什么进入内存驻留blob和什么进入流对象。例如,视频编译器将所有标题信息(游戏邦注:包括帧数、内容大小、帧率等)放入内存驻留blob,把原始帧数据放入流blob。这样,无需下载所有流数据,我们就可以知道视频的大小和其他有用的信息。

如果你想在此基础上制定MegaTexture的方案,那么你得把最低MIP(每秒百万条指令)与一些可以告诉你如何快速定位流中的数据的索引一起放入内存驻留数据。

对于声音数据,你可以将大约前100毫秒的声音放入内存驻留区域,其余的放入流中。这样,一旦触发,你就可以立即开始播放声音,而不必等任何数据流进。

特定数据包的所有流blob都被放入流束中,而这个流束紧接着下一个数据包的流束。各种资源的流数据的补偿和大小储存在一般的数据束中,与那种资源的内存驻留数据一起。这样,各种资源总是知道在哪里寻找它在流束中的流数据:

streaming(from gamasutra)

streaming(from gamasutra)

与一般的数据束不同,我们希望流束能够随机访问。因此,这应该依靠硬件驱动而不是光纤媒介。出于同一个原因,我们不压缩流束。你想流的大部分资源格式已经有内置压缩格式(视频、声音、贴图)。对于其他资源,当你编译数据时,你总是可以任意压缩。

当束被加载时,我们打开相应的流束,让文件句柄用于备查。任何想访问流数据的系统都可以使用这个句柄(异步)读取流束。

我们不提供任何通用系统来决定何时流进这个数据、在内存中贮存多少以及何时抛出。相反地,我们让各个支持资源流的独立系统来决定。视频流方案在如何贮存数据方面,与贴图流方案非常不同。迫使二者都遵守相同的模式会使事情变得复杂,且只能实现局部优化。

综合使用数据包流和资源流,你可以用一个简单又灵活的模式覆盖几乎所有流方案。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

In-depth: Read my lips – No more loading screens

by Niklas Frykholm

Today I want to focus on a particular aspect of resource management that I haven’t discussed in any great detail before: streaming.

Streaming is good for two things:

Getting rid of loading screens.

Showing more detail/variety than we can fit in memory.

This is 2012, and according to the Mayans it is the last year anyone should have to be forced to look at a loading screen. Since I’m not directly involved in games production, but only make an engine, I can afford to make such broad, sweeping statements.

A quick recap

Since the two links above are quite long reads, let’s start with a quick summary of how our resource system works.

A resource in the Bitsquid engine is a piece of data uniquely identified by its name and type. For example:

Type    Name

unit    units/beings/player

texture    vegetation/grass/tall_grass_01

wav    music/soft_jazz

The resource files are created by our tools. They are human readable files written in a JSON-like format. Before they can be used in the runtime, they need to be compiled. The data compiler compiles each resource into a platform specific optimized binary blob:

For loading and unloading, the resources are grouped into packages. A package (itself a resource) is a list of resources that can be loaded and unloaded together.

In a very small game, the entire game could be a single package. In a larger game a package could contain all the resources for a particular level, or a tile set that is used in multiple levels. The gameplay programmers decide when to load and unload packages.

For the final release of the game, the packages are converted to bundles. A bundle contains all the compiled resources in a package concatenated together to a single file which is compressed by a stream compression filter. The engine loads the entire bundle in one go, without seeking. This is crucial for optic media, but it also really speeds up hard drive performance.

Getting rid of loading screens

To get rid of loading screens we can use package streaming. By this I simply mean the ability to load new packages in the background while the engine is doing other things. This means that when the player is approaching a new area in the game, we can start downloading that data in the background, while she continues playing. When she arrives at the area, the data is already in memory and she can proceed without having to wait for a loading screen.

In the Bitsquid engine, packages are always loaded in the background, so package streaming is enabled by default. The gameplay programmer can tell the engine to start loading a package. The loading is handled by a separate background thread and the gameplay programmer can poll the engine every frame to determine if the loading has completed or not. When the loading is done she can start to use the new data.

It is up to the gameplay programmer to decide what the engine should do during the loading time. She can choose to stall the engine (ugh), show a loading screen (semi-ugh) or do something more interesting (yes, 2012!).

There are many different ways of organizing and structuring packages for successful streaming and the engine lets you use whichever method you prefer. Different solutions might work for different games. A game with linear progression could have a package for each “stage” and trigger download of the next one when the current is almost complete. An open world game could have a package for each section of the map, together with additional packages for “special” locations. A game with random encounters could have separate packages for the different encounters you can run into.

The fact that the engine doesn’t have a hard-wired streaming model gives the designer a lot of power and flexibility. But with that power also comes a greater responsibility. The designer must bear the entire burden of setting up the packages correctly and deciding when to load them and unload them.

Perhaps in the future, we will pick one or two streaming models as “standard solutions” and provide some convenience functionality for them. You will of course still have the option to drop into “manual” mode for full flexibility if needed.

Showing more detail

For showing more detail than can fit in memory we use something that I for lack of a better word call resource streaming. What resource streaming means is simply that for certain resources, we don’t load the entire resource into memory when we load its package. We only keep the resource partially in memory and stream bits and pieces of it in and out as needed.

The simplest example of a streaming resource is perhaps a video file. Video files can contain hundreds of megabytes of data and we don’t want to load all that into memory as one big blob. Instead we want to stream it in, frame by frame, as the video is being played.

Remember what I said in the recap, that each resource gets compiled into a platform specific binary blob. Actually, I lied. The truth is that each resource gets compiled into two blobs:

The first one is the memory-resident blob. That is the one we already know about. It goes into the bundle, gets loaded into memory by the background thread, etc.

The second one is the streaming blob. It contains data that shouldn’t go straight into memory but instead be accessible to a streaming manager that will move the data in and out of memory as necessary.

Not all data types produce a streaming blob when compiled. In fact, most don’t. Only types that use resource streaming (such as video) output any streaming data.

It is up to the data compiler for the specific content type to decide what goes into the memory-resident blob and what goes into the streaming blob. For example, the video compiler puts all header information (number of frames, content size, frame rate, etc) in the memory-resident blob, and just the raw frame data in the streaming blob. That way we can know the size of the video and other useful information without pulling in any stream data.

If you wanted to build a mega-texture solution on top of this (we haven’t) you would put the lowest MIP-level in the memory resident data together perhaps with some kind of index that told you how to quickly locate data in the stream.

For voice data, you could put the first 100 ms or so of the sound into the memory-resident area and the rest into the stream. That way you can start playing the sound immediately when it is triggered, without waiting for any data to be streamed in.

All the streaming blobs for a particular package are put into a streaming bundle that ends up next to the ordinary bundle for the package. The offset and size of the streaming data for each resource gets stored in the ordinary bundle together with the memory-resident data for that resource. That way, each resource always knows where to find its streaming data in the streaming bundle:

Unlike the ordinary bundle, we expect the stream bundle to be accessed randomly. For that reason it should preferably reside on a hard drive rather than on optical media. For the same reason, we do not compress the stream bundle. Most of the resource formats that you would want to stream already have built-in compression (video, sound, texture). For other resources, you can always add whatever kind of compression you want when you compile the data.

When a bundle is loaded, we open the corresponding stream bundle and keep the file handle around for future reference. Any system that wants to access the stream data can use this handle to (asynchronously) read from the stream bundle.

We don’t provide any general system for deciding when to stream in this data, how much to cache in-memory and when to throw it out. Instead we leave that up to each individual system that supports resource streaming. A video streaming solution will have very different ideas about how to cache data than a texture streaming solution. Forcing them both to adhere to the same model would just complicate and suboptimize things.

With the combination of packet streaming and resource streaming you can cover almost any imaginable streaming scenario with a model that is both simple and flexible.(source:gamasutra)


上一篇:

下一篇: