17173 > 游戏资讯 > 官方公告 > 正文

《十字军之王3》「唯主是依」开发日志 #6 - 性能

2026-09-09 03:51:47 神评论
17173 新闻导语

《十字军之王3》最新性能优化:加载时间从84秒缩至16秒,显存降低1/3,内存减少1/6,流畅体验大幅提升!点击了解详情。

「溥天之下」All under Heaven那阵子,我们发过一篇性能优化相关的日志()。

在那篇日志中,我们同时也展望了《十字军之王III》未来的愿景与规划。而本篇开发日志将汇总截止到「唯主是依」By God Alone更新时,各项进展的状态。

再跟各位说声大家好,我是 Carl-Henrik,《十字军之王III》团队的主程序员。在「溥天之下」发布前后,我曾讨论过加快加载速度以及改进各种系统的计划。本篇开发日志正是围绕这些内容展开,同时还将探讨日常游戏性能的发展趋势,以及玩家在游玩过程中游戏所占用的内存大小。

本文在「唯主是依」发布前几周撰写,目前我们仍在全力以赴进一步优化各个系统。在此期间任何情况都有可能发生,所以请大家对最终成果要保持一个灵活的心态,但这就是我们目前的现状。

直奔主题:

  • 模拟速度:目标是在日常性能上与上一个更新——1.19.0“Scribe”——保持一致。现阶段已经非常接近,并预计最终会达成这一目标。

  • 大幅减少初始加载内容。例如在我们配置较低的测试机上,通过流式加载网格和插图,而不是在启动时加载所有内容,从而大幅缩短了进入主菜单的时间——在我自己的电脑上,加载时间从84秒缩短至16秒。

  • 相同场景下显存占用显著减少——根据材质质量设置不同,减幅在1/3到1/2之间。

  • 游戏运行十年后的系统内存占用减少了大约六分之一。

以下是详细的长文版,其中也包括了那些最终没有成功的尝试。

为了现状并验证优化是否有效,我们需要可靠地测量性能。

模拟速度是通过模拟100年期间每一天的平均耗时来衡量的。为了保证测试的一致性,我们从1066年开始观察,至少持续到1166年。有时我们会让游戏夜间挂机运行,以获取后期游戏行为的画面,因为随着时间推移,更新也越来越多。

由于随机种子以及全体开发同仁每天都在更新内容进度,每一次运行都会略有不同。这意味着每次采集的数据都有所波动,我们在评估时已经考虑到了这一点。

在获取每日的tick性能的同时,我们也会关注启动时间。这里注意到的一点是:如果你想更快地进入游戏并开始游玩,Linux要比Windows快的多!

\[优化前的性能采集:当前版本中游戏加载至主菜单耗时84秒]

那么,游戏里一天过去到底需要多长时间?

我们使用了一台配置相当低的参考机器——一枚2012年产的八核CPU,配16GB内存——在游戏后期,过完一天只需不到一秒钟。大多数游戏日的耗时比平均值更快,其中只有极少一部分耗时明显较长——通常是有大事发生的时刻,比起整体变慢,即时的卡顿在体感上更明显。

随着进入游戏的年数的增长,计算量也会增加。在1070年到1160年之间,游戏日时间平均起来要高出约一倍半。

时间去哪了?粗略来看,在100年的运行期间:

  • AI决策,约占游戏日耗时的1/4多一点;

  • 并行预更新,各系统计算即将发生更改的耗时,约占1/4

  • 顺序更新,应用这些更改的耗时大约占1/5。

  • 触发事件,约占1/10多一点。

「唯主是依」的目标是为你带来与“Scribe”补丁(我们上一个生活质量更新)相同的游戏日速度。撰写本文时,我们已经足够接近这个目标,足够到这里我们可以信心十足的如此放话出来。不过我们不会在这里给出一个具体数字,因为发布前的最后几周里,这个数字随时可能上下浮动。

每一天都按预更新(pre-update)、更新(update)、AI更新(AI update)的顺序处理。

