安卓游戏循环VS更新的渲染线程线程、游戏、VS

2023-09-04 10:11:23 作者:分手说什么都显得做作

我在做一个机器人游戏,我目前没有得到我想要的性能。我有一个游戏循环在自己的线程用于更新对象的位置。渲染线程会遍历这些对象,并吸引他们。目前的行为是什么似乎像波涛汹涌/不均匀的运动。我无法解释的是,之前我把更新逻辑在自己的线程,我是有在onDrawFrame方法中,GL调用之前。在这种情况下,动画是完全光滑特别是当我试图通过节流我的视频下载的更新循环,它只是变得波涛汹涌/不均匀。甚至当我允许更新线程去疯抢(无眠),动画流畅,只有当视频下载参与不会影响动画的质量。

I'm making an android game and am currently not getting the performance I'd like. I have a game loop in its own thread which updates an object's position. The rendering thread will traverse these objects and draw them. The current behavior is what seems like choppy/uneven movement. What I cannot explain is that before I put the update logic in its own thread, I had it in the onDrawFrame method, right before the gl calls. In that case, the animation was perfectly smooth, it only becomes choppy/uneven specifically when I try to throttle my update loop via Thread.sleep. Even when I allow the update thread to go berserk (no sleep), the animation is smooth, only when Thread.sleep is involved does it affect the quality of the animation.

我已经创建了一个框架项目,看看我是否能重现问题,以下是更新循环和渲染器的onDrawFrame方法: 更新循环

I've created a skeleton project to see if I could recreate the issue, below are the update loop and the onDrawFrame method in the renderer: Update Loop

    @Override
