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

时间:2012-09-03 08:50:54

标签: android opengl-es bitmap textures

我对使用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个。

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

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

  2. 输入位图的宽度和高度是否为2的幂,或者我可以让OpenGL只采用它想要的部分,这将具有此规则吗?我的意思是,也许我可以让texImage2D命令只占位图的一部分?

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

  4. 是否可以加载并仅显示第一张图像,在后台加载其余图像?我听说你不能在一个不是处理它的线程中将位图发送到OpenGL。是否有必要在onDrawFrame的{​​{1}}方法加载它们,比如第二次被调用它?

  5. 我记得我听说过将多个图像合并为一个图像(一个接一个,从左到右)的提示,这样它就可以为解码阶段提供一些速度提升。不确定这项技术的名称是什么。 Android上的OpenGL ES仍然可以吗?

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

  7. 使用Procrank,我发现OpenGL可以占用大量内存。是否可以减少使用内存?也许使用更好的纹理类型?

  8. 因为Android可以在多种设备上运行,是否可以在清单中加入运行应用程序需要多少内存的要求,以便内存太少的人无法使用安装吗?


  9. @Majid Max:

    1. 因此以这种方式解码并将其发送给OpenGL就足够了,还是我在发送到openGL时还应该设置一些特殊内容?

    2. 没有这样的命令来占用位图的一部分?

    3. 我的意思是,我只能解码存储在位图中的部分文件,而不是所有的位图吗?

    4. 所以我唯一可以做的就是在开始时加载所有东西,然后全部使用它?怎么会这样?游戏如何处理它?我的意思是,它们确实显示了一个接一个的阶段,即使是看起来像OpenGL生成的进度条的东西。这是非常有问题的。

    5. 我所描述的内容也可以在网上找到。例如,网页不是加载多个微小图像,而是包含一个图像,并映射应该在哪里显示的图像。它的另一个名称是sprites。示例here

    6. 我明白了。当我看到不再使用时,我想我也应该调用glDeleteTextures,对吧?

    7. 我该怎么办?我应该在代码中更改什么?

    8. 当我使用大量内存时会发生什么?当没有空闲RAM时,是否存在虚拟RAM(可能使用内部存储器)?我在Google IO视频上听说过它,但似乎他们也不确定答案。链接here。他们只是说在发生这种事情时会发生更多的崩溃。


    9. @Majid Max:

      1 + 2 + 3。 ?

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

      2. 这也是一件好事。你有任何我应该阅读的链接吗?或者可能是我的代码中更改的片段?

      3. 感谢。

      4. 所以我认为使用ETC1最简单。但它根本不支持透明度,所以根据this link,我需要使用另一个纹理,但是我找不到这样的样本。

      5. 我可以假设可用的内存是什么?例如,我可以假设所有Android设备都可以为我提供200MB的可用于OpenGL的视频内存吗?

3 个答案:

答案 0 :(得分:5)

  1. 是的,如果您不需要透明度(alpha),请使用不带alpha的格式,较小的格式(RGB565)意味着较小的纹理,这肯定会加快解码并加载到opengl。

  2. 肯定是的,如果你加载一个非宽度/高度2的纹理,opengl将分配一个纹理,下一个2的幂(513/513纹理将变成1024/1024),这意味着你正在浪费vram记忆。并且你不能命令opengl获取图像的一部分,因为“teximage2d”获取加载图像的宽度/高度,并指定任何其他值将给你一个损坏的图像(如果不破坏应用程序)。

  3. 没有评论。

  4. 我不确定,已经有一个基于opengl的多线程渲染引擎。并且您可以在发出渲染命令时加载纹理(来自其他线程)(至少在本机C / C ++中,不确定java)。和NO,不要试图在渲染循环中加载纹理,这是不好的做法。

  5. 不确定你的意思

  6. 这是最好的方法,本机代码(C / C ++)总是更好

  7. (如果你的意思是vram)是的,使用像ETC1,PVRTC,ATC这样的纹理压缩格式总是一个好主意,它需要更少的vram内存并产生更好的性能。

  8. 考虑切换到后备解决方案(甚至更小的纹理),而不是切断低端设备。

  9. 编辑:

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

    5 ..这个技巧称为“纹理图集”,它用于加速渲染而不是加载。

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

    7 ..纹理压缩是特定于硬件的opengl扩展,因此您必须检查纹理压缩扩展是否存在,然后使用“glCompressedTexImage2D”函数而不是“texImage2D”。         PVRTC for PowerVR GPU;         适用于AMD GPU的ATC;         适用于Mali GPU的ASTC;         ETC1是一种标准纹理格式(在Android 2.2 +中支持)。

    8 ..当您将图像加载到opengl纹理时,不再需要图像。所以你必须从不需要的内容(图像)中释放ram。

    EDIT2:

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

    7 ..在opengl中使用“glCompressedTexImage2D”(比如textureId1,textureId2)加载两个ETC纹理(例如:第一个纹理保持颜色,第二个存储区域在红色通道中)后,绑定两个两个纹理单元中的纹理:

    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,而不是你必须释放其他未使用的资源(纹理,缓冲区......)。

答案 1 :(得分:0)

如果您仍在寻找使用OpenGL将位图渲染到InputSurface的示例。

我能够让它发挥作用 看看我的答案。

  

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

     

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

     

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

答案 2 :(得分:-1)

我认为你应该看看http://www.andengine.org/它会为你节省大量的时间和工作......