预更新中,角色和修正占了最大比例,这也在意料之中——毕竟这就是玩法互动的对象!在AI更新中,评估角色互动是最大的性能开销,这也是本项目中增长最多的部分:今年7月的几周内,实装了大量全新的教会互动内容,而每一个新互动都意味着每个AI角色在每个月都要多寻思一件事。这没法删,因为互动本身就是游戏玩法;因此,我们重新专注于降低每次评估的成本,策划和脚本团队在这方面取得了很好的成果。

通过删除代码来找到优化点相当有趣!在参与优化大型工程时,我们核对发现某个计数器竟然每个月都要进行一次检查。其本意是正确的,但检查顺序却是先算能不能承包得起其中任何一个项目,然后再去寻思要不要承包。简单地移除承包检查,就让整个流程的性能成本降到了以前的四分之一,而且还能轻松予以验证。

\[百年内按类别划分的平均每日耗时]

得益于我们在性能测试中记录的详细数据,我们可以发现一些奇怪的操作:可能单次运行非常快,但被调用的次数极其庞大。

对比仅仅相隔几天的两次采集,角色管理器明显变慢了,却没有显而易见的理由。结果发现是一个单一函数在作祟:它用于检查某位统治者是否被允许保留其某个领地法律。在“Scribe”版本中,这项检查一个月大约执行一千五百次;而在新版本中,一个月被检查了大约八十五万次。

这个函数本身没有任何问题。只是某次脚本变动将其移到了一个会被频繁调用的地方,仅从代码变更表面很难看出来——在文本编辑器里它看起来非常合理。将其缩小到单次提交后,脚本团队精准地获得了重构所需的依据,其结果是整个游戏日tick性能开销减少了几个百分点。仅凭这一点,就证明了持续进行性能测量而不是等到最后才测量的价值:在两天内发现问题与在两个月后发现问题的区别在于,两个月后它已经成了既成事实,没有人还记得当初为什么这么写了。

我们经常发现,为了做一个决定,我们对很多事情做了远超必要的计算。

最明显的一个例子是新教会内容中经常出现的一项检查:这个特定的统治者是否在这个教区内?为了回答这个问题,游戏会构建一个该区域内所有统治者的列表,进行排序,去除重复项,将完成的列表返回给调用者,然后调用者再查看它关心的那个统治者是否在里面。这其中的每一步逻辑都是对的,但没有一步是必要的。如果直接问这个问题——这个统治者在这个区域内吗?是或否——其成本降低了十倍以上。

在比较两个信仰时也出现了类似的情况。游戏过去会为两个信仰的教义分别构建一个表格,然后遍历整个表格寻找差异;而改为只为一个信仰构建表格并用另一个信仰去比对它,完成相同工作的速度快了大约三分之一。这些方法并不算高明,它们都只是少做了一些事。

这里有一个有趣的案例,其核心问题在于:这种性能开销最初为什么会存在。

游戏中的事物彼此关联——角色关联其领主,头衔关联其持有者——而导致游戏崩溃的常见原因,就是访问了一个已经被销毁的对象的引用。为了防止游戏崩溃,每个对象都带有小型身份标记,也就是我们所说的“空对象”(nullobject),检查引用是否仍然有效就意味着检查该标记是否完好。这项检查被调用的次数极其巨大。

它原先的实现方式需要机器在提问之前,先去查询它处理的是哪种对象。去掉了这层间接寻址——检查做同样的工作、给出同样的答案,只是不再需要先问路了——在整个百年的运行中,这就节省了大约3%的总体性能。这是本项目中单项收益最大的改进之一,且完全没有改变任何游戏行为。

不久前,我们通过将一项清理工作(每个tick清理一次临时角色修正)从并行区段中移出、改为在单线程上运行,修复了一个崩溃问题。这是一种完全合理且有效的消灭崩溃的方法,而且确实成功了。但它也悄悄消耗了大约4%的整体性能,因为这项清理工作极其廉价,却被限制在单个CPU线程上运行。

