摘要

受管贴图(Managed textures,也就是我们通常所谓的“自动管理贴图”),在DX6中首次被引入,经过一系列的改进和增强,在DX9中自动管理的资源类型增加到贴图,顶点缓冲,顶点索引缓冲,所有这些资源使用统一的公共接口。通过使用D3D资源管理器,应用程序可以轻松的处理设备丢失、处理稍微过量的显存使用。

有时开发者在使用受管资源会遇到一些困难,这部分归咎与系统的抽象特性。在大多数情况下使用受管对象是不错的选择,但有时出于性能考虑也会使用非托管资源。这篇文章将讨论一般情况下如何处理资源,受管与非受管资源的行为差别。

 

内容

l         显示内存

l         受管资源

l         驱动管制资源

l         默认资源

l         系统内存资源

l         一般性的建议

 

显示内存

为了使得资源可以利用显存,GPU需要通过内存访问定位他。GPU访问(Local video memory)显存是非常高效的,并且某些资源(例如RenderTarget,深度、模板缓冲)必须在本地显存(Local video memory)定位。由于AGP的出现,GPU可以直接访问部分系统内存,而这部分系统内存区域就是所谓的非本地显存(non-local video memory),当然这部分内存(显存)也是不能挪做它用的。非本地显存仅能GPU访问,与访问本地显存相比,其效率低一些。需要明确的是,所有AGP内存在设备丢失时都会失效,都需要在恢复他们。

一些集成显卡使用统一内存结构(Unified Memory Architecture),这样主内存可以被系统任何一个设备寻址。D3D支持UMA而不需要修改任何代码,这样我们把系统内存配置为本地显存,硬件确保资源的定位就像传统的结构一样进行工作。

受管资源

大部分资源应该使用POOL_MANAGED方式创建,即受管资源。所有受管资源将被创建在系统内存,在需要的时候复制到显存。当发生设备丢失时会自动copy系统内存到显存。既然不是所有受管资源都需要一次送入显存,这样你可以提交超过渲染每帧所必须使用的最小内存容量,但是这样会使得大量显存内容因为分页操作而写到磁盘上,这是非常耗时的。这也是为什么恢复设备如此耗时,因为需要将大量磁盘数据复制到显存。

DX会为每份资源在最后一次使用时加上时间戳,这样当显存分配失败时,它会释放那些最近最少使用的资源(LRU算法)。使用SetPriority函数可以标记资源的重要程度,重要的资源优于时间戳的判断,所以那些比较常用的资源应该设置高优先级,而不用担心因为时间戳过期而导致资源被释放。在DX9中,驱动程序提供的显存管理信息是非常有限的,运行时可能不得不清除大量资源用于分配足够的内存。设置适合的优先级是非常有用的,这样D3D不会清除那些马上又需要使用的资源。应用程序可以强制调用EvictManagedResources清除所有受管资源,但是如果下一帧又需要重新加载这些资源,这将是非常耗时的,不过这个函数在那些场景明显需要改变(比如进入下一个关卡)的情况下,还是非常有用的。

如果“当前帧”内需要非常多资源用于渲染,这将是件麻烦的事情,用前面的LRU方式调度资源效率就不太理想了,这个时候使用MRU资源调度方式取代,即优先清理那些比较活跃的资源。注意,这里“当前帧”的概念是指BeginSceneEndScene之间的需要渲染的帧。

开发人员如果想得到关于受管资源的更多信息,可以通过IDirect3DQuery9接口查询,但是这个接口仅能用于调试模式(debug runtimes),在发布版本中,应用程序不能依靠改接口的信息做任何假定。

了解资源管理如何工作可以帮助我们调试、调整程序,重要的是应用程序不要太过依赖当前的运行库(或者驱动程序)的资源管理方式,驱动更新有可能导致其行为发生变化,将来的D3D将会有套久经考验的资源管理方式。

驱动程序管理的资源

D3D驱动可以自由的实现“由驱动管理贴图”的特性,通过D3DCAPS2_CANMANAGERESOURCE段可以查询硬件驱动是否支持这个特性,这样驱动将代替D3D运行库管理资源。对于级少数的硬件是支持这个特性的,对于大多数硬件则不尽相同,你可以咨询你的产品提供商获得这方面信息。一般情况下,你可是使用D3DCREATE_DISABLE_DRIVER_MANAGEMENT方式创建设备,这样将由D3D运行库来管理资源。

缺省资源管理(非受管资源)

虽然受管资源非常简单,容易使用,高效,但是有时我们希望直接往显存里写东西,这种情况下我们需要使用POOL_DEFAULT方式创建资源。使用这种方式会增加程序的复杂性,代码需要应付所有设备丢失的情况,并需要谨慎考虑何时复制数据到显存。错误的指定USAGE_WRITEONLY标记或者锁定渲染目标(Render Target)将严重影响性能。

锁定POOL_DEFAULT类型的资源很可能导致GPU停止运转,这与POOL_MANAGED类型的资源是不同的,除非使用一些特性的指示标记。根据资源当前的位置不同,锁定后得到的指针也不相同,可能是一块临时的系统内存,也可能直接指向AGP内存。如果是临时的系统内存,Unlock后将把这段数据送入显存,这是因为如果显卡资源不是只写的(write-only),Lock的时候数据将不得不被送入一段临时的内存;如果指向的AGP内存区域,临时的拷贝是可以避免的,但是cache的行为将会降低性能。

