glClear()需要太长时间 - Android OpenGL ES 2

时间:2015-04-10 00:49:40

标签: android graphics opengl-es timing

我正在使用OpenGL ES 2开发Android应用程序。我遇到的问题是glClear()函数需要花费很长时间来处理,因为帧会延迟,因此游戏会显得紧张。 具有定时探测器的程序运行的输出显示,虽然设置来自地图集的所有顶点和图像仅需要不到1毫秒,glClear()需要10到20毫秒。事实上,清算通常占总渲染时间的95%。 我的代码基于常见教程,Render函数是这样的:

private void Render(float[] m, short[] indices) {
    Log.d("time", "--START RENDER--");

    // get handle to vertex shader's vPosition member
    int mPositionHandle = GLES20.glGetAttribLocation(riGraphicTools.sp_Image, "vPosition");

    // Enable generic vertex attribute array
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, 3,
    GLES20.GL_FLOAT, true,
    0, vertexBuffer);

    // Get handle to texture coordinates location
    int mTexCoordLoc = GLES20.glGetAttribLocation(riGraphicTools.sp_Image, "a_texCoord" );

    // Enable generic vertex attribute array
    GLES20.glEnableVertexAttribArray ( mTexCoordLoc );

    // Prepare the texturecoordinates
    GLES20.glVertexAttribPointer ( mTexCoordLoc, 2, GLES20.GL_FLOAT,
    false, 
    0, uvBuffer);

    // Get handle to shape's transformation matrix
    int mtrxhandle = GLES20.glGetUniformLocation(riGraphicTools.sp_Image, "uMVPMatrix");

    // Apply the projection and view transformation
    GLES20.glUniformMatrix4fv(mtrxhandle, 1, false, m, 0);

    // Get handle to textures locations
    int mSamplerLoc = GLES20.glGetUniformLocation (riGraphicTools.sp_Image, "s_texture" );

    // Set the sampler texture unit to 0, where we have saved the texture.
    GLES20.glUniform1i ( mSamplerLoc, 0);

    long clearTime = System.nanoTime();
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    Log.d("time", "Clear time is " + (System.nanoTime() - clearTime));

    // Draw the triangles
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length,
    GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
    GLES20.glDisableVertexAttribArray(mTexCoordLoc);

    Log.d("time", "--END RENDER--");
}

我尝试了moving the png atlas to /drawable-nodpi,但没有效果。

我也尝试过使用glFlush()glFinish()功能。 有趣的是,如果我不调用glClear(),则必须自动调用它。这是因为总渲染时间仍然与调用时一样高,并且屏幕上没有前一帧的残余。只有第一次调用glClear()非常耗时。如果再次调用它,后续调用只有1或2毫秒。

我还尝试了不同的参数组合(例如GLES20.GL_DEPTH_BUFFER_BIT)和glClearColor()。晴朗的时间仍然很高。

提前谢谢。

1 个答案:

答案 0 :(得分:8)

你没有衡量自己的想法。测量OpenGL API调用的耗用时间大多没有意义。

异步性

要理解的关键方面是OpenGL是一种将工作传递给GPU的API。最简单的心理模型(在很大程度上与现实相对应)是,当您进行OpenGL API调用时,您会将稍后提交给GPU的工作排队。例如,如果您进行glDraw*()调用,请记录构建排队的工作项的调用,稍后将提交给GPU执行。

换句话说,API是高度异步的。通过API调用请求的工作在调用返回时尚未完成。在大多数情况下,它甚至都没有提交给GPU执行。它只排队等候,稍后会提交,大部分时间都在您的控制之外。

这种一般方法的结果是,您进行glClear()调用的时间与清除帧缓冲所需的时间几乎无关。

同步

现在我们已经确定了OpenGL API是如何异步的,下一个要理解的概念是需要一定程度的同步。

让我们看一下整体吞吐量受GPU限制的工作负载(通过GPU性能,或者因为帧速率受显示器刷新限制)。如果我们保持整个系统完全异步,并且CPU可以比GPU处理它们更快地生成GPU命令,那么我们将排队逐渐增加的工作量。出于以下几个原因,这是不可取的:

  • 在极端情况下,排队的工作量会增加到无穷大,我们只会从存储排队的GPU命令中耗尽内存。
  • 在需要响应用户输入的应用中,比如游戏,我们会增加用户输入和呈现之间的延迟。

为避免这种情况,驱动程序使用限制机制来防止CPU过远。如何处理这个问题的细节可能相当复杂。但作为一个简单的模型,它可能就像在GPU完成渲染之前超过1-2帧时阻塞CPU。理想情况下,您总是希望排队一些工作,以便GPU永远不会为图形有限的应用程序闲置,但您希望尽可能减少排队的工作量,以最大限度地减少内存使用和延迟。

您的测量意义

通过解释所有这些背景信息,您的测量结果应该不那么令人惊讶。到目前为止,最有可能的情况是您的glClear()调用会触发同步,您测量的时间是GPU充分赶上的时间,直到提交更多工作是有意义的。

请注意,这并不意味着所有以前提交的作品需要完成。让我们看一下有点假设的序列,但要足够现实,以说明会发生什么:

  • 我们假设你进行glClear()调用,形成渲染帧n的开头。
  • 此时,显示屏上显示第n - 3帧,GPU正忙于处理第n - 2帧的渲染命令。
  • 司机决定你真的不应该超过2帧。因此,它会阻止您的glClear()调用,直到GPU完成帧n - 2的渲染命令。
  • 它也可能决定需要等到显示屏上显示n - 2帧,这意味着等待下一次光束同步。
  • 现在显示框架n - 2,之前包含框架n - 3的缓冲区不再使用。现在可以将其用于框架n,这意味着现在可以提交框架glClear()的{​​{1}}命令。

请注意,虽然您的n调用在此方案中进行了各种等待,而您在API调用所花费的时间中进行了测量,但这一次都没有用于实际清除帧缓冲帧。您可能只是坐在某种信号量(或类似的同步机制)上,等待GPU完成以前提交的工作。

结论

考虑到您的测量结果并非直接有用,您可以从中学到什么?不幸的是,不是很多。

如果您确实发现您的帧速率不符合您的目标,例如因为你观察到口吃,甚至更好,因为你在一段时间内测量帧率,你唯一知道的是你的渲染速度太慢。进入性能分析的细节是一个对于这种格式来说太大的话题。只是为了简要介绍一下您可以采取的步骤:

  • 测量/分析您的CPU使用情况,以验证您是否真的受GPU限制。
  • 使用GPU供应商经常提供的GPU分析工具。
  • 简化渲染,或跳过部分渲染,并查看性能如何变化。例如,如果简化几何体,它会变得更快吗?您可能受到顶点处理的限制。如果减少帧缓冲区大小,它会变快吗?或者,如果您简化片段着色器?您可能受到片段处理的限制。