public void run() 
{
    while(gameOn) 
    {
        long currentRun = SystemClock.uptimeMillis();
        if(lastRun == 0)
        {
            lastRun = currentRun - 16;
        }
        long delta = currentRun - lastRun;
        lastRun = currentRun;

        posY += moveY*delta/20.0;

        GlobalObjects.ypos = posY;

        long rightNow = SystemClock.uptimeMillis();
        if(rightNow - currentRun < 16)
        {
            try {
                Thread.sleep(16 - (rightNow - currentRun));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

和这里是我的 onDrawFrame 方法:

        @Override
public void onDrawFrame(GL10 gl) {
    gl.glClearColor(1f, 1f, 0, 0);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
            GL10.GL_DEPTH_BUFFER_BIT);

    gl.glLoadIdentity();

    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    gl.glTranslatef(transX, GlobalObjects.ypos, transZ);
    //gl.glRotatef(45, 0, 0, 1);
    //gl.glColor4f(0, 1, 0, 0);

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

    gl.glVertexPointer(3,  GL10.GL_FLOAT, 0, vertexBuffer);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, uvBuffer);

    gl.glDrawElements(GL10.GL_TRIANGLES, drawOrder.length,
              GL10.GL_UNSIGNED_SHORT, indiceBuffer);

    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}

我已经通过副本岛上的来源望去,他做了更新逻辑在一个单独的线程,以及与视频下载节流,但他的比赛看起来很流畅。有没有人有任何意见或有没有人经历了什么,我描述?

I've looked through replica island's source and he's doing his update logic in a separate thread, as well as throttling it with Thread.sleep, but his game looks very smooth. Does anyone have any ideas or has anyone experienced what I'm describing?

---编辑:13年1月25日 - 我有一些时间去思考并理顺了这个游戏引擎增色不少。我如何管理这可能是诽谤或侮辱实际的游戏程序员,所以请随时纠正任何这些想法。

--- 1/25/13--- I've had some time to think and have smoothed out this game engine considerably. How I managed this might be blasphemous or insulting to actual game programmers, so please feel free to correct any of these ideas.

其基本思想是保持更新的图案,画...更新,画......而保持时间差相对同一个(通常是在你的控制)。 我的第一个动作当然是要以这样一种方式,它被通知其被允许这样做后,只画同步我的渲染器。这看起来是这样的:

The basic idea is to keep a pattern of update, draw... update, draw... while keeping the time delta relatively the same (often out of your control). My first course of action was to synchronize my renderer in such a way that it only drew after being notified it was allowed to do so. This looks something like this:

public void onDrawFrame(GL10 gl10) {
        synchronized(drawLock)
    {
        while(!GlobalGameObjects.getInstance().isUpdateHappened())
        {
            try
            {
                Log.d("test1", "draw locking");
                drawLock.wait();
            } 
            catch (InterruptedException e) 
            {
                e.printStackTrace();
            }
        }
    }

当我完成我的更新逻辑,我叫drawLock.notify(),释放渲染线程要画什么我刚刚更新。这样做的目的是帮助建立更新的图案,画...更新,画...等。

When I finish my update logic, I call drawLock.notify(), releasing the rendering thread to draw what I just updated. The purpose of this is to help establish the pattern of update, draw... update, draw... etc.

在我执行了,这是相当流畅的,虽然我还在经历着运动偶尔跳跃。经过一些测试,我发现我有ondrawFrame的调用之间发生的多次更新。这是使一个帧,以显示两个(或更多)的更新,更大的跳跃比正常的结果。

Once I implemented that, it was considerably smoother, although I was still experiencing occasional jumps in movement. After some testing, I saw that I had multiple updates occurring between calls of ondrawFrame. This was causing one frame to show the result of two (or more) updates, a larger jump than normal.

我所做的解决,这是封顶的时间差一些值,比如18毫秒,二onDrawFrame通话之间以及额外的时间存储在一个余数。这剩下的就被分配到随后的时间增量在未来几年的更新,如果他们能够处理它。这个想法prevents所有突然长的跳跃,基本上是平滑一次扣球出多个帧。这样做给了我很大的成绩。

What I did to resolve this was to cap the time delta to some value, say 18ms, between two onDrawFrame calls and store the extra time in a remainder. This remainder would be distributed to subsequent time deltas over the next few updates if they could handle it. This idea prevents all sudden long jumps, essentially smoothing a time spike out over multiple frames. Doing this gave me great results.

这样做的缺点做法是,对一点点的时间,对象的位置将不准确随着时间的推移,并实际上将加速,以弥补这一差异。但它的顺畅,速度变化不是很明显。

The downside to this approach is that for a little time, the position of objects will not be accurate with time, and will actually speed up to make up for that difference. But it's smoother and change in speed is not very noticeable.

最后,我决定重写我的引擎在考虑上述两种观点,而不是修修补补我原本国产发动机。我做了一些优化的线程同步,也许有人可以发表评论。

Finally, I decided to rewrite my engine with the two above ideas in mind, rather than patching up the engine I had originally made. I made some optimizations for the thread synchronization that perhaps someone could comment on.

我的当前线程交互所示: - 更新主题更新当前的缓冲区(双缓冲体系,以更新,同时绘制),然后将给这个缓冲的渲染器,如果previous框架已经拟定 - 如果previous框架尚未得出,或正在制定,更新线程将等到渲染线程的通知它,它已经引起。 - 渲染线程等待通过的,直到被通知更新线程已经发生了更新 - 当渲染线程的画,它设置了最后绘制的变量,表明这两个缓冲区的上次画了,也通知更新线程,如果它在等待了previous缓冲器为绘制。

My current threads interact like this: -Update thread updates the current buffer (double buffer system in order to update and draw simultaneously) and will then give this buffer to the renderer if the previous frame has been drawn. -If the previous frame has not yet draw, or is drawing, the update thread will wait until the render thread notifies it that it has drawn. -Render thread waits until notified by update thread that an update has occurred. -When the render thread draws, it sets a "last drawn variable" indicating which of the two buffers it last drew and also notifies the update thread if it was waiting on the previous buffer to be drawn.

这可能有点令人费解,但什么是在做是允许对多线程的优势,因为它可以为框架执行更新ñ而帧n-1是借鉴,同时也$每帧p $ pventing多个更新迭代如果渲染器花费很长的时间。为了进一步解释,此多更新方案是由更新螺纹锁固,如果它检测到lastDrawn缓冲器等于这是刚刚更新所述一个处理。如果它们相等,这表明该更新线程该帧之前尚未绘制。

That may be a little convoluted, but what that's doing is allowing for the advantages of multithreading, in that it can perform the update for frame n while frame n-1 is drawing while also preventing multiple update iterations per frame if the renderer is taking a long time. To further explain, this multiple-update scenario is handled by the update thread locking if it detects that the lastDrawn buffer is equal to the one which was just updated. If they are equal, this indicates to the update thread that the frame before has not yet been drawn.

到目前为止,我收到了良好的效果。让我知道如果任何人有任何意见,将很高兴听到任何东西我做,对还是错你的看法。

So far I'm getting good results. Let me know if anyone has any comments, would be happy to hear your thoughts on anything I'm doing, right or wrong.

感谢

推荐答案

(从Blackhex答案提出了一些有趣的观点,但我不能塞进这一切都变成了评语)

(The answer from Blackhex raised some interesting points, but I can't cram all this into a comment.)

有异步操作势必会导致这样的问题,两个线程。看看这样说:驱动动画是硬件垂直同步的信号,即在其Android的表面合成器提供了一个完整的数据来显示硬件的新屏幕的点的事件。你想有一个新的数据帧时垂直同步到达。如果没有新的数据,游戏看起来波涛汹涌。如果产生在那个时期3帧数据,两人将被忽略,而你只是浪费电池的使用寿命。

Having two threads operating asynchronously is bound to lead to issues like this. Look at it this way: the event that drives animation is the hardware "vsync" signal, i.e. the point at which the Android surface compositor provides a new screen full of data to the display hardware. You want to have a new frame of data whenever vsync arrives. If you don't have new data, the game looks choppy. If you generated 3 frames of data in that period, two will be ignored, and you're just wasting battery life.

(运行CPU满了还可能导致设备升温,这可能会导致热量抑制,减慢一切都在系统停机......,可以使你的动画不连贯。)

(Running a CPU full out may also cause the device to heat up, which can lead to thermal throttling, which slows everything in the system down... and can make your animation choppy.)

要保持与显示器同步的最简单的方法是执行 onDrawFrame所有的状态更新()。如果有时需要超过一帧来执行你的状态更新和渲染的图像,然后你会看坏了,需要修改你的方法。简单地将所有的游戏状态更新为第二核心是不会帮助尽可能多的,你可能会喜欢的 - 如果核心#1的渲染线程,核心#2是游戏状态更新线程,那么核心#1是怎么回事坐视不管,而核心#2更新了状态,之后内核#1将恢复做实际的渲染而核心#2处于空闲状态,并且它会采取一样长。要真正提高计算的,你可以每帧做量,你需要有两个(或更多)的核心同时工作,这引起了一些有趣的同步问题,这取决于你如何定义你的劳动分工(见的 http://developer.android.com/training/articles/smp.html ,如果​​你想要去这条道路)。

The easiest way to stay in sync with the display is to perform all of your state updates in onDrawFrame(). If it sometimes takes longer than one frame to perform your state updates and render the frame, then you're going to look bad, and need to modify your approach. Simply shifting all game state updates to a second core isn't going to help as much as you might like -- if core #1 is the renderer thread, and core #2 is the game state update thread, then core #1 is going to sit idle while core #2 updates the state, after which core #1 will resume to do the actual rendering while core #2 sits idle, and it's going to take just as long. To actually increase the amount of computation you can do per frame, you'd need to have two (or more) cores working simultaneously, which raises some interesting synchronization issues depending on how you define your division of labor (see http://developer.android.com/training/articles/smp.html if you want to go down that road).

尝试使用视频下载()管理​​帧速率通常非常结束。你可以不知道多久VSYNC之间的周期,要不了多久,直到下一个到达。这对每一个设备的不同,并在某些设备可以是可变的。你基本上结束了两个时钟 - VSYNC和睡眠 - 拍打对方,其结果是波涛汹涌的动画。最重要的是,视频下载()并没有做出准确或最低的睡眠时间任何具体的保障。

Attempting to use Thread.sleep() to manage the frame rate generally ends badly. You can't know how long the period between vsync is, or how long until the next one arrives. It's different for every device, and on some devices it may be variable. You essentially end up with two clocks -- vsync and sleep -- beating against each other, and the result is choppy animation. On top of that, Thread.sleep() doesn't make any specific guarantees about accuracy or minimum sleep duration.

我还没有真正通过副本岛来源消失了,但在 GameRenderer.onDrawFrame(),你可以看到他们的比赛状态的线程之间的相互作用(这将创建一个列表对象绘制)和GL渲染线程(这只是绘制列表)。在他们的模型,游戏状态更新只在需要的,如果一切都没有改变,它只是重新绘制previous抽奖名单。这种模式非常适用于事件驱动的游戏,即凡在屏幕更新的内容,当有事情发生(你打一个关键,一个计时器火灾等)。当事件发生时,他们可以做一个最小的状态更新和调整抽奖名单为宜。

I haven't really gone through the Replica Island sources, but in GameRenderer.onDrawFrame() you can see the interaction between their game state thread (which creates a list of objects to draw) and the GL renderer thread (which just draws the list). In their model, the game state only updates as needed, and if nothing has changed it just re-draws the previous draw list. This model works well for an event-driven game, i.e. where the contents on screen update when something happens (you hit a key, a timer fires, etc). When an event occurs, they can do a minimal state update and adjust the draw list as appropriate.

从另一个方面来说,渲染线程并行比赛状态工作,因为他们没有硬性捆绑在一起。本场比赛的状态只是到处跑,需要更新的东西,而渲染线程锁下来每场同步,并提请不管它找到。只要任何一方都保持任何关了太久,他们没有明显的干扰。唯一有趣的共享状态的抽奖名单,守卫与互斥,所以他们的多核心问题被最小化。

Viewed another way, the render thread and the game state work in parallel because they're not rigidly tied together. The game state just runs around updating things as needed, and the render thread locks it down every vsync and draws whatever it finds. So long as neither side keeps anything locked up for too long, they don't visibly interfere. The only interesting shared state is the draw list, guarded with a mutex, so their multi-core issues are minimized.

有关Android的突破( HTTP://$c$c.google.com / P / Android的突破/ ),游戏中有一球蹦跳着,在连续运动。在那里,我们想尽可能频繁地更新我们的状态显示可以让我们,让我们开车的状态变化VSYNC过的,用时间差从previous框架,以确定多远的东西有先进的。的每帧计算的小,并且所述呈现是pretty的琐碎对于现代的GL设备,所以这一切可以方便地放入一个第二的1/60位。如果显示更新要快得多(240Hz的),我们可能偶尔会丢弃帧(同样,不太可能被发现),我们就会被燃烧4倍作为帧更新的CPU(这是不幸的)。

For Android Breakout ( http://code.google.com/p/android-breakout/ ), the game has a ball bouncing around, in continuous motion. There we want to update our state as frequently as the display allows us to, so we drive the state change off of vsync, using a time delta from the previous frame to determine how far things have advanced. The per-frame computation is small, and the rendering is pretty trivial for a modern GL device, so it all fits easily in 1/60th of a second. If the display updated much faster (240Hz) we might occasionally drop frames (again, unlikely to be noticed) and we'd be burning 4x as much CPU on frame updates (which is unfortunate).

如果由于某种原因,这些游戏的一遗漏一个垂直同步,玩家可能会或可能不会注意到。由经过时间的状态进步,一个固定持续时间的帧的不是$ P $对置的概念,所以如球将或者移动1个单位的每个连续两帧,或2个单位上一帧。根据帧速率和显示的响应,这可能是不可见。 (这是一个关键的设计问题,并且是可以弄乱你的头,如果你在蜱的角度设想你的游戏状态。)

If for some reason one of these games missed a vsync, the player may or may not notice. The state advances by elapsed time, not a pre-set notion of a fixed-duration "frame", so e.g. the ball will either move 1 unit on each of two consecutive frames, or 2 units on one frame. Depending on the frame rate and the responsiveness of the display, this may not be visible. (This is a key design issue, and one that can mess with your head if you envisioned your game state in terms of "ticks".)

这两者都是有效的方法。关键是要吸取当前的状态,只要 onDrawFrame 被调用,以尽可能少更新状态。

Both of these are valid approaches. The key is to draw the current state whenever onDrawFrame is called, and to update state as infrequently as possible.

请注意其他任何人碰巧读这谁:不要使用 System.currentTimeMillis的()。在 SystemClock.uptimeMillis(),系统中使用的问题是基于该单调时钟而非挂钟时间的例子。这,或 System.nanoTime(),是较好的选择。 (我在对的currentTimeMillis 小十字军东征,这在移动设备上可能会突然跳跃前进或后退。)

Note for anyone else who happens to read this: don't use System.currentTimeMillis(). The example in the question used SystemClock.uptimeMillis(), which is based on the monotonic clock rather than wall-clock time. That, or System.nanoTime(), are better choices. (I'm on a minor crusade against currentTimeMillis, which on a mobile device could suddenly jump forward or backward.)

更新:我写了even再回答以一个类似的问题。

Update: I wrote an even longer answer to a similar question.

更新2:我写了一个甚至更长的时间更长回答有关的一般问题(见附录A)。

Update 2: I wrote an even longer longer answer about the general problem (see Appendix A).