为了避免在写入一整行数据(a full cache line of data)进入AGP内存区导致write-combing性能下降(一般是由于发生了一次读写周期),顺序的访问AGP内存是推荐的做法,如果你的程序需要随机的访问AGP内存,而你又不希望使用受管资源,那么你可以使用系统内存作为替代方案,这样当你生成了数据之后,可以lock后拷贝,这样不会带来太大的性能损失,这里的性能损失一般是由缓冲的“写搜索”操作引起。(注,这里关于词汇cache write-combing译者也不知道对应的中文含义,只能按照字面意思翻译,见谅)

对于某些类型的资源,使用LOCK_NOOVERWRITE标记会使添加数据比较有效率,但是多次的LockUnlock同一资源还是需要尽量避免的,适当的利用多种不同的锁定标记对于效率优化使非常重要的,就像填充锁定内存区域最好使用cache友好的(cache-friendly)数据访问方式一样。

受管资源和缺省资源混合使用

受管资源与非受管资源的混合分配使用可能导致显存碎块,并且扰乱受管资源使用的内存区域。最好在使用受管资源前使用非受管资源,或者使用受管资源后使用EvictManagedResources函数清除那些受管资源再使用非受管资源。记住,所有非受管资源都会常驻显存,这样其他内存需求就不能使用了。

注意,与以往的DX版本不同,在显存缺乏时,如果分配非受管资源失败,DX9会自动清除受管资源,这有可能导致潜在的显存碎块,甚至把资源放入不适当的地方(比如非本地内存的静态贴图区)。所以,最好在使用受管资源之前分配全部的非受管资源。

动态缺省资源

如果数据需要很高频率更新,那最好使用非受管资源,并使用USAGE_DYNAMIC标记,这样驱动会决定最适合的地方放置这些需要经常更新的数据。这通常意味着放置在非本地显存中,这样对于GPU来说,访问速度可能相对要慢一些。而对于UMA架构,驱动将会选择CPU访问效率较高的特殊地方放置这些数据。

这种用法(动态缺省资源类型)一般用于软蒙皮和基于CPU计算的粒子系统的顶点/顶点索引的Buffer填充,LOCK_DISCARD标记可以保证资源仍被使用的时候,锁定操作不会导致系统停止暂停工作。在这种情况下,使用受管资源会更新系统内存,然后拷贝到显存。对于系统的非本地内存,多余拷贝是不需要的。

标准的贴图是不允许锁定的,仅仅可以通过UpdateSurfaceUpdateTexture函数更新。一些系统支持动态贴图,它可以通过配合使用LOCK_DISCARD标记进行锁定,但这需要检查D3DCAPS2_DYNAMICTEXTURES硬件能力。对于高动态贴图(如视频、程序生成贴图),最好使用非受管资源和系统内存资源,并且通过UpdateTexture函数更新贴图。对于高频度的粒子更新,UpdateTexture函数可能是最好的选择。

在有限的总线-内存带宽下,静态贴图资源应该使用POOL_MANAGED方式,这样可以确保它最好的利用本地显存,并有较好的效率。对于“半静态”资源,使用动态类型资源有时会获得更好的效率。

系统内存资源

资源可以使用POOL_SYSTEMMEM方式创建。但他们不能用于图形管线,他们仅能做为源数据用于更新POOL_DEFAULT类型的资源,这是通过UpdateSurfaceUpdateTexture函数完成的。他们的锁定操作也非常简单,尽管他们同样可能因为前面提到的原因导致系统停止运转。

虽然是在系统内存中创建资源,但POOL_SYSTEMMEM所支持的资源格式和能力(比如最大尺寸)是受硬件、驱动限制的。同样是在系统内存中创建资源的POOL_SCRATCH则没有这方面限制,它支持所有格式和能力,但设备却不能直接访问它。SCRATCH类型资源一般用于内容创建工具。

一般性的建议

了解资源管理的技术实现细节对达成你的程序的性能目标是有非常大的帮助的,规划你的资源如何交给D3D并设计好的结构以便能及时加载必要的数据是一件非常复杂的工作,为此我们给出一些好的实践经验做为一般性的原则:

l         预处理你的资源。不要将耗时的加载资源、资源转换、资源优化丢给用户去做,虽然这样便于开发,但确让用户无法忍受。预处理这些资源可以加快加载,更快使用,你的用户也会发现你的程序跑的更快了。

l         避免在每帧创建过多的资源。对于过多的资源加载,可以把他们分到多帧里完成或者不要急于释放那些暂时不用的资源。

l         确保在一帧结束时已经断开了所有资源通道。(比如,顶点流,texture stages,顶点索引)。

l         对于贴图,建议使用压缩贴图(DXTn)格式,建议使用mip-map或者将小贴图拼接为大贴图使用。

l         建议使用顶点索引,这将减少数据传输量。

l         对于过渡的优化资源管理是需要谨慎的。如果你的程序过分依赖驱动、硬件和操作系统的某些特征,那么这些程序、硬件的修改将会导致潜在的性能问题。