【问题标题】:Android OpenGL rendering bug without glClear?没有glClear的Android OpenGL渲染错误?
【发布时间】:2014-12-05 16:38:32
【问题描述】:

我正在开发一个绘图应用程序,用户可以在其中选择一系列画笔并在屏幕上绘画。我将纹理用作画笔,并将顶点绘制为启用 PointSpriteOES 的点,如下所示。

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnable(GL11.GL_POINT_SPRITE_OES);
gl.glTexEnvf(GL11.GL_POINT_SPRITE_OES, GL11.GL_COORD_REPLACE_OES, GL10.GL_TRUE);

该应用程序按预期工作,但我需要针对运行时优化它,因为在处理大量顶点时它的帧率下降到 30 以下。由于应用程序的域启用了它,因此离开 glClear 并保留已经存在的线条的重绘似乎是个好主意,因为它确实没有必要。但是,这导致了一个非常奇怪的错误,从那时起我就无法修复。当 OpenGL 不渲染时(我将渲染模式设置为WHEN_DIRTY),屏幕上只有大约 1/3 的顶点可见。通过调用requestRender() 请求重绘会使这些顶点消失并显示其他顶点。我可以区分三种状态,每种状态大约显示所有顶点的 1/3。

我上传了三张截图(http://postimg.org/image/d63tje56l/http://postimg.org/image/npeds634f/),方便大家理解。屏幕截图显示了我用不同颜色绘制了三条线的状态(所以我无法链接所有 3 张图像,但我希望你能想象它 - 它缺少第 1 和第 2 的片段)。可以清楚地看到,如果我可以将屏幕合并为一个,我会得到想要的结果。

我只是猜测问题是由什么引起的,因为我不是 OpenGL 专家。我最好的方法是 OpenGL 使用三重缓冲区,并且在给定时间只显示一个缓冲区,而其他顶点放置在后缓冲区上。我曾尝试强制渲染所有缓冲区以及强制所有顶点出现在所有缓冲区上,但我也无法管理。

你能帮我解决这个问题吗?

【问题讨论】:

    标签: android opengl-es


    【解决方案1】:

    我相信你的猜测是完全正确的。 OpenGL 的常用方式是,每次要求您重绘时,您都需要绘制一个完整的帧,包括初始清除。如果你不这样做,行为通常是不确定的。在您的情况下,看起来确实使用了三重缓冲,并且您的绘图分布在 3 个单独的表面上。

    此模型不适用于增量绘图,因为绘制全帧非常昂贵。您可以考虑几个选项。

    优化您的绘图

    这不是直接的解决方案,但总是值得考虑的事情。如果您能找到一种方法来提高渲染效率,则可能不需要增量渲染。你没有显示你的渲染代码,所以你可能只是有太多的点来获得一个好的帧率。

    但无论如何,请确保您有效地使用 OpenGL。例如,将您的积分存储在 VBO 中,并仅更新更改为 glBufferSubData() 的部分。

    绘制到 FBO,然后 blit

    这是最通用和实用的解决方案。不是直接绘制到主帧缓冲区,而是使用帧缓冲区对象 (FBO) 渲染到纹理。您在此 FBO 上完成所有绘图,并在需要重绘时将其复制到主帧缓冲区。

    为了从 FBO 复制到主帧缓冲区,您将需要 ES 2.0 中的一对简单的顶点/片段着色器。在 ES 3.0 及更高版本中,您可以使用glBlitFramebuffer()

    优点:

    • 适用于任何设备,仅使用标准 ES 2.0 功能。
    • 易于实施。

    缺点:

    • 每次重绘时都需要一份帧缓冲区。

    单缓冲

    EGL 是在 Android 中将 OpenGL 连接到窗口系统的底层 API,它确实具有创建单个缓冲表面的属性。虽然很少建议使用单缓冲渲染,但您的用例是少数仍然可以考虑的用例之一。

    虽然 API 定义存在,但 documentation 将支持指定为可选:

    客户端 API 可能无法遵守请求的呈现缓冲区。要确定上下文渲染到的实际缓冲区,请调用 eglQueryContext。

    我自己从未尝试过这个,所以我不知道支持有多广泛,或者它是否在 Android 上受支持。如果您想尝试一下,以下概述了如何实现它:

    如果您从 GLSurfaceView 派生用于 OpenGL 渲染,则需要提供自己的 EGLWindowSurfaceFactory,如下所示:

    class SingleBufferFactory implements GLSurfaceView.EGLWindowSurfaceFactory {
        public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
                                              EGLConfig config, Object nativeWindow) {
            int[] attribs = {EGL10.EGL_RENDER_BUFFER, EGL10.EGL_SINGLE_BUFFER,
                             EGL10.EGL_NONE};
            return egl.eglCreateWindowSurface(display, config, nativeWindow, attribs);
        }
    
        public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
            egl.eglDestroySurface(display, surface);
        }
    }
    

    然后在您的GLSurfaceView 子类构造函数中,在调用setRenderer() 之前:

    setEGLWindowSurfaceFactory(new SingleBufferFactory());
    

    优点:

    • 可以直接绘制到主帧缓冲区,无需副本。

    缺点:

    • 部分或所有设备可能不支持。
    • 单缓冲渲染可能效率低下。

    使用 EGL_BUFFER_PRESERVE

    EGL API 允许您指定请求缓冲区内容保留在eglSwapBuffers() 上的表面属性。不过,这在EGL10 界面中不可用。您必须使用EGL14 接口,该接口至少需要 API 级别 17。

    要设置它,请使用:

    EGL14.eglSurfaceAttrib(EGL14.eglGetCurrentDisplay(), EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW),
                           EGL14.EGL_SWAP_BEHAVIOR, EGL14.EGL_BUFFER_PRESERVED);
    

    您应该能够将它放在您的GLSurfaceView.Renderer 实现的onSurfaceCreated() 方法中。

    这在某些设备上受支持,但在其他设备上不支持。您可以通过查询配置的EGL_SURFACE_TYPE 属性来查询它是否支持,并根据EGL_SWAP_BEHAVIOR_PRESERVED_BIT 位检查它。或者您可以将这部分作为您的配置选择。

    优点:

    • 可以直接绘制到主帧缓冲区,无需副本。
    • 仍然可以使用双/三缓冲渲染。

    缺点:

    • 仅在部分设备上受支持。

    结论

    我可能会检查特定设备上的EGL_BUFFER_PRESERVE 支持,如果支持就使用它。否则,请使用 FBO 和 blit 方法。

    【讨论】:

      猜你喜欢
      • 2015-09-29
      • 2013-04-16
      • 2011-12-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-29
      • 2020-05-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多