OpenGL纹理Mipmap:加载和卸载

时间:2015-12-16 08:09:41

标签: opengl opengl-es textures mipmaps

在完美的情况下,如果屏幕分辨率仅为1024x768 在给定时刻需要786432个纹素,以及2兆字节的内存 足够。但在现实世界中,有管理成本,所以纹理 记得更多。

纹理流可以降低纹理的内存成本 是的,并非在给定时刻需要纹理的所有mipmap。 纹理需要0级mipmap,因为它靠近相机,如果 它远离当前的摄像头,可能是5到11级的mipmaps 足够。相机在场景中移动一段时间,一些mipmap可以 加载,一些mipmap可以卸载。

我的问题是如何有效地做到这一点。

假设我在场景中有一个512x512的OpenGL纹理,所以它会有 10个mipmap。从0级到9级,有:512x512,256x256, 128x128 ...和1x1 mipmap。简单地上传这样的数据:

glBindTexture(GL_TEXTURE_2D, texId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, p1);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, p2);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, p3);
...
glBindTexture(GL_TEXTURE_2D, 0);

过了一会儿,相机远离场景中的这个纹理, 64x64 mipmap就足够了,所以前3个mipmap将会是 卸载:

glBindTexture(GL_TEXTURE_2D, texId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, p4);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, p5);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, p6);
...
glBindTexture(GL_TEXTURE_2D, 0);

然后,相机朝着这个纹理移动,256x256 mipmap是 需要:

glBindTexture(GL_TEXTURE_2D, texId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, p4);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, p5);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, p6);
...
glBindTexture(GL_TEXTURE_2D, 0);

这只是效率低下。基本上它重新创建纹理 每次虽然纹理ID没有改变。公益组织可以做出 它更快但数据复制仍然是成本。

对于1024x1024 OpenGL纹理,我可以使它仅使用较低的纹理 mipmaps,比如级别1到9,并将级别0 mipmap保留为空(do 不在视频内存中分配)?换句话说:永远保持一个 mipmap的子集从低级别到高级别。装载或卸载 更高级别的mipmap而不更改a的下部mipmap 质地。我认为从硬件的角度来看,这可能是可能的。

这就是我尝试过的:如果我没有为级别0调用glTexImage2D mipmap,此纹理可能处于不完整状态。但是,如果我打电话 glTexImage2D带有一个空数据指针,它将被分配 零数据的视频内存(通过gDEBugger进行分析)。

2 个答案:

答案 0 :(得分:1)

  

所以纹理需要更多的记忆。

其实没有。 (EDIT)用于1维纹理第0个mipmap级别消耗N个字节,第一个N / 2,第二个N / 4,依此类推。所以消耗的总字节数是

sum(i in 0…){ N * 2^-i } = N * sum(i in 0…){2^-i}

这是一个收敛到2的几何系列。因此,一个mipmaped纹理消耗的内存恰好是非mipmaped纹理的两倍。对于2D纹理,它是1 / 4,1 / 16等等。纹理的尺寸越大,mipmap开销就越小。

这不是“很多”。

  

纹理需要等级0 mipmap,因为它靠近相机,如果距离当前相机很远,则5到11个mipmap可能就足够了。

OpenGL中没有摄像头,而不是如何确定mipmaping级别。使用的Mipmap级别由屏幕坐标中纹理坐标的变化率(纹理坐标的渐变)确定。

  

过了一会儿,相机远离场景中的这个纹理,64x64 mipmap就足够了

也许你在使用浅纹理坐标渐变绘制它之后,使用相同的纹理在具有非常陡峭的纹理坐标渐变(低mipmap级别)的基元上进行渲染。

  

但在现实世界中存在管理成本,

我想说的是,尝试正确传输mipmap的管理开销要高得多,并且消耗的GPU资源要多得多,而不是简单地加载所有mipmap并将其称为一天。

现代GPU也可以自行获取数据;图形RAM只是系统RAM的缓存,当你加载纹理时,它实际上并不首先进入图形内存。 GPU将获取所需的数据。

答案 1 :(得分:1)