它无法并行运行的根本原因在于,其使用的内存分配器无法同时安全地被多个线程调用。通过使分配器线程安全,并复用对象而不是销毁并重新创建它们,这项工作得以回到并行运行状态而没有让崩溃死灰复燃。这几乎将该部分tick耗时减少了一半。这里的教训是:检查崩溃修复是否具有性能影响,因为这些修复通常是在时间压力下完成的,即时方案可能并不是最优解。

顺便我在那儿还发现了几个地方——角色记忆、宗族、好感度——游戏会从一个极长的列表中逐个删除大量条目,而每一次删除都会将后面的所有内容向前移动一位。改为在单次遍历中将它们全部删除,这种修改只需花一个下午,不会出现在任何图表上,并且从此以后完全零开销。

加载界面正是我说过要开始优化的地方,而本次性能更新的重头戏在于:游戏不再在让你游玩之前加载所有东西了。

此前,游戏中的每一个3D模型在启动时都会被加载、构建并上传到显卡,不管你是否会看到它。现在,模型只有在实际需要它的那一刻才会被加载,并且只有在显存空间不足且一段时间未被绘制时才会释放。

所以,你们中的主观唯心主义者在此可以高兴地了解到,我们现在确保了游戏准确模拟了世界正确的形而上学,其中物体恒存性确保了,某物不在视线中,就会停止存在(于内存中); )

这是本次更新中对加载时间影响最大的单一改动。在我们较慢的测试机上,流式加载将进入主菜单的时间缩短了约三分之二。换一种方式测量,游戏在启动时构建的材质数量大约只有过去的三分之一——其余的都在之后需要时才构建,如果根本不需要,就永远不会构建。

图形流式加载引入了一种物件突现的风险:即资产在你看到它们的那一刻才突然出现。我们通过两种方式解决了这个问题。首先,只要显存还有富余,模型就会保留在内存中,而不是无论显卡有多少空间都按固定定时器释放。在正常的游玩会话中,其占用远低于现代显卡可以腾出的空间,这意味着在视野中来回滚动的资产不会每次都被重新构建。其次,我们在各个地方确保资产在展示王座厅等场景之前就已经就位。

\[网格流式加载调试器:显示了加载了什么、距离上次绘制有多久——仅适用于游戏的内部版本]

材质也受到了同样的对待,只不过是从另一个端入手。

《十字军之王III》拥有大量的二维美术——事件插图、特质图标、肖像细节遮罩——以往所有这些内容在整个游戏过程中都常驻在显存中,无论它们是否被展示。插图流式加载会在需要时加载这些图像,并在停止绘制后不久将其释放。一张事件图片只要你在阅读事件时就会保留在屏幕上,而这正是它需要存在的所有时间。

其中有两个特定案例本身就非常值得投入精力。所有的宝物图标过去都被加载并存放在显存中,倒不是因为它们需要显示,而是为了在有遗漏时向日志报告错误。检查文件是否存在可以产生完全相同的错误报告,而且几乎不消耗任何成本。此外,用于服装和纹章细节的肖像图案遮罩(接近1GB的源美术)现在也会根据需要进行流式加载,而不是永久保留在活动内存中。

\[我们添加的用于追踪显存占用的可视化资源管理器]

正是这个问题导致了某些硬件配置出现大量难以捉摸的卡顿甚至崩溃。

在「溥天之下」之后我们不断添加资产,所有这些资产都是在启动时一次性加载的。随着游戏本身的壮大,内容在每个更新中都在增长,但预加载意味着无论屏幕上是否出现,显卡都要承担这笔开销。一旦超过某个临界点,显存小于游戏需求的显卡就必须在每一帧中来回搬运总线数据,而当这种情况发生时,性能不会平稳下降——而是会断崖式下跌。在低配机器上,这会将帧率降至个位数。有时甚至会毫无预警地直接崩溃跳出到桌面。没法玩儿。

这就是两个流式加载系统所要解决的问题,这也是它们比本篇开发日志中的任何单一优化都更重要的原因。在开启和关闭这两种系统的情况下测量同一场景,它们消除了三分之一到二分之一的显存占用,并且这一比例在任何材质质量设置下都保持不变。

