最近,我在加载2D纹理数组的图层时出现了相当混乱的性能下降,但是仅在一种情况下,我可以告诉我,所以我推测为什么会发生这种情况,因为我之前从未见过这样的事情。

首先,为了解释该问题的背景,我基于NVIDIA和idTech阅读的一些论文实施了虚拟纹理图集的简单版本,这些论文在短时间内加载了纹理(已被分成固定大小块)划分为2D纹理数组的各个级别,根据这些层在CPU端的空闲索引列表中被视为“空闲”的层。然后,通过访问存储为持久一致地映射的SSBO(用于CPU端更新)的页表来执行片段着色器中的采样,该页表将UV中的偏移与纹理数组的层相关联。

创建时纹理未初始化的事实是,每一层都被认为是空闲的,因此要加载的纹理图块将缓慢填充未初始化的层(优先于先前释放的层)。一旦使用了所有未初始化的层,然后其他瓦片将开始覆盖先前释放的层(如果需要时存在)。另外请注意,我正在使用glCompressedTexSubImage3D加载每一层,并且也通过此调用手动加载了mipmap级别。

该系统运行良好,并且经过相当广泛的测试,直到我开始注意到该问题所涉及的问题。简而言之,在纹理阵列的所有未初始化层用完后,接下来加载的5-15(我不知道确切多少,但只有几个)纹理瓦片会遭受性能大幅下降。在那之后,一切恢复正常,问题再也没有恢复(据我所知)。还要注意,这是在纹理数组的下端使用某些当前绘制的纹理的时候,该纹理正好在第一个免费图块开始之前(由于空闲列表的先进先出性质)。 >
进一步检查问题后,我注意到通过使用NVIDIA Nsight图形分析工具,在性能受到长时间影响的情况下,glCompressedTexSubImage3D之后的调用会严重阻塞CPU,通常每个mipmap 10-40ms级别(在本例中为glGetError,但是在禁用错误检查以确保这不是问题之后,跟随它的其他任何调用也具有相同的效果)。与GPU发生某种同步问题,可能是由于新加载的图块相对于当前正在绘制的图块而言所处的位置所致,因为它可能认为它们“正在使用”。由于每个呼叫阻塞了多长时间,因为每个呼叫都是16ms的多个帧的长度,而且即使经过相同的过程多次,这个问题也似乎再也不会出现,所以这个假设在我心中没有加起来我并没有忽略这种可能性。

我发现的另一个奇怪的事情是,在下一个调用被阻塞的时候,根据NVIDIA Nsight,许多微小的GPU命令正在执行,我通常只能在绘画活动。

在这一点上,我很沮丧,不知道是什么原因造成的,因此,不胜感激。下面提供了其他详细信息(如果需要,可以提供更多详细信息):

纹理数组(共有3个):


格式:COMPRESSED_RGB_BPTC_UNSIGNED_FLOATCOMPRESSED_RED_RGTC1
COMPRESSED_RG_RGTC2

GPU: NVIDIA Nsight结果:

3个长帧的宽视图,发生缓慢的纹理加载(屏幕下方的线位于第一个纹理加载调用的中心): br />
在第一次缓慢调用glCompressedTexSubImage3D(由于它仅阻塞了几微秒)的开始附近时放大了视图,请注意,阻塞的glGetError调用下方代表着GPU命令的绿色细线(同样,屏幕下方的一行位于第一个纹理加载调用的中心):



评论

可能是某种错误的共享:您用纹理上传覆盖的内存与GPU同时读取的部分地图集共享了缓存行?您要覆盖的地图集的区域大小和形状是什么?

@DanHulme因为它是2D阵列纹理,所以我只加载要覆盖的图层的每个mipmap级别。我在GPU上使用了512层纹理中的50层左右,这些层在加载时会导致奇怪的性能问题,因为在查找新层时,它遍历了所有未初始化的层后从头开始缠绕返回列表中最旧的“免费”页面,该页面恰好位于该活动纹理层范围的旁边。我以为这与它有关,但事实是它只在第一次发生时才让我觉得不一样。
另外,一旦有时间,我将尝试对此问题进行最小化的说明,因为我对发生的事情做出了很多可能的假设,更不用说该项目中发生的所有其他事情了。这可能会影响纹理加载(在很多地方,我都使用glClientWaitSync和其他方法进行同步,但我怀疑这些位置是否会引起类似问题)。

