什么是GPU驱动渲染?

7

现在我从不同的地方听到了所谓的 GPU 驱动渲染,它是一种全新的渲染范式,完全不需要绘制调用,并且它被新版本的 OpenGL 和 Vulkan API 支持。有人能够解释一下它在概念层面上是如何工作的,以及与传统方法的主要区别是什么吗?

1个回答

28

概述

为了渲染场景,需要进行多项操作。您需要遍历场景图以确定存在哪些对象。对于每个存在的对象,您现在需要确定它是否可见。对于每个可见的对象,您需要确定其几何形状存储在何处,将使用哪些纹理和缓冲区来渲染该对象,将使用哪些着色器来渲染该对象等等。然后您就可以渲染该对象。

处理此过程的“传统”方法是由 CPU 处理。场景图位于 CPU 可访问的内存中。CPU 对该场景图进行可见性剔除。CPU 获取可见对象并访问一些有关几何形状(OpenGL 缓冲区对象和纹理名称、Vulkan 描述符集和 VkBuffer 等)和着色器等方面的 CPU 数据,将其作为状态数据传输到 GPU。然后,CPU 发出 GPU 命令以使用该状态渲染该对象。

现在,如果我们回到更远的过去,最“传统”的方法根本不涉及 GPU。 CPU 只需获取这些网格和纹理数据,进行顶点变换、光栅化等操作,在 CPU 内存中生成图像。然而,我们开始将其中的一些内容转移到单独的处理器上。我们从光栅化开始(最早的图形芯片只是光栅化器;CPU 执行所有顶点 T&L)。然后,我们将顶点变换合并到 GPU 中。在这样做时,我们开始使用 GPU 可访问的内存存储顶点数据,以便 GPU 可以按自己的时间读取它。

我们之所以将所有这些内容转移至单独的处理器,有两个原因:GPU 的速度(大大)更快,而 CPU 现在可以将其时间用于其他事情。

GPU驱动渲染只是这个过程的下一阶段。我们从没有GPU到光栅化GPU,再到顶点GPU,现在到了场景图级别的GPU。"传统"方法将如何渲染卸载到GPU上;GPU驱动渲染则卸载了决定何时渲染。

机制

现在,我们之所以一直没有这样做,是因为基本的渲染命令都需要来自CPU的数据。glDrawArrays/Elements从CPU中获取许多参数。因此,即使我们使用GPU生成该数据,我们也需要完全的GPU/CPU同步,以便CPU可以读取数据...并将其立即返回给GPU。

那没用。

OpenGL 4为我们提供了各种形式的间接渲染。基本思想是,它们不是从函数调用中获取这些参数,而是存储在GPU内存中的数据。CPU仍然必须进行函数调用以启动渲染操作,但该调用的实际参数只是存储在GPU内存中的数据。

另一半需要GPU能够以间接渲染可读取的格式将数据写入GPU内存的能力。历史上,GPU上的数据只有一个方向:读取数据以将其转换为渲染目标中的像素。我们需要一种在GPU上从其他任意数据生成半任意数据的方法。