这种节省实际上与材质分辨率无关,而是关于游戏内容同时常驻内存的多少,无论你处于什么质量设置下,常驻的内容量大致都是相同的。这也意味着这种优化带来的收益恰好落在最需要它的地方——不是那些原本就有富余的机器上,而是那些显存耗尽的机器上。

事实上,中、低画在显存占用上好像没啥区别。大家可以随意调整图形选项,并让我们知道你们的看法。

渲染目标是预留用于绘制的显存空间,例如静态肖像或屏幕的帧缓冲区。这是我们可以独立于流式加载之外优化显存的一个领域。

当游戏渲染一帧时,它会在一组全屏缓冲区中工作——场景本身、环境光遮蔽通道、各种模糊和后处理步骤。这些以前都采用高精度格式,将每个像素存储为四个16位浮点值,在高分辨率和抗锯齿下,这会累积成相当大的一笔显存,而它们纯粹只是作为暂存空间存在。

其中大部分精度并未被使用。地图在渲染后会进行增亮处理,因此存储的值实际上位于可用范围的下半部分,事实证明,采用一半大小、将精度集中在数据实际所在位置的格式就足够了。将这些缓冲区中最大的一半缩减,可以节省渲染器持有内存的相当大一部分,而且与流式加载不同,它完全不会带来任何物体突现的代价。

这并不是一行代码就能解决的改动,这也是我如果跟你喝咖啡时会聊起的话题。切换格式破坏了战争迷雾——它在迷雾最浓的地方变得十分块状。最显而易见的解释是我们丢失了黑暗中的精度,但这个解释是错的;我们花了很长时间推翻了它。实际发生的情况是,地图将其云层阴影和云层作为对相同像素的两个独立通道进行绘制,因此第一通道的结果被以降低的精度写入缓冲区,然后作为第二通道的起点被读取。在高精度下,这种往返是无损的。而在较小的格式下,它被量化了两次,并且正好发生在迷雾最浓郁的地方。

解决方案是将这两个通道合并为一个,这样复合效果就在着色器内部以全精度计算并存储一次。这也消除了一个全屏绘制,因此它比它所替代的东西稍微省了一点成本。地面迷雾中一个独立且陈旧的条纹瑕疵(在高精度下也存在,我们只是之前没注意到)通过在帧的最后阶段添加少许抖动得到了修复。

\[前后对比:内存占用减少后的地图]

有时会有人问我是如何工作的。通常情况下,追踪当前的游戏日tick速率上升没啥意义,因为频繁的更新会频繁地添加更多功能,但随着我们接近发布日,工期变得更加紧张。我从获取夜间挂机运行游戏的日志文件开始,这些日志可能记录了600到1000年不等。

日志为我提供了100年的图表(如果需要从宏观视角观察,则会更多),它帮我找到了自上次以来发生变化的系统类别。一个系统通常既包含脚本又包含代码,所以下一步就是弄清楚是什么改变了这些数字。

一旦我有了潜在嫌疑对象的名单,就该进行一轮上门送温暖了:这玩意谁改的,是本来就打算这么写还是不小心写的,谁能提供更多信息来帮我确定下一步该怎么做?

由此,我通常可以确定在代码的什么位置寻找更多信息。单纯匆忙一瞥很少有帮助,因为一行代码可能隐藏着巨大的复杂性,但至少我对大部分代码都很熟悉,足以收集基础信息。

如果代码可以被隔离到一个独立的事物中,就该在本地对其进行测量了。像Very Sleepy或类似的外部采样性能分析器通常可以精确定位瓶颈,但我还有一个游戏内置的插桩性能分析器。“插桩”意味着它能精确测量一个函数所花费的时间,然后将结果汇总,这样我既能知道某段代码被调用了多少次,也能知道在那里总共花费了多少时间。

在大多数情况下,我会去策划,向他们解释我发现了什么,而他们总是能确切地听懂我在说什么(即使我自己有时候并不完全懂)。

难得的是,我甚至有机会重写一小段代码来让它运行得更快!

