如何在OpenGL ES中有效地加载纹理

7 人关注

我对使用OpenGL有非常基本的了解,尤其是在Android上。我正在开发一个使用OpenGL的应用程序,以便在全屏图像之间快速切换(因为使用普通的Android框架太慢)。

我发现,为了加载纹理,我需要做这样的事情。

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
_gl = gl;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), _imageResourceId);
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();

现在,由于这些图片是要全屏显示的(而且它们相当大--1024x1024),这需要一些时间,特别是我需要同时加载5张图片。

所以我需要问一些关于改进代码的技巧的问题,特别是关于高效加载的问题(如果可能的话,还要使用更少的内存)。

  • 如果我通过使用RGB_565格式,将位图解码为没有透明像素,会不会提高加载图像到OpenGL(或解码阶段)的速度?

  • 输入的位图的宽度和高度是否必须是2的幂,或者我可以让OpenGL只取它想要的部分,这将有这个规则?我是说,也许我可以让texImage2D命令只取位图的一部分?

  • 也许我甚至可以从头开始只解码这一部分(所以如果我有一个10000x10000的巨大图像,我可以只解码其中1024x1024的部分,并将其交给OpenGL)?

  • 有没有可能只加载并显示第一张图片,然后在后台加载其他的图片?我听说你不能在一个不是处理它的线程中向OpenGL发送位图。在GlRendereronDrawFrame方法上加载它们,比如说,在它第二次被调用时,这样做有意义吗?

  • 我记得我听说过一个小窍门,就是把多个图像合并成一个图像(一个接一个,从左到右),这样在解码阶段可能会有一些速度提升。不知道这个技术的名字是什么。对于安卓上的OpenGL ES来说,这还可能吗?

  • 是否有可能避免创建一个Bitmap实例,而让解码以更原生的方式完成(NDK,也许)?根据我的测试,在模拟器上解码一个1024x1024大小的PNG文件大约需要400ms-700ms,然而将其发送到OpenGL大约需要50ms-70ms。

  • 在使用Procrank时,我发现OpenGL会占用大量的内存。有没有可能让它使用更少的内存?也许使用更好的纹理类型?

  • 既然安卓系统可以在多种设备上运行,是否可以在清单中加入应用运行需要多少内存的要求,这样内存太小的人就无法安装?

  • 那么,这样解码并将其发送到OpenGL就足够了,还是在发送到OpenGL时我还应该设置一些特殊的东西?

  • 有没有这样的命令来获取位图的部分内容?

  • 我的意思是,我能否只对文件的部分内容进行解码,将其存储在位图中,而不是所有的位图?

  • 所以我唯一能做的就是在开始时加载所有的东西,然后全部使用?这怎么可能呢?游戏是如何处理的?我的意思是,他们确实显示了一个又一个的阶段,甚至有一些看起来像OpenGL生成的进度条。这是很有问题的。

  • 我所描述的东西在网络上也是可用的。例如,网页不是加载多个微小的图像,而是包含一个单一的图像,并映射它的哪一部分应该显示在哪里。它的另一个名字是sprites.例子here .

  • 我明白了。我想我也应该在看到不再使用时调用glDeleteTextures,对吗?

  • 我怎么做呢?我应该在代码中改变什么?

  • 当我使用大量的内存时会发生什么?当没有空闲内存时,是否有虚拟内存(也许使用内部存储)这种东西?我在谷歌IO视频上听说过,但似乎他们对答案也不确定。链接here.他们只是说,当这种事情发生时,要期待更多的崩溃。

  • 这可能有用。你的意思是,我建立一个新的线程来加载位图,然后将结果送回OpenGL线程,后者将把纹理绑定到位图上?也许我可以使用类似于asyncTask的方法,并使用publishprogress。

  • 这也是一件好事。你有什么链接可以让我阅读吗?或者是在我的代码中修改一个片段?

  • 所以我认为使用ETC1是最简单的事情。然而,它根本不支持透明度,所以根据this link我需要使用另一种纹理,但我找不到这方面的样本。

  • 我可以假设我可以使用的可用内存是多少?例如,我可以假设所有的安卓设备都能为我提供200MB的视频内存,我可以使用OpenGL吗?

  • android
    opengl-es
    bitmap
    textures
    android developer
    android developer
    发布于 2012-09-03
    3 个回答
    Majid Max
    Majid Max
    发布于 2012-09-03
    已采纳
    0 人赞同

    如果我通过使用RGB_565格式,将位图解码为没有透明像素,会不会提高加载图像到OpenGL(或解码阶段)的速度?

    是的,如果你不需要透明度(阿尔法),就使用没有阿尔法的格式,更小的格式如(RGB565)意味着更小的纹理尺寸,这当然会加快解码和加载到opengl的速度。

    输入的位图的宽度和高度是否必须是2的幂,或者我可以让OpenGL只取它想要的部分,这将有这个规则?我是说,也许我可以让texImage2D命令只取位图的一部分?

    绝对是的,如果你加载一个非2次方的宽度/高度的纹理,opengl将分配一个2次方的纹理(513/513纹理将变成1024/1024),这意味着你在浪费vram内存。而且你不能命令opengl取一部分图像,因为 "teximage2d "取加载图像的宽度/高度,指定任何其他值将给你一个损坏的图像(如果不是崩溃的应用程序)。

    也许我甚至可以从头开始只解码这一部分(所以如果我有一个10000x10000的巨大图像,我可以只解码其中1024x1024的部分,并将其交给OpenGL)?

    no comment.

    是否有可能只加载并显示第一张图片,而在后台加载其他的图片?我听说你不能在一个不是处理它的线程中向OpenGL发送位图。在GlRenderer的onDrawFrame方法上加载它们,比如说,在它第二次被调用时,这样做有意义吗?

    我不确定,已经有一个基于opengl的多线程渲染引擎了。你可以在发出渲染命令的同时加载一个纹理(来自其他线程)(至少在本地C/C++中是这样,不确定java)。

    我记得我听说过一个小窍门,就是把多个图像合并成一个图像(一个接一个,从左到右),这样在解码阶段可能会有一些速度提升。不知道这个技术的名字是什么。对于安卓上的OpenGL ES来说,这还可能吗?

    不知道你在这里是什么意思

    是否有可能避免创建一个Bitmap实例,而让解码以更原生的方式完成(NDK,也许)?根据我的测试,在模拟器上解码一个1024x1024大小的PNG文件大约需要400ms-700ms,然而将其发送到OpenGL大约需要50ms-70ms。

    这是最好的方法,本地代码(C/C++)总是更好的。

    在使用Procrank时,我发现OpenGL会占用大量的内存。有没有可能让它使用更少的内存?也许使用更好的纹理类型?

    (如果你指的是vram)是的,使用纹理压缩格式,如ETC1,PVRTC,ATC总是一个好主意,它占用更少的vram内存,产生更好的性能。

    既然安卓系统可以在多种设备上运行,是否可以在清单中加入应用运行需要多少内存的要求,这样内存太小的人就无法安装?

    考虑改用后备解决方案(甚至更小的纹理),而不是砍掉低端设备。

    EDIT:

    4. 你在一个单独的线程中开始加载内容(图片),向渲染线程显示加载过程的百分比,这可以用来为加载过程画一个进度条。

    5. 这个技巧叫做 "纹理图集",它是用来加快渲染速度的,而不是用来加载的。

    6.在将解码后的图像加载到opengl纹理中后,你可以自由地删除解码后的图像(从系统内存中),然后再加载下一个。在你使用完opengl纹理后,你可以删除它(从视频内存中)。

    7. 纹理压缩是硬件特定的opengl扩展,所以你必须检查纹理压缩扩展是否存在,然后用 "glCompressedTexImage2D "函数而不是 "texImage2D "来使用它。 PVRTC用于PowerVR GPU。 用于AMD GPU的ATC。 用于Mali GPU的ASTC。 ETC1是一种标准的纹理格式(在安卓2.2以上版本中支持)。

    8.当你完成了对opengl纹理的加载后,这个图像就不再需要了,所以你必须释放内存中不需要的内容(图像)。

    EDIT2:

    5.. http://http.download.nvidia.com/developer/NVTextureSuite/Atlas_Tools/Texture_Atlas_Whitepaper.pdf

    7. 在你使用opengl中的 "glCompressedTexImage2D "加载了两个ETC纹理(例如:第一个纹理保存颜色,第二个纹理保存红色通道的alpha)后(比方说textureId1, textureId2),将这两个纹理绑定在两个纹理单元中。

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureId1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, textureId2);
    

    然后用以下碎片着色器创建一个着色器程序。

    uniform sampler2D     sTexture1;
    uniform sampler2D     sTexture2;
    varying mediump vec2  vTexCoord;
    void main()
        gl_FragColor.rgb = texture2D(sTexture1, vTexCoord).rgb;
        gl_FragColor.a   = texture2D(sTexture2, vTexCoord).r;
    

    最后,将两个着色器纹理采样器绑定到两个纹理单元(指向两个ETC纹理)。

    GLint texture1Sampler = glGetUniformLocation(programObject, "sTexture1");
    GLint texture2Sampler = glGetUniformLocation(programObject, "sTexture2");
    glUniform1i(texture1Sampler, GL_TEXTURE0);
    glUniform1i(texture2Sampler, GL_TEXTURE1);
    

    8......你不能假设任何事情,你不断分配纹理和缓冲区(根据需要),直到你得到GL_OUT_OF_MEMORY,然后你必须释放其他未使用的资源(纹理,缓冲区,......)。

    谢谢你的回答。请看我更新的问题,对你的回答进行评论。
    @马吉德。ASTC还不能用在任何东西上。 对于基于马里的系统,我想你需要使用ETC,它也是 exposed on some other systems (depending on the vendor)
    David Knight
    David Knight
    发布于 2012-09-03
    0 人赞同

    如果你还在寻找一个使用OpenGL将位图渲染到InputSurface的例子。

    我能够让这个东西发挥作用。
    看看我在这里的答案。

    https://stackoverflow.com/a/49331192/7602598

    https://stackoverflow.com/a/49331352/7602598