如果你所有这一切的目标是限制你可用的活动纹理内存量,那么OpenGL 4.5中没有任何东西可以帮助你。将纹理重新分配为较小的一个是一个糟糕的想法(不,虚幻引擎那样做)。

有两种情况你所谈论的可能很重要。案例1将是虚幻引擎用于它的内容:加载性能。它分配整个纹理,所有的mipmap,但它只是首先加载较低的mipmap。这节省了级别加载的时间。这也可以通过做同样的事情来加速流媒体性能。

这在OpenGL 4.5中很容易完成。这就是mipmap范围设置的用途;您只需加载较低的mipmap,并将GL_TEXTURE_BASE_LEVELGL_TEXTURE_MAX_LEVEL设置为该范围。 OpenGL保证它不会尝试访问该范围之外的内存。

但是,从内存中动态驱逐mipmap并不是OpenGL 4.5具有的机制。

ARB_sparse_texture然而 。它允许您声明某些mipmap是"稀疏"。您可以正常为它们分配存储空间,但您可以声明可以从内存中逐出更高级别的存储空间。您可以通过为其提供虚拟页面来确定何时可用级别。并且您可以删除这些页面,这允许GPU将该内存用于其他人。

理论上,这样做的目标是能够使用更多纹理而不会耗尽GPU内存(从而导致抖动)。但是,你真的没有能够有效地做到这一点的工具。

这是因为OpenGL没有告诉你你有多少内存可用。它也没有说明为缓冲区/纹理分配了多少内存。也没有说这些分配的缓冲区/纹理中有多少当前是活动的并且驻留在GPU上。所以你真的不知道什么时候接近捶打。

这意味着,如果您接近"接近"一些大的纹理,你仍然可以打破内存限制。并且OpenGL不会告诉你什么时候这样做。

即便如此,如果您计划围绕它的关卡布局,这仍然会有所帮助。哦,稀疏纹理也适用于虚幻引擎的情况。

  

我在考虑OpenGL纹理的每个mipmap是独立存储的,我们可以在运行时将较低级别的mipmap附加到纹理,为什么不能附加更高的mipmap。

不要误以为硬件遵循API所说的内容。

您是否只能对除第0层以外的mipmap发出glTexImage*D次来电?是;只需使用基本/最大级别来阻止在分配范围之外的访问(这将保持纹理完整)。

保证实现是否只为这些特定的mipmap级别分配内存?没有;实现可以在该范围之外分配mipmap级别。

事实上,有证据表明它不会那样运作。考虑ARB_texture_storage。这个扩展,OpenGL 4.3+的核心,提供了一个功能,可以一次分配纹理的所有mipmap级别。因此,不是为每个级别调用glTexImage2D,而是调用glTexStorage2D一次,它将为您指定的大小分配所有指定的mipmap级别。你可以让一些人离开小范围,但不能离开顶部。

这也使纹理不可变,因此您无法再次更改该纹理的存储空间。您可以上传到它,但无法在其上调用glTexStorage*DglTexImage*D。所以没有重新分配。

为什么ARB会创建一个扩展,如果个别分配是硬件实际支持的,那么整个目的是阻止您分配单个mipmap?如果你认为这是一个侥幸,也要考虑这一点。

当创建ARB_direct_state_access时,他们显然添加了用于纹理分配的DSA样式函数。但请注意,他们没有添加DSA样式的函数来制作不可变的纹理;他们没有添加glTextureImage*D。他们的推理? "不可变纹理是处理纹理的更强大方法"。

显然,ARB认为API中没有任何值可以让用户说明分配了哪些mipmap,哪些不是。

应该注意的是,Direct3D 12 (一个更低级别的API)中的任何内容都不允许这样做。 how much memory a texture needs问题的答案是purely a byte-count + alignment。它不是一系列字节计数,每个mipmap一个。 the functions to allocate resources类似地不允许扩展mipmap或类似的东西。

我甚至查看了Mantle的文档。它无法使图像的存储器不连续。 grBindObjectMemory(将内存存储与纹理相关联的函数)不接受指定mipmap级别的参数。

因此,我不会假设基于旧OpenGL API的硬件性质。