【问题标题】:pygame.display.update() causing periodic lag spikespygame.display.update() 导致周期性滞后峰值
【发布时间】:2016-02-25 19:16:37
【问题描述】:

我用 Pygame 做了一个简单的程序,它基本上是一个滚动背景,并注意到周期性的滞后峰值。搞了半天代码,发现调用 pygame.display.update() 有时候会花很多时间来执行。

为了真正简化并复制问题,我编写了以下代码:

import pygame
import sys
import time

FRAME_RATE = 30

# don't mind the screen and time_passed variables; they aren't used in this script

def run_game():
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((500, 500))
    prev_spike = 0
    time_passed = 0
    while 1:
        start = time.clock()
        pygame.display.update()
        timenow = time.clock()
        time_spent = timenow - start
        if time_spent > 0.01:
            print time_spent 
            if prev_spike:
                print "Last spike was: {} seconds ago".format(timenow - prev_spike)
            prev_spike = timenow
        time_passed = clock.tick(FRAME_RATE)

if __name__ == "__main__":
    run_game()

以该帧速率输出的 sn-p:

0.0258948412828
Last spike was: 1.01579813191 seconds ago
0.0186809297657
Last spike was: 0.982841934526 seconds ago
0.0225958783907
Last spike was: 2.01697784257 seconds ago
0.0145269648427
Last spike was: 1.01603407404 seconds ago
0.0186094554386
Last spike was: 2.01713885195 seconds ago
0.0283046020628
Last spike was: 1.03270104172 seconds ago
0.0223322687757
Last spike was: 1.01709735072 seconds ago
0.0152536205013
Last spike was: 1.01601639759 seconds ago

我真的不知道发生了什么,并且真的很想得到一些见解。

更多细节:

在每次循环迭代中打印 time_spent 时输出的 sn-p(而不是仅当它 > 0.01 时):

0.000204431946257
0.000242090462673
0.000207890381438
0.000272447838151
0.000230178074828
0.0357667523718          <-- update taking two orders of magnitude longer than normal
0.000293582719813
0.000343153624075
0.000287818661178
0.000249391603611

以 60 FPS 运行时,每个尖峰之间的间隔几乎总是 1 秒,很少是 2 秒(尖峰持续时间大约是两倍)。在较低的帧速率下,尖峰之间的间隔将开始变化更大,但始终接近整数值。

我尝试在另一台计算机上运行脚本,但问题没有重现; pygame.display.update() 的执行时间相当快且一致。然而,当我在那台机器上运行我的原始程序时,一秒间隔的延迟峰值仍然存在(我可能会寻找其他机器来测试......)

我测试的两台机器都运行 Windows 7。

编辑:

我抓取了一些托管在 Pygame 网站上的随机游戏,我得到了类似的行为 - 定期调用 pygame.display.update(或 flip)需要 10 到 40 毫秒,而它们通常需要不到 2 毫秒。

似乎没有其他人遇到此问题(或至少在抱怨。这可能是因为大多数游戏的运行速度低于 30 FPS,而此问题不太明显),所以我的环境可能有问题.我确实(有点)在第二台机器上重现了这个问题(如上所述),所以我宁愿不要忽视这个问题,希望最终用户不会遇到它......

【问题讨论】:

标签: python pygame


【解决方案1】:

Try asking this in Game Development,您可能会得到更好的答案。

编辑:以下代码似乎无法解决所提出的问题,但确实提供了动画测试并使用了主游戏循环的定时回调

尝试对渲染函数使用定时回调。

import pygame
import time
import math
from pygame.locals import *

desiredfps = 60
updaterate = int(1000 / desiredfps)
print "Aiming for {0}fps, update every {1} millisecond".format(desiredfps, updaterate)
lasttime = 0
rectx = 0
recty = 0

def run_game():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    pygame.time.set_timer(USEREVENT+1, updaterate)

    def mainloop():
        global lasttime
        global rectx
        global recty
        screen.fill(pygame.Color("white"))
        screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
        screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
        screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
        screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
        rectx += 5
        if rectx > 500:
            rectx = 0
            recty += 20
        beforerender = time.clock()
        pygame.display.update()
        afterrender = time.clock()
        renderdelta = afterrender - beforerender
        framedelta = beforerender - lasttime
        lasttime = beforerender
        if renderdelta > 0.01:
            print "render time: {0}".format(renderdelta)
            print "frame delta: {0}".format(framedelta)
            print "-------------------------------------"            
    while(1):
        for event in pygame.event.get():
            if event.type == USEREVENT+1:
                mainloop()
            if event.type == QUIT:
                pygame.quit()
                return
# Run test           
run_game()

我在执行此操作时似乎没有任何问题,但如果您仍然遇到问题,请告诉我。

【讨论】:

  • 对我不起作用。报告的帧时间没有滞后峰值,但视觉上(我添加了移动对象)滞后非常明显。
  • 嗯.. 试试那个编辑。它只有非常基本的动画。如果你仍然有明显的滞后,那么我不知道,否则可能是你的动画。
【解决方案2】:

经过一些测试,以下是一些结果。首先,回答问题: 延迟峰值的原因不是 pygame.display.update()。延迟尖峰的原因是clock.tick(FRAME_RATE)。请注意,没有 FRAME_RATE 参数的 clock.tick() 不会导致尖峰。当我尝试使用 python 的 time.sleep() 方法手动跟踪帧速率来替换 pygame 的 clock.tick() 时,问题并没有消失。我认为这是因为在内部,python 的 time.sleep() 和 pygame 的 clock.tick() 都使用相同的函数,这被认为是不精确的。似乎如果你让这个函数休眠 1 毫秒(这样如果游戏足够简单,就不会占用所有的 CPU),这个函数有时会比这长得多,大约长 10-15 毫秒。这取决于睡眠机制的操作系统实现和所涉及的调度。

解决办法是不要使用任何与睡眠相关的功能。

还有第二部分。即使您不使用任何 sleep(),也存在各个帧之间的增量时间不一致的问题,如果不考虑这一点,可能会导致视觉抖动/卡顿。相信这个tutorial已经对这个问题进行了非常详细的探讨。

所以我继续在 python 和 pygame 中实现了本教程中提出的解决方案,并且效果很好。即使我以 30fps 的速度更新“物理”,它看起来也很流畅。它吃很多cpu,但看起来不错。代码如下:

from __future__ import division
import pygame
from random import randint
from math import fabs

PHYS_FPS = 30
DT = 1 / PHYS_FPS
MAX_FRAMETIME = 0.25



def interpolate(star1, star2, alpha):
    x1 = star1[0]
    x2 = star2[0]
    # since I "teleport" stars at the end of the screen, I need to ignore
    # interpolation in such cases. try 1000 instead of 100 and see what happens
    if fabs(x2 - x1) < 100:
        return (x2 * alpha + x1 * (1 - alpha), star1[1], star1[2])
    return star2


def run_game():
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((500, 500))

    # generate stars
    stars = [(randint(0, 500), randint(0, 500), randint(2, 6)) for i in range(50)]
    stars_prev = stars

    accumulator = 0
    frametime = clock.tick()

    play = True
    while play:
        frametime = clock.tick() / 1000
        if frametime > MAX_FRAMETIME:
            frametime = MAX_FRAMETIME

        accumulator += frametime

        # handle events to quit on 'X' and escape key
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                play = False
            elif e.type == pygame.KEYDOWN:
                if e.key == pygame.K_ESCAPE:
                    play = False

        while accumulator >= DT:
            stars_prev = stars[:]
            # move stars
            for i, (x, y, r) in enumerate(stars):
                stars[i] = (x - r * 50 * DT, y, r) if x > -20 else (520, randint(0, 500), r)
            accumulator -= DT

        alpha = accumulator / DT
        stars_inter = [interpolate(s1, s2, alpha) for s1, s2 in zip(stars_prev, stars)]

        # clear screen
        screen.fill(pygame.Color('black'))

        # draw stars
        for x, y, r in stars_inter:
            pygame.draw.circle(screen, pygame.Color('white'), (int(x), y), r)

        pygame.display.update()

if __name__ == "__main__":
    run_game()

【讨论】:

  • 是的,pygame.Clock.tick() 不准确,因为它只使用了SDL_Delay pygame.Clock.tick_busy_loop() 更准确,但会浪费更多 CPU 资源。
  • 更新了可工作的 pygame 代码、插值和其他好东西。
  • 太棒了,但不幸的是,我认为这个答案不适用于我的问题。我在我的机器上尝试了您的代码(以及其他答案的代码),但尖峰仍然存在。我提供的代码测量了 update() 执行所花费的时间,所以 tick() 没有空间来搞砸任何事情。
  • 那我想我帮不了你太多,因为我无法重现 update() 尖峰。用 SDL 试试看是否仍然存在尖峰。如果有,请使用 SDL 的源代码对其进行分析(这样它会向您显示 update() 的调用及其时间)。
【解决方案3】:
import pygame
import time
import math
from pygame.locals import *
desiredfps = 60
updaterate = int(1000 / desiredfps)
lasttime = 0
rectx = 0
recty = 0

def run_game():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    pygame.time.set_timer(USEREVENT+1, updaterate)
    def mainloop():
        global lasttime
        global rectx
        global recty
        screen.fill(pygame.Color("white"))
        screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20))
        screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20))
        screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20))
        screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20))
        rectx += 5
        if rectx > 500:
            rectx = 0
            recty += 20
        beforerender = time.clock()
        pygame.display.update()
        afterrender = time.clock()
        renderdelta = afterrender - beforerender
        framedelta = beforerender - lasttime
        lasttime = beforerender
        if renderdelta > 0.01:
            print ("render time: {0}").format(renderdelta)
            print ("frame delta: {0}").format(framedelta)
            print ("-------------------------------------")      

    while(1):
        for event in pygame.event.get():
            if event.type == USEREVENT+1:
                mainloop()
            if event.type == QUIT:
                pygame.quit()
                return # 

【讨论】:

  • 没有解释,没有文字,只有代码。这应该回答这个问题吗?
猜你喜欢
  • 1970-01-01
  • 2016-06-14
  • 2013-12-24
  • 2015-12-03
  • 2019-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多