#1 楼

从问题仅在开始覆盖以前使用的纹理图块时才表现出来的方式来看,我怀疑您正在尝试驱动一些启发式驱动程序,试图管理GPU上纹理数据的寿命。

在通常,当您从CPU更新缓冲区或纹理数据时,驱动程序必须确保它不会覆盖GPU可能仍在使用的任何内容(考虑到CPU和GPU彼此异步运行)。驱动程序可以通过多种方式执行此操作,例如在内部对纹理进行双缓冲,插入同步点(供CPU在GPU上等待),或首先将数据复制到临时暂存缓冲区,然后再将其复制到它的最终目的地。所有这些东西都是在后台进行的,驱动程序会根据纹理大小,使用模式等使用一些启发式方法,自行决定在任何给定情况下采用何种策略。

我只能猜测细节,但我怀疑发生的事情是驱动程序进入一种状态,认为该状态以前使用过的纹理图块仍以某种方式在GPU上使用。然后,当您开始覆盖它们时,它决定需要制作整个纹理的第二个实例,并在最终完成所需的更新(或类似的操作)之前费力地复制所有内容。那可能是您在Nsight中看到的许多小图。也许纹理足够大,以至于第二个副本会使您用完VRAM?在这种情况下,由于事情会溅到主RAM中,因此会产生很多麻烦。不过,这只是一个猜测。至于为什么不良行为只发生在几个图块上然后又恢复正常的原因,可能是驾驶员的启发式方法决定在此时切换到其他策略。

无论如何,关于修复方法,每当您释放纹理图块时,都可以尝试使用glInvalidateTexSubImage。这使驱动程序知道您不在意该纹理图块中的内容,这有望使它说服它只是就地更新纹理,而不是对其执行任何疯狂的操作。

另一件事尝试进行的操作是确保您在释放帧后不要在下一帧中重新使用纹理图块,而应在重新使用它之前给它一两个“停机时间”。但这听起来似乎已经是事实了,因为在开始重新使用它们之前,您更喜欢未使用的图块。 .com。那里的OpenGL驱动程序团队可能会捕获您的Nsight捕获并帮助诊断问题,如果是由于驱动程序错误而导致的,请予以解决。

评论


$ \ begingroup $
这是有道理的,尽管我怀疑我是否真的会用完VRAM,因为上次我检查这些纹理每个大约只有40MB,而960M有4GB,所以应该足够。同样,是的,整个未分配优先级和先进/先进的管理磁贴系统都应该始终确保其更新最旧的已释放磁贴。我会尝试该呼叫,看看是否可行。
$ \ endgroup $
–柠檬掉落
17年4月11日在0:34

$ \ begingroup $
好吧,我在释放图块时实现了glInvalidateTexSubImage事情(假设我做对了,它在有问题的图层的每个mipmap级别上都调用了它),它似乎没有任何改变。我将保留它,因为它可能会以某种方式对GPU有所帮助,但是我想现在向NVIDIA发送一些东西可能是最好的选择(我可能会在以后的一个小程序中尝试做一个最小的示例)。我有时间)。现在,我还将尝试预先初始化整个纹理,以查看是否有帮助。
$ \ endgroup $
–柠檬掉落
17年4月11日在17:58



$ \ begingroup $
您也可以尝试使用PBO从驱动程序映射内存客户端。然后,您将填满PBO,取消映射内存,并排队glCompressedTexSubImage2D,让驱动程序自己上传。
$ \ endgroup $
– Mokosha
17-4-14在6:24



$ \ begingroup $
@Mokosha是的,我可能要在下一个项目中这样做,但我当时并不知道这是做这个事情的原因,由于时间限制,我现在无法完全实现它。
$ \ endgroup $
–柠檬掉落
17年4月14日在6:36