【问题标题】:Most efficient way of creating large textures at runtime in OpenGL ES for Android在 Android 的 OpenGL ES 中运行时创建大型纹理的最有效方法
【发布时间】:2017-04-03 13:27:23
【问题描述】:

我正在开发一个内置于 Unity3D 的 Android 应用,它需要在运行时根据不同的图像像素数据每隔一段时间创建新的纹理。

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

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

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

不幸的是,我正在为其创建纹理的这些图像具有我事先不知道的不同大小,因此我不能在加载时预先创建一堆纹理,我需要指定一个新宽度和每次新纹理的高度。 =(

无论如何我可以避免这种内存分配,也许在开始时分配一个巨大的内存块并将其用作新纹理的池?我已经阅读过,人们似乎建议使用 FBO 代替?我可能误解了,但在我看来,在将纹理附加到 FBO 之前,您仍然需要调用 glTexImage2D() 来分配纹理?

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

PS:我不是图形背景,所以我不知道 OpenGL 或其他图形库的最佳实践,我只是想在运行时创建新的纹理而不是框架!

【问题讨论】:

    标签: android c++ graphics opengl-es textures


    【解决方案1】:

    我还没有处理过您所面临的具体问题,但我发现纹理池在 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 并将它们从池中移除。我使用一个单独的线程和一个条件变量来构建一个纹理列表,以便在下次有效上下文可用时通过定期扫描哈希表来删除,但如果您的应用程序都是单线程的,您可能只需调用此清理在主循环中每隔几秒运行一次。

    也就是说,我从事的视觉特效工作不像 AAA 游戏那样对实时性有严格的要求。我的领域更关注离线渲染,我离 GPU 向导还很远。可能有更好的方法来解决这个问题。但是,我发现从这个纹理池开始非常有帮助,我认为它对你的情况也可能有帮助。而且实现起来相当简单(只花了我半个小时左右)。

    如果您请求分配/释放的纹理大小、格式和参数到处都是,这仍然可能导致分配和删除大量纹理。在那里它可能有助于统一一些东西,比如至少使用 POT(2 的幂)大小等等,并决定要使用的最小像素格式数量。在我的情况下,这不是什么大问题,因为我只使用一种像素格式,而且我想要创建的大部分纹理临时文件都是放大到天花板 POT 的视口大小。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      相关资源
      最近更新 更多