android - 在Android的OpenGL ES中运行时创建大型纹理的最有效方法

标签 android c++ graphics opengl-es textures

我正在使用Unity3D内置的Android应用程序工作,该应用程序需要经常在运行时基于不同的图像像素数据创建新的纹理。

由于Android版Unity使用的是OpenGL ES,而我的应用程序是一个图形化的应用程序,理想情况下必须以每秒60帧的速度运行,因此我创建了一个在OpenGL代码上运行的C++插件,而不仅仅是使用Unity的Texture2D慢速纹理构造。该插件允许我将像素数据上传到新的OpenGL纹理,然后通过其Texture2D的CreateExternalTexture()函数让Unity知道。

不幸的是,由于在此设置中运行的OpenGL ES版本是单线程的,因此为了使事物在帧中运行,我对glTexImage2D()进行了调用,并调用了已经生成的TextureID,但第一帧中的数据为空。然后在多个后续帧上用我的像素数据缓冲区的一部分调用glTexSubImage2D(),以填充整个纹理,本质上是同步进行纹理创建,但将多个帧上的操作分块!

现在,我遇到的问题是,每次创建大尺寸的新纹理时,即使我将空数据放入其中,第一个glTexImage2D()调用仍然会导致出帧。我猜想这是因为即使我稍后才填充图像,第一个glTexImage2D()调用仍会在后台进行大量的内存分配。

不幸的是,我要为其创建纹理的这些图像具有不同的大小,这些大小我之前都不知道,因此我不能仅在加载时就创建一堆纹理,我需要使用每次都有新的纹理。 =(

无论如何,我是否可以避免这种内存分配,也许在开始时分配了一块巨大的内存并将其用作新纹理的池?我读过很多书,人们似乎建议使用FBO?我可能会误解了,但是在我看来,您仍然需要执行glTexImage2D()调用来分配纹理,然后再将其附加到FBO?

欢迎任何和所有建议,在此先感谢! =)

PS:我不是来自图形背景,所以我不了解OpenGL或其他图形库的最佳实践,我只是在尝试在运行时创建新纹理而不会形成框架!

最佳答案

我还没有处理您遇到的特定问题,但是我发现纹理池在OpenGL中非常有用,因为它无需考虑太多即可获得有效的结果。

就我而言,问题是我不能将与用于输出结果的纹理相同的纹理用于延迟着色器的输入。但是我经常想这样做:

// Make the texture blurry.
blur(texture);

但是,我不得不创建11种具有不同分辨率的不同纹理,并且必须在它们之间进行交换,以用作带有FBO的水平/垂直模糊着色器的输入和输出,以得到看起来不错的模糊效果。我从未非常喜欢GPU编程,因为我曾经遇到过一些最复杂的状态管理。由于根本要求着色器的纹理输入也不能用作纹理输出,因此我不得不去画图板只是为了弄清楚如何最大程度地减少分配的纹理数量,这实在令人难以置信。

所以我创建了一个纹理池和OMG,它简化了很多事情!它做到了,所以我可以左右左右创建临时纹理对象,而不必担心,因为销毁纹理对象实际上并没有调用glDeleteTextures,它只是将它们返回到池中。所以我终于能够做到:
blur(texture);