过去用于此目的的旧机制是滥用变换反馈,但现在我们只使用SSBOs或无法实现时使用image load/store。计算着色器也有所帮助,因为它们被设计为超出标准渲染管线之外,因此不受其限制。
GPU驱动渲染的理想形式使场景图成为渲染操作的一部分。还有较小的形式,例如仅让GPU执行每个对象的视口裁剪。但是,让我们看看最理想的流程。从CPU的角度来看,这看起来像:
1. 在GPU内存中更新场景图。 2. 发布一个或多个计算着色器,生成多次绘制间接呈现命令。 3. 发布一个单独的多次绘制间接调用,绘制everything
当然,没有免费的午餐。在GPU上进行完整的场景图处理需要以对GPU处理有效的方式构建场景图。更重要的是,可见性剔除机制必须考虑到有效的GPU处理。这是我不会在这里解决的复杂性。
实施
相反,让我们看看使绘图部分工作的细节。我们必须解决很多问题。
看,间接渲染命令仍然是一个普通的渲染命令。虽然多绘制形式可以绘制多个不同的“对象”,但它仍然是一个CPU渲染命令。这意味着,在此命令的持续时间内,{{所有渲染状态}}都是固定的。
因此,此多重绘制操作范围内的所有内容必须使用相同的着色器、绑定的缓冲区和纹理、混合参数、模板状态等。这使得实现GPU驱动的渲染操作有点复杂。
状态和着色器
如果您需要混合或类似基于状态的渲染操作差异,则需要发出另一个渲染命令。因此,在混合情况下,您的场景图处理将不得不计算多组渲染命令,每组命令针对特定的混合模式集。您还可能需要使用此系统对透明对象进行排序(除非您正在使用OIT机制进行渲染)。因此,您不只是拥有一个渲染命令,而是有几个小的渲染命令集。
但是,此次练习的重点并不是只有一个渲染命令;重点是与您渲染的物品数量无关的CPU渲染命令数量不会改变。无论场景中有多少对象,CPU都将发出相同数量的渲染命令。
在着色器方面,这种技术需要一定程度的“ubershader”风格:您需要很少量但非常灵活的着色器。您需要对着色器进行参数化,而不是拥有数十个或数百个着色器。
然而,事情可能会以这种方式发展,特别是在延迟渲染方面。延迟渲染的几何处理通常使用相同的处理方式,因为它们只是进行顶点变换和提取材质参数。最大的区别通常在于进行蒙皮与非蒙皮渲染,但这实际上只有2种着色器变化。你可以类似地处理混合情况。
说到延迟渲染,GPU驱动的进程也可以遍历光线图,从而生成绘制调用和渲染数据,以进行照明传递。因此,虽然照明传递将需要单独的绘制调用,但无论灯光数量如何,它仍将只需要一个多重绘制调用。
缓冲区
这里就开始有趣了。如果GPU正在处理场景图,那么意味着GPU需要以某种方式将多重绘制命令中的特定绘制与该特定绘制所需的资源关联起来。它还可能需要将数据放入这些资源中,例如给定对象的矩阵变换等。
哦,你还需要以某种方式将顶点输入数据绑定到特定的子绘制。
最后一部分可能是最复杂的。OpenGL/Vulkan标准顶点输入方法所提取的缓冲区是状态数据;它们不能在多重绘制操作的子绘制之间更改。
你最好的选择是尝试将每个对象的数据放入相同的缓冲对象中,使用相同的顶点格式。实质上,你有一个巨大的顶点数据数组。然后,你可以使用子绘制的绘制参数来选择使用哪些缓冲区的部分。
但是对于每个对象的数据(矩阵等),通常会使用UBO或全局uniform,我们该怎么办呢?如何在CPU渲染命令中有效地更改缓冲区绑定状态?嗯...你不能。所以你需要“作弊”。首先,你要意识到SSBO可以任意大。所以你不需要真正改变缓冲区绑定状态。你需要的是一个包含每个对象数据的单个SSBO。对于每个顶点,VS只需从巨大的数据列表中选择出正确的子绘制数据即可。这是通过一个特殊的顶点着色器输入完成的:gl_DrawID。当你发出多次绘制命令时,VS会得到一个表示此子绘制操作在多次绘制命令中的索引的输入值。因此,你可以使用gl_DrawID来索引一个每个对象数据表,以获取该特定对象的适当数据。这也意味着生成这个子绘制的计算着色器还需要使用该子绘制的索引来定义在数组中放置该子绘制的每个对象数据的位置。因此,编写子绘制的CS还需要负责设置与子绘制相匹配的每个对象数据。纹理OpenGL和Vulkan对绑定的纹理数量有相当严格的限制。实际上,相对于传统的渲染来说,这些限制是相当大的,但在GPU驱动的渲染领域,我们需要一个单个CPU渲染调用来潜在地访问任何纹理。这更难了。
现在,我们有gl_DrawID;结合上面提到的表格,我们可以检索每个对象的数据。那么:我们如何将其转换为纹理?
有多种方法。我们可以将一堆2D纹理放入数组纹理中。然后,我们可以使用gl_DrawID从我们的SSBO中获取数组索引,该数组索引成为我们用于获取“自己”纹理的数组层。请注意,我们不直接使用gl_DrawID,因为多个不同的子绘制可能会使用相同的纹理,并且设置绘制调用数组的GPU代码不能控制纹理在数组中出现的顺序。
数组纹理有明显的缺点,其中最显著的是我们必须尊重数组纹理的限制。数组中的所有元素都必须使用相同的图像格式。它们必须全部具有相同的大小。此外,数组纹理中的数组层数量有限,因此您可能会遇到限制。
与数组纹理相比,替代方案在API线路上有所不同,但基本上归结为同一件事:将数字转换为纹理。
在OpenGL中,您可以使用无绑定纹理(适用于支持该功能的硬件)。这个系统提供了一种机制,允许您生成一个64位整数句柄来表示特定的纹理,并将此句柄传递给GPU(因为它只是一个整数,所以可以使用任何机制),然后将这个64位句柄转换成sampler类型。因此,您可以使用gl_DrawID从每个对象数据中获取64位句柄,然后将其转换为相应类型的sampler并使用它。
在Vulkan中,您可以使用采样器数组(适用于支持该功能的硬件)。请注意,这些不是数组纹理;在GLSL中,这些是数组化的sampler类型:uniform sampler2D my_2D_textures[6000];。在OpenGL中,这将是一个编译错误,因为每个数组元素代表一个纹理的不同绑定点,您不能有6000个不同的绑定点。在Vulkan中,一个数组采样器只代表一个单一描述符,无论该数组中有多少元素。 Vulkan实现对此类数组中可以有多少元素有限制,但是支持该功能的硬件(shaderSampledImageArrayDynamicIndexing)通常会提供一个慷慨的限制。
因此,您的着色器使用gl_DrawID从每个对象数据中获取索引。该索引通过从采样器数组中获取值来转换为sampler。该数组描述符中纹理的唯一限制是它们必须都是相同类型和基本数据格式(sampler2D的浮点二维,usamplerCube的无符号整数立方体贴图等)。格式、纹理大小、mipmap计数等的具体细节都是无关紧要的。

如果您担心Vulkan的采样器数组与OpenGL的无绑定的成本差异,请不要担心; 无绑定实现只是在背后执行此操作


1
感谢您的出色回答! - Victor
2021年,SSBO比普通UBO慢吗? - Ciberman

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