本文介绍了没有glClear的Android OpenGL渲染错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm developing a drawing application where the user can select a range of brushes and paint on the screen. I'm using textures as brushes and I'm drawing vertexes as points with PointSpriteOES enabled as displayed below.

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.

The application worked just as desired, but I needed to optimize it for runtime as its framerate dropped under 30 when dealt with a lot of vertexes. Since the application's domain enables it, it seemed a good idea to leave the glClear and leave the redrawing of already existing lines as it's really unnecessary. However, this resulted in a very strange bug I couldn't fix since then. When the OpenGL is not rendering (I have set render mode to WHEN_DIRTY), only about 1/3 of all the vertexes are visible on the screen. Requesting a redraw by calling requestRender() makes these vertexes disappear and others are shown. There are three states I can tell apart, each state showing an approximate of 1/3 of all vertexes.

我已经上传了三个屏幕截图( http://postimg.org/image/d63tje56l/ http://postimg.org/image/npeds634f/)可以使您更轻松地进行操作理解.屏幕截图显示了我绘制了三行不同颜色的线条的状态(因此我无法链接所有3张图像,但我希望您能想象得到-它缺少第一和第二部分的线段).可以清楚地看到,如果我可以将屏幕合并为一个屏幕,那么我会得到理想的结果.

I have uploaded three screenshots (http://postimg.org/image/d63tje56l/, http://postimg.org/image/npeds634f/) to make it a bit easier for you to understand. The screenshots show the state where I have drawn three lines with different colors (SO didn't enable me to link all 3 images, but I hope you can imagine it - it has the segments missing from the 1st and the 2nd). It can clearly be seen that if I could merge the screens into a single one, I would get the desired result.

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

I'm only guessing what the issue is caused by since I'm not an OpenGL expert. My best shot is that OpenGL uses triple buffers and only a single buffer is shown at a given time, while other vertexes are placed on the backbuffers. I have tried forcing all buffers to be rendered as well as trying to force all vertexes to appear on all buffers, but I couldn't manage either.

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

Could you help me solve this?

推荐答案

我相信您的猜测是完全正确的. OpenGL的常用用法是,每次要求重画时,您都希望绘制一个完整的帧,包括初始的空白.如果不这样做,行为通常是不确定的.在您的情况下,肯定看起来像是在使用三重缓冲,并且您的图形分布在3个单独的表面上.

I believe your guess is exactly right. The way OpenGL is commonly used, you're expected to draw a complete frame, including an initial clear, every time you're asked to redraw. If you don't do that, behavior is generally undefined. In your case, it certainly looks like triple buffering is used, and your drawing is distributed over 3 separate surfaces.

此模型不适用于增量绘图,在这种情况下,全帧绘图非常昂贵.您可以考虑以下几种选择.

This model does not work very well for incremental drawing, where drawing a full frame is very expensive. There are a few options you can consider.

这不是直接解决方案,但总有一些值得考虑的问题.如果您找到一种使渲染更加高效的方法,则可能无需进行增量渲染.您没有显示渲染代码,因此很可能您的点太多而无法获得良好的帧率.

This is not directly a solution, but always something worth thinking about. If you can find a way to make your rendering much more efficient, there might be no need to render incrementally. You're not showing your rendering code, so it's possible that you simply have too many points to get a good framerate.

但是在任何情况下,请确保您有效地使用OpenGL.例如,将点存储在VBO中,并仅更新用glBufferSubData()更改的零件.

But in any case, make sure that you use OpenGL efficiently. For example, store your points in VBOs, and update only the parts that change with glBufferSubData().

这是最通用,最实用的解决方案.与其直接绘制到主帧缓冲区,不如使用帧缓冲区对象(FBO)渲染到纹理.您将所有图形都制作到该FBO,并在需要重绘时将其复制到主帧缓冲区.

This is the most generic and practical solution. Instead of drawing directly to the primary framebuffer, use a Frame Buffer Object (FBO) to render to a texture. You do all of your drawing to this FBO, and copy it to the primary framebuffer when it's time to redraw.

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

For copying from FBO to the primary framebuffer, you will need a simple pair of vertex/fragment shaders in ES 2.0. In ES 3.0 and later, you can use glBlitFramebuffer().

优点:

  • 仅使用标准ES 2.0功能即可在任何设备上工作.
  • 易于实施.

缺点:

  • 每次重新绘制时都需要帧缓冲区的副本.

EGL是将OpenGL连接到Android中的窗口系统的基础API,它确实具有创建单个缓冲曲面的属性.尽管很少建议使用单缓冲渲染,但是您的用例是仍然可以考虑使用的少数情况之一.

EGL, which is the underlying API to connect OpenGL to the window system in Android, does have attributes to create single buffered surfaces. While single buffered rendering is rarely advisable, your use case is one of the few where it could still be considered.

尽管API定义存在,但文档将支持指定为可选:

While the API definition exists, the documentation specifies support as optional:

我从来没有尝试过这种方法,所以我不知道这种支持的广泛性,或者它是否在Android上完全受支持.下面概述了如果要尝试可以如何实现它:

I have never tried this myself, so I have no idea how widespread support is, or if it's supported on Android at all. The following sketches how it could be implemented if you want to try it out:

如果从GLSurfaceView派生用于OpenGL渲染,则需要提供自己的EGLWindowSurfaceFactory,它看起来像这样:

If you derive from GLSurfaceView for your OpenGL rendering, you need to provide your own EGLWindowSurfaceFactory, which would look something like this:

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);
    }
}

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

Then in your GLSurfaceView subclass constructor, before calling setRenderer():

setEGLWindowSurfaceFactory(new SingleBufferFactory());

优点:

缺点:

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

The EGL API allows you to specify a surface attribute that requests the buffer content to be preserved on eglSwapBuffers(). This is not available in the EGL10 interface, though. You'll have to use the EGL14 interface, which requires at least API level 17.

要设置此设置,请使用:

To set this, use:

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

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

You should be able to place this in the onSurfaceCreated() method of your GLSurfaceView.Renderer implementation.

某些设备支持此功能,而其他设备则不支持.您可以通过查询配置的EGL_SURFACE_TYPE属性来查询它是否受支持,并对照EGL_SWAP_BEHAVIOR_PRESERVED_BIT位进行检查.或者,您可以将其作为配置选择的一部分.

This is supported on some devices, but not on others. You can query if it's supported by querying the EGL_SURFACE_TYPE attribute of the config, and check it against the EGL_SWAP_BEHAVIOR_PRESERVED_BIT bit. Or you can make this part of your config selection.

优点:

缺点:

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

I would probably check for EGL_BUFFER_PRESERVE support on the specific device, and use it if it is suppported. Otherwise, go for the FBO and blit approach.

这篇关于没有glClear的Android OpenGL渲染错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!