防止“闪烁”当调用Drawable.draw()时
我有一个小实验应用程序(本质上是 非常精简版本Android SDK 中的“nofollow">LunarLander 演示),带有单个 SurfaceView
。我有一个 Drawable
“精灵”,我定期将其绘制到不同位置的 SurfaceView
的 Canvas
对象中,而不尝试擦除之前的图像。因此:
private class MyThread extends Thread {
SurfaceHolder holder; // Initialised in ctor (acquired via getHolder())
Drawable sprite; // Initialised in ctor
Rect bounds; // Initialised in ctor
...
@Override
public void run() {
while (true) {
Canvas c = holder.lockCanvas();
synchronized (bounds) {
sprite.setBounds(bounds);
}
sprite.draw(c);
holder.unlockCanvasAndPost(c);
}
}
/**
* Periodically called from activity thread
*/
public void updatePos(int dx, int dy) {
synchronized (bounds) {
bounds.offset(dx, dy);
}
}
}
在模拟器中运行时,我看到的是,在发生几次更新后,图像的几个旧“副本”开始闪烁,即出现和消失。我最初认为我可能误解了 Canvas 的语义,并且它以某种方式维护了“层”,并且我正在将它殴打致死。然而,我后来发现,只有当我尝试更新速度快于大约每 200 毫秒时,才能获得这种效果。所以我的下一个最佳理论是,这可能是模拟器无法跟上并撕裂显示器的产物。 (我还没有可以测试的物理设备。)
这些理论是否正确?
注意:我实际上不想在实践中这样做(即绘制同一事物的数百个重叠副本)。但是,我想了解为什么会发生这种情况。
环境:
- Windows 7
- JDK 6
- Android SDK Tools r9
- 应用程序上的 Eclipse 3.6.1 (Helios) 面向 Android 2.3.1
切线问题:
我的run()
方法本质上是LunarLander示例如何工作的精简版本(删除了所有多余的逻辑)。我不太明白为什么这不会使 CPU 饱和,因为似乎没有什么可以阻止它全速运行。谁能澄清这一点吗?
I have a little experimentation app (essentially a very cut-down version of the LunarLander demo in the Android SDK), with a single SurfaceView
. I have a Drawable
"sprite" which I periodically draw into the SurfaceView
's Canvas
object in different locations, without attempting to erase the previous image. Thus:
private class MyThread extends Thread {
SurfaceHolder holder; // Initialised in ctor (acquired via getHolder())
Drawable sprite; // Initialised in ctor
Rect bounds; // Initialised in ctor
...
@Override
public void run() {
while (true) {
Canvas c = holder.lockCanvas();
synchronized (bounds) {
sprite.setBounds(bounds);
}
sprite.draw(c);
holder.unlockCanvasAndPost(c);
}
}
/**
* Periodically called from activity thread
*/
public void updatePos(int dx, int dy) {
synchronized (bounds) {
bounds.offset(dx, dy);
}
}
}
Running in the emulator, what I'm seeing is that after a few updates have occurred, several old "copies" of the image begin to flicker, i.e. appearing and disappearing. I initially assumed that perhaps I was misunderstanding the semantics of a Canvas
, and that it somehow maintains "layers", and that I was thrashing it to death. However, I then discovered that I only get this effect if I try to update faster than roughly every 200 ms. So my next best theory is that this is perhaps an artifact of the emulator not being able to keep up, and tearing the display. (I don't have a physical device to test on, yet.)
Is either of these theories correct?
Note: I don't actually want to do this in practice (i.e. draw hundreds of overlaid copies of the same thing). However, I would like to understand why this is happening.
Environment:
- Eclipse 3.6.1 (Helios) on Windows 7
- JDK 6
- Android SDK Tools r9
- App is targetting Android 2.3.1
Tangential question:
My run()
method is essentially a stripped-down version of how the LunarLander example works (with all the excess logic removed). I don't quite understand why this isn't going to saturate the CPU, as there seems to be nothing to prevent it running at full pelt. Can anyone clarify this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好吧,我已经用与你类似的方式屠杀了月球着陆器,并且看到了闪烁,我可以告诉你,你所看到的是每个
Surface
都有的双缓冲机制的简单产物。当您在附加到
Surface
的Canvas
上绘制任何内容时,您正在绘制“后”缓冲区(不可见的缓冲区)。当您unlockCanvasAndPost()
时,您正在交换缓冲区...您绘制的内容突然变得可见,因为“后”缓冲区变成了“前”缓冲区,反之亦然。因此,您的下一帧绘制是在旧的“前”缓冲区中完成的...重点是您始终绘制到交替帧上的单独缓冲区。我猜图形架构中有一个隐含的假设,即您总是将写入每个像素。
理解了这一点后,我认为真正的问题是为什么它在硬件上不闪烁?过去几年我一直致力于图形驱动程序,我可以猜测其中的原因,但不愿推测得太过分。希望以上内容足以满足您对此渲染制品的好奇。 :-)
Ok, I've butchered Lunar Lander in a similar way to you, and having seen the flickering I can tell you that what you are seeing is a simple artefact of the double-buffering mechanism that every
Surface
has.When you draw anything on a
Canvas
attached to aSurface
, you are drawing to the 'back' buffer (the invisible one). And when youunlockCanvasAndPost()
you are swapping the buffers over... what you drew suddenly becomes visible as the "back" buffer becomes the "front", and vice versa. And so your next frame of drawing is done to the old "front" buffer...The point is that you always draw to seperate buffers on alternate frames. I guess there's an implicit assumption in graphics architecture that you're always going to be writing every pixel.
Having understood this, I think the real question is why doesn't it flicker on hardware? Having worked on graphics drivers in years gone by, I can guess at the reasons but hesitate to speculate too far. Hopefully the above will be sufficient to satisfy your curiousity about this rendering artefact. :-)
您需要清除精灵的先前位置以及新位置。这是 View 系统自动执行的操作。但是,如果您直接使用 Surface 并且不重绘每个像素(使用不透明颜色或使用 SRC 混合模式),则必须自行清除缓冲区的内容。请注意,您可以将脏矩形传递给 lockCanvas(),它会为您将前一个脏矩形与您传递的脏矩形进行并集(这是 UI 工具包使用的机制。)它还会设置剪辑矩形画布的大小是这两个矩形的并集。
至于你的第二个问题,unlockAndPost() 将执行垂直同步,因此你的绘制速度永远不会超过 60fps(我见过的大多数设备的显示刷新率设置为 55Hz 左右。)
You need to clear the previous position of the sprite, as well as the new position. This is what the View system does automatically. However, if you use a Surface directly and do not redraw every pixel (either with an opaque color or using a SRC blending mode) you must clear the content of the buffer yourself. Note that you can pass a dirty rectangle to lockCanvas() and it will do the union for you of the previous dirty rectangle and the one you are passing (this is the mechanism used by the UI toolkit.) It will also set the clip rect of the Canvas to be the union of these two rectangles.
As for your second question, unlockAndPost() will do a vsync, so you will never draw at more than ~60fps (most devices that I've seen have a display refresh rate set around 55Hz.)