不在 Android OpenGL 上绘图时屏幕快速闪烁
Fast Screen Flicker while NOT drawing on Android OpenGL
我想节省电池使用时间。我的应用程序有时只需要绘制。
所以我在 onDraw 方法中将这段代码添加到我的渲染器中:
boolean dirty = true;
public void onDrawFrame(GL10 arg0) {
if (!dirty) return;
dirty = false;
..... draw images ....
}
所以我的应用程序只在我需要时才被绘制。但是会发生什么,如果我不在每一帧上绘制我的应用程序,它就会闪烁得非常快。看起来它会每 2. 帧左右绘制一次,而在所有其他帧中只会绘制一个黑屏。
我知道我可以将渲染模式设置为 RENDERMODE_WHEN_DIRTY
。但是我不想创建另一个线程来检查它是否脏。
我的问题是为什么会闪烁?在我进行检查之前,我没有调用任何方法或 GLES20 调用:if (!dirty) return;
并且我确信 boolean dirty
不会改变并且除了第一帧之外始终为 false。
编辑:
我将代码更改为:
int dirty = 0;
public void onDrawFrame(GL10 arg0) {
if (dirty > 1) return;
dirty++;
..... draw images ....
}
这样就可以停止闪烁了!看起来你必须至少绘制 2 次,这样你就不会看到这个奇怪的屏幕闪烁。无论如何,我现在将尝试使用更干净的方式并创建一个线程,当我想绘制一些东西时调用 requestRender()
并将我的渲染模式设置为 RENDERMODE_WHEN_DIRTY
我猜想窗口系统仍会假设您已经更新了框架,并将缓冲区放到屏幕上 - 也就是说,它怎么知道您没有绘制任何东西?最终出现在屏幕上的将是该内存缓冲区中发生的任何事情(通常是 N-2 帧之前的帧)。
使用一个单独的线程来休眠渲染,直到状态变为脏,所以您只将实际渲染过的帧发送到 OS。这样,您不仅可以节省 GPU 负载,还可以停止 CPU 线程在无所事事地轮询脏状态时浪费功率。
当 onDrawFrame()
被调用时,已经来不及决定不想画框了。无论你用那种方法画什么,都会被呈现出来。如果你不绘制任何东西,渲染目标中发生的任何事情都会被呈现。无法保证那是什么。它可能是旧框架,或随机垃圾。
您找到的将帧渲染至少两次的解决方法可能适用于您当前的系统,但我不指望它在任何地方都适用。它还会浪费电池电量。当您跳过实际渲染时,帧仍会显示。
最好的解决方案确实是使用 RENDERMODE_WHEN_DIRTY
,并在实际需要时触发更新。我不确定为什么你需要一个额外的线程。在您的程序逻辑中的某个时刻,您将更改需要重绘的 data/state。当发生这种情况时,您可以直接调用 requestRender()
,而不是设置必须由另一个线程检查的标志。
另一种选择是您致电:
eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
同时设置 context/surface。这要求在 eglSwapBuffers()
之后保留缓冲区内容。不过,它有一个很大的警告:并非所有设备都支持它。您可以测试它是否受支持:
EGLint surfType = 0;
eglGetConfigAttrib(display, config, EGL_SURFACE_TYPE, &surfType);
if (surfType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
// supported!
}
您也可以在选择配置时请求此功能。作为传递给 eglChooseConfig()
的属性的一部分,添加:
...
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
...
但同样,这在所有设备上都不支持。因此,只有当您针对特定设备时,它才是真正的选择,或者如果它不受支持,则有功能回退。
我想节省电池使用时间。我的应用程序有时只需要绘制。 所以我在 onDraw 方法中将这段代码添加到我的渲染器中:
boolean dirty = true;
public void onDrawFrame(GL10 arg0) {
if (!dirty) return;
dirty = false;
..... draw images ....
}
所以我的应用程序只在我需要时才被绘制。但是会发生什么,如果我不在每一帧上绘制我的应用程序,它就会闪烁得非常快。看起来它会每 2. 帧左右绘制一次,而在所有其他帧中只会绘制一个黑屏。
我知道我可以将渲染模式设置为 RENDERMODE_WHEN_DIRTY
。但是我不想创建另一个线程来检查它是否脏。
我的问题是为什么会闪烁?在我进行检查之前,我没有调用任何方法或 GLES20 调用:if (!dirty) return;
并且我确信 boolean dirty
不会改变并且除了第一帧之外始终为 false。
编辑:
我将代码更改为:
int dirty = 0;
public void onDrawFrame(GL10 arg0) {
if (dirty > 1) return;
dirty++;
..... draw images ....
}
这样就可以停止闪烁了!看起来你必须至少绘制 2 次,这样你就不会看到这个奇怪的屏幕闪烁。无论如何,我现在将尝试使用更干净的方式并创建一个线程,当我想绘制一些东西时调用 requestRender()
并将我的渲染模式设置为 RENDERMODE_WHEN_DIRTY
我猜想窗口系统仍会假设您已经更新了框架,并将缓冲区放到屏幕上 - 也就是说,它怎么知道您没有绘制任何东西?最终出现在屏幕上的将是该内存缓冲区中发生的任何事情(通常是 N-2 帧之前的帧)。
使用一个单独的线程来休眠渲染,直到状态变为脏,所以您只将实际渲染过的帧发送到 OS。这样,您不仅可以节省 GPU 负载,还可以停止 CPU 线程在无所事事地轮询脏状态时浪费功率。
当 onDrawFrame()
被调用时,已经来不及决定不想画框了。无论你用那种方法画什么,都会被呈现出来。如果你不绘制任何东西,渲染目标中发生的任何事情都会被呈现。无法保证那是什么。它可能是旧框架,或随机垃圾。
您找到的将帧渲染至少两次的解决方法可能适用于您当前的系统,但我不指望它在任何地方都适用。它还会浪费电池电量。当您跳过实际渲染时,帧仍会显示。
最好的解决方案确实是使用 RENDERMODE_WHEN_DIRTY
,并在实际需要时触发更新。我不确定为什么你需要一个额外的线程。在您的程序逻辑中的某个时刻,您将更改需要重绘的 data/state。当发生这种情况时,您可以直接调用 requestRender()
,而不是设置必须由另一个线程检查的标志。
另一种选择是您致电:
eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
同时设置 context/surface。这要求在 eglSwapBuffers()
之后保留缓冲区内容。不过,它有一个很大的警告:并非所有设备都支持它。您可以测试它是否受支持:
EGLint surfType = 0;
eglGetConfigAttrib(display, config, EGL_SURFACE_TYPE, &surfType);
if (surfType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
// supported!
}
您也可以在选择配置时请求此功能。作为传递给 eglChooseConfig()
的属性的一部分,添加:
...
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
...
但同样,这在所有设备上都不支持。因此,只有当您针对特定设备时,它才是真正的选择,或者如果它不受支持,则有功能回退。