提高显示性能

4
我添加了一些东西到我的游戏后,特别是在增加一个功能后,游戏变得很卡顿。我使用pygame.time.get_ticks()进行了测试,发现我的循环中约有90%的时间用在了两个位置上: 1.将所有精灵绘制到屏幕上。 2.绘制我的能力管理器,它只是绘制/混合一些图像。
当我删除convert()和convert_alpha()时,我的能力管理器的性能显著提高,这让我感到困惑,因为在绘制精灵中删除转换似乎并没有影响性能。是否有人知道为什么convert会拖慢游戏速度?文档说这是最好的方法。另外,为什么它可以在一个区域有帮助,在另一个区域却没有呢?
编辑:以下是我的测试结果数据。 删除#2中的converts(即能力管理器绘制) ,将平均绘制时间从大约80毫秒降低到大约45毫秒。 删除或添加#1中的converts(即绘制精灵到屏幕) ,几乎不影响绘制所需时间,其影响范围为+/- 5。这种微小的变化可能不是删除converts的结果,因此我的问题应主要集中在“为什么删除转换可以在能力管理器绘制中有如此大的帮助?”上,而只是稍微涉及为什么它在一个区域有如此大的帮助,在另一个区域却没有。
1个回答

1

注意: 我不使用 pygame,但我专业地编写高清视频的缩放器和转换器,因此我在此基础上进行了绘制。

我在这里查阅了文档:http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert

从文档中可以看出:

如果没有传递参数,则新 Surface 将具有与显示 Surface 相同的像素格式。这始终是最快的 blitting 格式。

换句话说,如果格式匹配,则速度很快。但是,如果不匹配,则必须进行转换[这将运行得更慢]。

在它们被多次 blitting 之前,将所有 Surface 转换为所需格式是一个好主意。

这可能是您想要做的事情(即保留与最终输出格式匹配的已转换表面的缓存副本)

对于您的精灵,它们应该相对较小,因此差异不大。但对于较大的区域,转换可能会[并且似乎是]显著的。

与简单的blit不同,可以使用一系列快速的[C]memcpy操作完成,转换必须逐像素完成。这可能涉及使用周围像素的卷积核[对于一个好的缩放器,我看到过使用2D 6 tap FIR滤波器]。

由于精灵较小,转换器可以选择更简单的转换算法,因为失真会更不明显。对于较大的区域,转换器可以选择更复杂的算法,因为失真会在更大的区域内累积。

所以,再次强调,预加载是可行的方式。

如果源区域在每帧中发生变化而无法执行上述操作,则可以引入一帧延迟,并将转换分成多个线程/核心,在线程之间将整个区域细分为子区域。


更新:

所以,你会发现一开始速度会降低,因为像素格式必须更改。

在游戏开始时进行预计算不应该是问题,因为你的数字是80毫秒。用户甚至不会注意到游戏开始时这么小的延迟。

专业游戏通过“闪屏”页面来掩盖这一点,其中包含他们的标志,可能会进行[琐碎的]动画(例如只是变形颜色等)

但是,在游戏开始时转换后,剩下的速度不应该更快吗?

是的,根据你已经描述的内容,速度应该更快:后续帧应该是45毫秒而不是80毫秒。这现在给你一个帧率为22,这可能足够了。如果你仍然需要更快(即达到30 fps),那么执行我已经提到的子区域技术可能会有所帮助。此外,仅将从帧N到N + 1更改的内容复制到表面也可能有所帮助。

我仍然困惑于为什么转换后整个游戏的速度都变慢了。

以下是一些简陋的blit和convert代码(仅用于说明,不是真正的代码)。
现在你所做的就像下面的blit_convert一样,对于你的数据中的每一帧,也就是我们将称之为ability_manager_surface。
请注意,它比简单的blit慢(例如下面的blit_fast或blit_slow)。快速的blit只是将每个源像素复制到目标像素。示例转换器必须取当前源像素及其最近邻居的平均值,因此它必须为每个目标像素提取五个源像素值。因此,它更慢。实际用于缩放的算法可能会更慢。
如果在游戏启动时在ability_manager_surface上执行blit_convert并将输出保存到“已经转换”的变量(例如precalc_manager_surface),那么您可以使用precalc_manager_surface在每个帧上使用blit_fast。也就是说,不需要重新计算“静态”数据。
# dstv -- destination pixel array
# dsthgt -- destination height
# dstwid -- destination width
#
# dstybase -- destination Y position for upper left corner of inset
# dstxbase -- destination X position for upper left corner of inset
#
# srcv -- source pixel array
# srchgt -- source height
# srcwid -- source width