在内存方面,在同一台机器上运行游戏十年后进行测量,与我们启动本项目时相比,内存占用减少了大约1/6。这并不是特别大的变动,但考虑到玩家在玩的时候可能同时挂着浏览器和聊天软件,这里能省超过1GB的内存。这对于内存刚达到及格线的机器来说,近似于流畅和掉帧的区别。

其中一项修复是在动画数据中,我们发现自己存储了许多次相同的运动数据。当动画从一个骨骼重定向到另一个骨骼时,重定向后的副本过去会复制其所有的关键帧数据,尽管它们存储的都是相同的运动数据;现在它会读取原始数据并在采样时应用四肢比例的差异。此外,由几个不同骨骼加载的同一个动画文件过去每个骨骼会从磁盘读取一次,每个骨骼都在内存中产生了自己的独立相同副本。

另一个节省来自以类似方式处理的动画元数据,其用量从1200MB降至约12MB,减少了100倍。

从所有统计数据中得出一个有趣的发现是:内存占用在战役运行到第五年左右时会趋于平稳。游戏持有的内容几乎全都是在启动时加载的,而不是在游玩过程中累积的,这意味着一个长战役并不会比一个短战役明显更沉重。

我们在动画数据和其他系统中还发现了各种其他有趣的机会,我们将在未来继续探索。

仅流式加载所需内容就完成了缩短启动时间的大部分重任,但这并非全部,其余的工作主要在于找出游戏根本不需要去做的工作。

最大的单项开销甚至不是某个系统,而是一个函数。加载本地化文本(分布在一千多个文件中的游戏文本)过去是在一个单线程上逐个文件进行的,而其他CPU核心则处于闲置状态。在低配电脑上,这是整个启动配置文件中最大的单项条目,占你盯着加载界面时间的相当大一部分。如果并行读取这些文件并在之后以固定顺序合并,就会把这段耗时缩短到可以忽略不计的舍入误差。

游戏过去还会连续算两次校验码,第一遍还没算完就丢掉草稿纸并开始算第二遍。这个问题已经修复,剩下的单次遍历现在会在主菜单出现之后运行,而不是在它前面运行,因此你可以更早地到达菜单并在它完成的同时开始单人游戏。多人游戏按钮则会等待它完成,因为校验和是告诉两个玩家他们正在运行相同版本游戏的关键。

\[启动时间性能分析前后对比(“Scribe”版本84秒 VS 「 唯主是依」版本16秒)]

大目录过去会被多次扫描以查找文件,因为每次扫描都在问略有不同的问题,并且所有九种本地化语言都会在启动时被枚举,即使你只会阅读其中一种。

在同一台机器上,Vulkan渲染器的启动速度比DirectX 11慢大约1.8倍,结果证明这是由单行代码引起的。当一个线程将材质上传到显卡时,它等待的是图形队列清空,而不是等待它自己的上传完成——因此八个资产线程中的每一个都吸收了其他所有七个线程的工作。等待正确的事物让这两个后端拉近了差距。

另一个发现是,在Linux系统上,与相同配置的Windows相比,我们进入主菜单的速度似乎快整整一倍。文件系统代码并没有实质性差异,所以大概是天意在发力。

我们将新的流式加载选项保留在设置中。如果出现意想不到的问题,仍然可以像以前一样使用“全部预加载”系统。但我们更希望解决任何新出现的问题,让这个更快的新系统为每位玩家服务。

我们希望在游戏不断发展的同时保持模拟速度稳定,并开始把历经五年开发所累积消耗的内存和加载时间“还”给玩家。在第一点上,在我们测量的机器上,我们已经非常接近“Scribe”更新的水平,并预计会保持在这一附近。在第二点上,游戏对显存的需求大大减少,系统内存的占用也明显下降,并在低配电脑上以极小的一部分时间就到达了主菜单(高配电脑上也是如此,只是区别没那么明显)。

