用 Vulkan 渲染小文本?

Rendering small text with Vulkan?

字体渲染库(比如 freetype)提供了一个函数,它可以获取轮廓字体文件(比如 .ttf)和字符代码,并在主机内存中生成相应字形的位图。

对于小文本(比如最大 30x30 像素的字形)将这些字形渲染到 Vulkan 帧缓冲区的最有效方法是什么?

我考虑过的一些选项可能是:

  1. 每次按需使用字体渲染库渲染字形,使用主机代码将它们 blit 到单个主机端图像,包含整个“文本框”,传输主机端图像文本框到设备本地图像,然后使用要绘制的文本框的片段着色器/图像采样器渲染四边形(像普通图像一样)。

  2. 在程序启动时循环遍历主机端的所有字形,将它们呈现为字形位图。与 1 相同,但从缓存的字形位图中 blit(占用大约 1 MB 主机内存)。

  3. 将字形位图单独缓存到设备本地图像中。不是咬住主机端,而是为每个字形设备端渲染一个四边形,并每次将图像采样器设置为相应的字形。 (不确定绘制调用如何工作?每次使用不同的组合图像采样器,每个字形一个绘制调用?)

  4. 将所有的字形位图缓存到一个大的设备端图像中(比如布置在一个大网格中)。使用单个设备端组合图像采样器,并推送参数来描述包含字形图像的子区域。每个字形一次绘制调用,每次更新推送参数。

  5. 与 4 类似,但使用单个实例化绘制调用,而不是推送参数,而是使用随实例变化的输入属性。

  6. 还有别的吗?

我的意思是,像 Unreal 或 Unity 或 Godot 等常见游戏引擎如何解决这个问题?是否有典型的方法或最佳实践?

首先,一些注意事项:

  1. 使用 freetype 光栅化一个大约 30px 的字形可能需要 on the order of 10μs。这是一个非常小的一次性成本,但渲染例如。每帧 100 个字形会严重消耗您的帧预算(如果我们假设数学简单到 100 * 10μs == 1ms)。

  2. 状态更改(如描述符更新)相对昂贵。更改您呈现的每个字符的绑定描述符具有不可忽略的成本。这可以通过批处理字符绘制(绘制所有 A,然后绘制 B 等)来限制,但使用推送常量通常是 fastest.

  3. 具有小网格(例如四边形或单个三角形)的实例化绘图在某些 GPU 上可能非常慢,因为它们不会在一个 GPU 上安排多个实例单身wavefront/warp。如果您正在渲染具有 6 个顶点的四边形,并且单个执行单元可以处理 64 个顶点,您最终可能会浪费 58/64 = 90.6% 的可用顶点着色容量。

这表明 4 是您的最佳选择(尽管 5 可能具有可比性);您可以通过缓存绘制调用的结果来进一步优化该方法。假设您有一些菜单文本:

  1. 需要第一帧,将所有文本渲染为中间图像。
  2. 需要的每一帧,都使用中间图像进行一次绘制调用。 (如果不需要透明度,也可以 blit 文本。)