Why is glClear blocking in OpenGLES?

2020-08-26 02:05发布

问题:

I'm trying to profile my renderer, and I'm seeing some weird profiling behavior that I can't explain.

I'm using a glSurfaceView, which I have set to render continuously.

This is how my onDrawFrame() is structured

public void onDrawFrame(GL10 unused) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    executeAllDrawCommands();
}

This was behaving slowly under light load, so I created a timer class and started to profile this some. I was quite surprised by what I saw.

I put some probes on my onDrawFrame method like so:

public void onDrawFrame(GL10 unused) {
   swapTimer.end();

   clearTimer.start();
     GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
   clearTimer.end();

   drawTimer.start();
     executeAllDrawCommands();
   drawTimer.end();

   swapTimer.start();
}

clearTimer measures the time it takes to call glClear, drawTimer measures the time it takes to run all my draw calls, and swapTimer measures the time from when onDrawFrame exits and when it returns (the time taken to call eglSwapBuffers).

When I ran a very lightly loaded scene, I got some really strange numbers I can't explain:

swapTimer  : 20ms (average)
clearTimer : 11ms (average)
drawTimer  :  2ms (average)

I expected the swap time to be somewhat largish, as I believe the device has vsync forced enable at ~30fps, though I don't know why the actual 'clear' call is blocking for 11 milliseconds? I thought it was just supposed to issue an asynchronous command and return?

When I draw a much more busy scene, the numbers change quite a bit:

swapTimer  :  2ms (average)
clearTimer :  0ms (average)
drawTimer  : 44ms (average)

In this scene my draw calls are taking so much time that it looks like its hiding a lot of the vsync period, and the block on the clear call totally goes away.

Is there any explanation for why glClear is blocking on my lightly loaded scene?

Link to my 'Timer' class source code in case someone is suspicious of my measuring technique: http://pastebin.com/bhXt368W

回答1:

I put a glFinish (and finishTimer.start()/end() around it), and it takes all the time away from glClear. Instead now glFinish takes some number of milliseconds, and glClear becomes instant.

That explains it.

When your scene is very light and the drawings are rendered very fast, the time to clear and fill the pixels with the new color will take some time (it will always take time, otherwise the renderer is behind and is currenty drawing new stuff). The newer Android devices have fillrate limits. For example, Nexus One has a fillrate lock at 30 Hz - the screen will be synced at that frequency no matter how fast your actual drawings are going. If the drawings finishes under 30 Hz the renderer will sync up with the screen. This is why you notice this delay, which you should notice even if you remove the glClear() call. The renderer is before and faster than the screen's updates.

When the renderer have many objects to draw, the synchronization will halt (given your busy scene's profile data) because the renderer is now after the screen's updates.

When you use glFinish(), it removes the time the glClear() function otherwise would cause, which, by following the fillrate logic, means that glFinish() is now the function that is ensuring synchronization with the screen.

Calculations:

F = 1/T

Easy scene:

F = 1/T = 1/((20+11+2)*10^-3) =~ 30 Hz

The synchronization's delay time appears in your profiler. Renderer is being synchronized with the screen. That means that if you remove the glClear() or the glFinish() call, the delay would appear somewhere else.

Heavy scene:

F = 1/T = 1/((2+0+44)*10^-3)) =~ 22 Hz

The synchronization's delay time does not appear in your profiler. Renderer is after the screen's update frequency.

It seems like this is all related to vsync

That seems to be correct.