# ------------------------------------------------------------------------------
# blit_fast -- fast blit
# this uses a 1 dimensional array to be fast
def blit_fast(dstv,dsthgt,dstwid,dstybase,dstxbase,srcv,srchgt,srcwid):

    # NOTE: I may have messed up the equations here
    for yoff in range(dstybase,dstybase + srchgt):
        dstypos = (yoff * dstwid) + dstxbase
        srcypos = (yoff * srcwid);

        for xoff in range(0,srcwid):
            dstv[dstypos + xoff] = srcv[srcypos + xoff]

# ------------------------------------------------------------------------------
# blit_slow -- slower blit
# this uses a 2 dimensional array to be more clear
def blit_slow(dstv,dsthgt,dstwid,dstybase,dstxbase,srcv,srchgt,srcwid):

    for yoff in range(0,srchgt):
        for xoff in range(0,srcwid):
            dstv[dstybase + yoff][dstxbase + xoff] = srcv[yoff][xoff]

# ------------------------------------------------------------------------------
# blit_convert -- blit with conversion
def blit_convert(dstv,dsthgt,dstwid,dstybase,dstxbase,srcv,srchgt,srcwid):

    for yoff in range(0,srchgt):
        for xoff in range(0,srcwid):
            dstv[dstybase + yoff][dstxbase + xoff] = convert(srcv,yoff,xoff)

# convert -- conversion function
# NOTE: this is more like a blur or soften filter
# the main point is this takes _more_ time than a simple blit
def convert(srcv,ypos,xpos):

    # we ignore the special case for the borders

    cur = srcv[ypos][xpos]

    top = srcv[ypos - 1][xpos]
    bot = srcv[ypos + 1][xpos]
    left = srcv[ypos][xpos - 1]
    right = srcv[ypos][xpos + 1]

    # do a [sample] convolution kernel
    # this equation probably isn't accurate -- just to illustrate something that
    # is computationally expensive on a per pixel basis
    out = (cur * 0.6) + (top * 0.1) + (bot * 0.1) + (left * 0.1) + (right * 0.1)

    return out
< p >< em >注意:上述示例使用了“玩具”转换函数。要进行高分辨率/高质量图像重新缩放(例如1024x768 --> 1920x1080),您可能需要使用/选择“多相重采样”,其计算量巨大。例如,仅供娱乐,请参见[令人震惊的]:https://cnx.org/contents/xOVdQmDl@10/Polyphase-Resampling-with-a-Ra


更新 #2:

发现只更新移动的物体的想法很有帮助

这是实时动画和图形的标准建议。只重新计算需要的部分。你只需要确定哪些是需要的。

然而,如果我理解正确,你说我的游戏在每一帧转换后会变慢。

根据你最初的描述,这将应该是情况。

但事实并非如此,因为我在开始时进行转换,所以应该是你所说的快速 blit,但如果我根本不进行转换,则更快。

没有你的实际代码,很难推测。但是...

当你创建一个表面(例如用于保存像 .png 这样的图像文件),默认格式是使用与屏幕格式相近的格式。因此,可以进行 blit 而无需转换。

因此,如果您预先转换了一个离屏表面,为什么在后转换格式与屏幕格式匹配的情况下会更慢[blit]呢?如果它更慢,那么肯定存在某种不匹配。而且,如果您使用默认设置创建表面,为什么它需要转换呢?
标准模型是尽可能直接在屏幕上执行操作。屏幕是“双缓冲区”,实际渲染是在主显示循环底部使用pygame.display.flip完成的。
因此,我不确定表面转换在您的程序中的作用是什么。
这是一些示例程序的链接[包括一些精灵]: http://www.balloonbuilding.com/index.php?chapter=example_code 这只是一个从“pygame sample program”搜索的“all words”的链接之一。因此,上面的链接[以及其他链接]可能会帮助您,如果您能将自己正在做的与它们进行比较。

因此,您会注意到起初速度会降低,因为像素格式必须更改。但是,在游戏开始时进行转换后,剩下的速度不应该更快吗?如果我已经进行了转换,我仍然对整个游戏速度较慢感到困惑。 - David Jay Brady
感谢您的更新,我发现只更新移动的内容的想法很有帮助,所以我已经赞同了您的答案。然而,如果我读得正确,您说我的游戏在转换后会变慢,因为我每帧都这样做。事实并非如此,因为我在最开始就进行了转换,所以它应该是您所说的快速 blit,但如果我根本不转换,它会更快。 - David Jay Brady

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接