我们远没有完成所有这些工作!其中一些工作正在紧锣密鼓地进行中——就在我写下这些文字和你游玩它们之间的时间里。角色互动的性能开销下降了,但尚未完全回到夏初时的水平,还有一堆动画数据正等待着我们已经知道如何进行的重构。其余的则是一如既往的常规业务:仍在开发中的游戏将继续引入需要优化的新内容,这完全不是问题。它还会让我忙上一阵子!

真实的数据将随着版本的发布而到来,所有玩家都可以对其进行测量,我们很高兴听到你们的测试结果,无论它们是惊艳、优秀还是仅仅凑合。

祝你游玩愉快!

技术主管 Divine 悄悄来到这里,带来关于即将推出的补丁系统需求的小更新。

正如 Carl-Henrik 上面所概述的,各项数据正以惊人的速度在各个方面缩减,那么这对于我们游戏的最低硬件规格意味着什么呢?好消息是,尽管在新中添加了所有的内容和功能,我们仍将保持与以前补丁版本相同的低最低硬件规格。上面解释的所有炫酷技术,在让我们能够让十年前的硬件依然能够流畅运行现代游戏方面发挥了巨大作用。

对于「丝绸与白银」Silk and Silver,我们开始对游戏和代码库进行面向未来的升级,以更好地支持未来的开发周期。我们将转向更现代的编译器工具集,以便让代码库兼容 C++20 标准。因此,我们需要更新 Linux 和 Mac 玩家所需的操作系统版本。因此,在今年晚些时候“丝绸与白银”补丁发布时,游戏将需要 Ubuntu 24.04 LTS(原为Ubuntu 18.04 LTS)和 MacOS 15 Sequoia(原为MacOS Catalina)。在我们开始考虑更新编译器工具链之前,我们打算对这些新的操作系统版本提供几年的支持。


后记:但这究竟能走多远?

几年来,黑色工作室一直举办名为 Black Forge Jam 的游戏创作活动。出于对公司历史的传承,我自己的 Paradox 之旅正是从开发 Mega Drive 游戏开始的,所以还有什么比开始制作《十字军之王》的Mega版更合适的呢?

这是对性能和内存优化的终极挑战!

我想向大家汇报,我已经迈出了第一步。去年12月,我花了几天时间用68000汇编语言从头开始将《十字军之王》“移植”到世嘉 Mega Drive 上,荒谬的是,它居然能读取真正的CK3数据。实地头衔文件和省份地图在离线状态下被转换为紧凑的表格,直接烧录到卡带中,控制台在启动时根据它们重建了欧洲的政治地图——59个领地和1028个伯爵领,每个都对齐到16种可用颜色的最近色,可以用十字键在周围滚动。屏幕上有HUD边框,还有哈罗德·戈德温森的肖像正注视着你。

\[在模拟器中运行的 Mega《十字军之王》。地图使用的是真实的 CK3 数据]

这里没有游戏性。没有模拟、没有文字、没有声音——声卡在启动时被关闭,且再也没有被打开过。我遇到了一堵墙,读过上面显存章节的人都会觉得很熟悉:Mega Drive 一次只能容纳1536个独特图块,而地图需要大约六千个,因此它在画到欧洲中途时就直接停止绘制了。我在最后一次提交中留下的备注完整写着:“需要节约更多显存。”

从技术上讲,所有国家和地区的名称都已经在内存中准备好显示,伯爵领在游戏运行时也可以从一个领地转移到另一个领地,但目前还没有实装任何玩法。事实证明,大部分的工作周时间对于一个16位游戏来说也远远不够。我不确定是否还会有更多时间在这个项目上投入精力,但我对它切实可行能做些什么持开放态度!

我可以使用大约4MB的ROM,或者通过定制卡带有条件地将其中一部分换成RAM。这里有64kb的RAM和64kb的VRAM。目前ROM大小为104024字节。这与试图让常见的低端PC满速运行《十字军之王III》有着些许不同。

【来源:steam】
我想了解这个游戏:
官网 专区 下载 礼包
关于十字军之王3的新闻
17173想了解一下大家怎么看17173的,以便提供更好的阅读体验,所以请各位路过的大佬们花一分钟时间看看这个问卷吧 一键参与