没有glClear的Android OpenGL渲染bug?

时间:2014-12-05 16:38:32

标签: android opengl-es

我正在开发一个绘图应用程序,用户可以在屏幕上选择一系列画笔和绘画。我正在使用纹理作为画笔,我将顶点绘制为点,并启用了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/),让您更容易理解。屏幕截图显示了我用不同颜色绘制三条线的状态(SO不能让我链接所有3个图像,但我希望你能想象它 - 它在第1和第2个区域缺少部分)。可以清楚地看到,如果我可以将屏幕合并为一个屏幕,我会得到所需的结果。

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

你能帮我解决一下吗?

1 个答案:

答案 0 :(得分:1)

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

此模型对增量绘图效果不佳,绘制全帧非常昂贵。您可以考虑几个选项。

优化您的绘图

这不是一个直接的解决方案,但总是值得思考的事情。如果您可以找到一种方法来提高渲染效率,则可能无需逐步渲染。您没有显示渲染代码,因此您可能只是有太多的分数来获得良好的帧速率。

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

绘制到FBO,然后blit

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

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

优点:

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

缺点:

  • 每次重绘时都需要一份framebuffer。

单缓冲

EGL是将OpenGL连接到Android窗口系统的底层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);

您应该可以将其放在onSurfaceCreated()实施的GLSurfaceView.Renderer方法中。

某些设备支持此功能,但其他设备不支持。您可以通过查询配置的EGL_SURFACE_TYPE属性来查询它是否受支持,并根据EGL_SWAP_BEHAVIOR_PRESERVED_BIT位进行检查。或者你可以选择配置这部分。

优点:

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

缺点:

  • 仅支持设备子集。

结论

我可能会检查特定设备上的EGL_BUFFER_PRESERVE支持,并在支持时使用它。否则,请选择FBO和blit方法。