...就像我一直想要的。出于某些有趣的原因,当我越来越多地使用该池时,它加快了帧速率。我想即使我想尽一切办法来减少分配的纹理数量,但我仍在以消除池的方式分配比我需要的更多的东西(请注意,实际示例中所做的远远超过了包括DOF在内的模糊处理, Bloom,hipass,lowpass,CMAA等,而GLSL代码实际上是根据用户可用来动态创建新着色器的可视化编程语言即时生成的。

因此,我真的建议从探索这个想法开始。听起来这将对您的问题有所帮助。以我为例:
struct GlTextureDesc
{
    ...
};

……鉴于我们可以指定多少个纹理参数(像素格式,颜色分量的数量,LOD级别,宽度,高度等),这是一个相当庞大的结构。

然而,该结构具有可比性和可哈希性,最终被用作哈希表(如unordered_multimap)中的键以及实际的纹理句柄作为关联的值。

然后,我们可以执行以下操作:
// Provides a pool of textures. This allows us to conveniently rapidly create,
// and destroy texture objects without allocating and freeing an excessive number 
// of textures.
class GlTexturePool
{
public:
    // Creates an empty pool.
    GlTexturePool();

    // Cleans up any textures which haven't been accessed in a while.
    void cleanup();

    // Allocates a texture with the specified properties, retrieving an existing 
    // one from the pool if available. The function returns a handle to the
    // allocated texture.
    GLuint allocate(const GlTextureDesc& desc);

    // Returns the texture with the specified key and handle to the pool.
    void free(const GlTextureDesc& desc, GLuint texture);

private:
    ...
};

在这一点上,我们可以左右左右创建临时纹理对象,而不必担心过多调用glTexImage2DglDeleteTextures。我发现它非常有用。

最后要注意的是上面的cleanup函数。当我将纹理存储在哈希表中时,我在它们上添加了时间戳(使用系统实时)。我会定期调用此清理函数,然后该函数将扫描哈希表中的纹理并检查时间戳。如果他们刚坐在游泳池中闲置时经过了一段时间(例如8秒),我将调用glDeleteTextures并将其从游泳池中删除。我使用一个单独的线程以及一个条件变量来构建纹理列表,以在下次通过定期扫描哈希表来删除有效上下文时将其删除,但是如果您的应用程序都是单线程的,则可以调用此清理操作在您的主循环中每隔几秒钟运行一次。

就是说,我在VFX中工作,实时性要求不如AAA游戏那么严格。我的 Realm 更多地关注离线渲染,而我离GPU向导还很远。可能有更好的方法来解决此问题。但是,我发现从这个纹理池开始非常有帮助,我认为这对于您的情况也可能会有所帮助。实现起来相当琐碎(仅仅花了我半个小时左右的时间)。

如果您请求分配/释放的纹理大小,格式和参数到处都是,那仍然可能最终分配和删除很多纹理。在那里,这可能有助于稍微统一一些东西,例如至少使用POT(2的幂)大小等等,并确定要使用的最小像素格式数目。在我的情况下,这并不是什么大问题,因为我只使用一种像素格式,并且我想创建的大多数纹理临时尺寸恰好是放大到天花板POT的视口(viewport)的大小。

至于FBO,我不确定它们如何通过过多的纹理分配/释放来解决您的直接问题。我主要将它们用于延迟着色,以便在将几何图形以合成样式的方式多次应用到生成的2D纹理后,对诸如DOF的效果进行后处理。我自然而然地使用FBO,但我想不出FBO如何立即减少您必须分配/解除分配的纹理数量,除非您可以仅将一个大纹理与FBO一起使用并将多个输入纹理呈现给屏幕外输出质地。在那种情况下,FBO不能直接提供帮助,而只能创建一个巨大的纹理,您可以将其部分用作输入/输出,而不是许多较小的纹理。

关于android - 在Android的OpenGL ES中运行时创建大型纹理的最有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43185911/

相关文章:

java - 将文本中心与 android 对齐

java - 在 Java 中使用特定的安全提供程序

Android NDK - getFieldID 结果导致 NoSuchFieldError

android - 如何使用flutter阻止用户访问应用程序中的音频文件?

c++ - 如何使用 is_base_of 专门化模板而不与主模板混淆?

c++ - 在 OS X 上的 Xcode 中使用 AES/GCM 时出现编译错误

graphics - 如何实现老片效果?

c++ - 梯度下降算法不会收敛

java - 使用 java.swingTimer 重复动画

algorithm - 路径跟踪算法 - 需要帮助理解关键点