NVRHI的主要功能及通过编写可移植的渲染代码

网友投稿 232 2022-10-24

NVRHI的主要功能及通过编写可移植的渲染代码

另一方面,这更接近于对 GPU 的硬件访问,这意味着应用程序必须自己管理这些东西,而不是依赖 GPU 驱动程序。使用这些 API 绘制单个三角形的基本“ hello world ”程序可以扩展到 1000 行或更多代码。在复杂的渲染器中,如果不系统地管理 GPU 内存、描述符等,可能会很快变得难以控制。

如果应用程序或引擎必须使用多个图形 API ,可以通过两种方式完成:

复制渲染代码以分别使用每个 API 。这种方法有一个明显的缺点,就是必须开发和维护多个独立的实现。

在图形 API 上实现一个抽象层,在公共接口中提供必要的功能。这在开发和维护抽象层方面有一个不同的缺点。大多数主要的游戏引擎都实现了第二种方法。

NVRHI 没有提供一些与便携性相关的功能。首先,它不会在运行时编译着色器或读取着色器反射数据以动态绑定资源。事实上, NVRHI 根本不在运行时处理着色器。该应用程序提供特定于平台的着色器二进制文件,即 DXBC 、 DXIL 或 SPIR-V blob 。 NVRHI 将其直接传递给底层图形 API 。匹配绑定布局由应用程序决定,并由底层图形 API 验证。其次, NVRHI 不创建图形设备或窗口。这也取决于应用程序或其他库,如GLFW。

资源生命周期管理

绑定布局和绑定集

自动资源状态跟踪

上传管理

与图形 API 的交互

着色器置换

资源生命周期管理

在 Vulkan 和 D3D12 中,应用程序必须注意仅销毁 GPU 不再使用的设备资源。如果仔细规划资源使用情况,这可以用很少的开销完成,但问题在于规划。

NVRHI 几乎完全遵循 D3D11 资源生命周期模型。资源(如缓冲区、纹理或管道)具有引用计数。复制资源句柄时,引用计数将递增。当句柄被销毁时,引用计数将递减。当最后一个句柄被销毁并且引用计数达到零时,资源对象被销毁,包括底层图形 API 资源。但 D3D12 也是这么做的,对吗?不完全是。

NVRHI 还保留对命令列表中使用的资源的内部引用。打开命令列表进行录制时,将创建命令列表的新实例。该实例保存对其使用的每个资源的引用。当命令列表关闭并提交以供执行时,实例与围栏或信号量值一起存储在队列中,可用于确定实例是否已在 GPU 上完成执行。之后可以立即重新打开相同的命令列表进行录制,即使之前的实例仍在 GPU 上执行。

此行为可通过以下代码示例显示:

与 D3D12 和 Vulkan 不同,在 NVRHI 中,当应用程序创建资源、使用资源并立即释放资源时,此处显示的“触发并忘记”模式非常好。

如果应用程序执行多个draw调用,并且为每个draw调用绑定了大量资源,那么这种类型的资源跟踪是否会变得昂贵。不是真的。Draw调用和分派不处理单个资源。纹理和缓冲区被分组为不可变的绑定集,这些绑定集被创建,保存对其资源的永久引用,并作为单个对象进行跟踪。

因此,当在命令列表中使用某个绑定集时,命令列表实例仅存储对该绑定集的引用。如果绑定集已绑定,则跳过该存储,以便使用相同绑定重复调用 draw 不会增加跟踪成本。我将在下一节更详细地解释绑定集。

另一个有助于减少资源生存期跟踪带来的 CPU 开销的方法是绑定集和加速结构上的trackLiveness设置。当此参数设置为false时,不会为该特定资源创建内部引用。在这种情况下,应用程序负责保留自己的引用,而不是在资源使用时释放它。

绑定布局和绑定集

NVRHI 具有独特的资源绑定模型,旨在实现安全性和运行效率。如前所述,图形或计算管道使用的各种资源被分组到绑定集中。

与根签名和描述符集布局一样, NVHRI 绑定布局用于创建管道。可以使用多个绑定布局创建单个管道。根据资源的修改频率将资源分为不同的组,或者将不同的资源集绑定到不同的管道阶段,这些都很有用。

以下代码示例显示了如何使用一个绑定布局创建基本计算管道:

auto layoutDesc = nvrhi::BindingLayoutDesc() .setVisibility(nvrhi::ShaderType::All) .addItem(nvrhi::BindingLayoutItem::Texture_SRV(0)) // texture at t0 .addItem(nvrhi::BindingLayoutItem::ConstantBuffer(2)); // constants at b2 // Create a binding layout.nvrhi::BindingLayoutHandle bindingLayout = device->createBindingLayout(layoutDesc); auto pipelineDesc = nvrhi::ComputePipelineDesc() .setComputeShader(shader) .addBindingLayout(bindingLayout); // Use the layout to create a compute pipeline.nvrhi::ComputePipelineHandle computePipeline = device->createComputePipeline(pipelineDesc);

只能从匹配的绑定布局创建绑定集。匹配意味着布局必须具有相同数量、相同类型、绑定到相同插槽、顺序相同的项目。这看起来可能是冗余的, D3D12 和 Vulkan API 在其描述符系统中的冗余更少。这种冗余非常有用:它使代码更加明显,并且允许 NVRHI 验证层捕获更多的 bug 。

auto bindingSetDesc = nvrhi::BindingSetDesc() // An SRV for two mip levels of myTexture. // Subresource specification is optional, default is the entire texture. .addItem(nvrhi::BindingSetItem::Texture_SRV(0, myTexture, nvrhi::Format::UNKNOWN, nvrhi::TextureSubresourceSet().setBaseMipLevel(2).setNumMipLevels(2))) .addItem(nvrhi::BindingSetItem::ConstantBuffer(2, constantBuffer)); // Create a binding set using the layout created in the previous code snippet.nvrhi::BindingSetHandle bindingSet = device->createBindingSet(bindingSetDesc, bindingLayout);

由于绑定集描述符也包含创建绑定布局所需的几乎所有信息,因此可以通过一个函数调用同时创建这两个信息。这在创建仅需要一个绑定集的某些渲染过程时可能很有用。

绑定集是不可变的。创建绑定集时, NVRHI 从 D3D12 上的堆中分配描述符,或在 Vulkan 上创建描述符集,并用必要的资源视图填充它。

稍后,当绑定集用于绘制或分派调用时,绑定操作是轻量级的,并转换为相应的图形 API 绑定调用。渲染时不会创建或复制描述符。

自动资源状态跟踪

在 D3D12 和 Vulkan API 中,改变资源状态并在图形管道中引入依赖关系的显式屏障都是一个重要部分。它们允许应用程序最小化管道依赖项和气泡的数量,并优化它们的位置。通过从驱动程序中删除该逻辑,它们同时减少了 CPU 开销。这主要与绘制大量几何体的紧密渲染循环有关。大多数情况下,尤其是在编写新的渲染代码时,处理障碍非常烦人且容易出现错误。

NVHRI 实现了一个系统,该系统跟踪每个资源的状态,以及每个命令列表的子资源(可选)。当命令与资源交互时,资源将转换为该命令所需的状态(如果尚未处于该状态)。例如,writeTexture命令将纹理转换为CopyDest状态,随后从纹理读取的绘制操作将纹理转换为ShaderResources状态。

我前面说过, NVRHI 会根据每个命令列表跟踪每个资源的状态。应用程序可以以任意顺序或并行方式记录多个命令列表,并在每个命令列表中以不同方式使用相同的资源。因此,您无法全局或每个设备跟踪资源状态,因为在记录命令列表时需要导出屏障。执行命令列表时,全局跟踪可能不会按照与设备命令队列上实际资源使用情况相同的顺序进行。

因此,您可以分别跟踪每个命令列表中的资源状态。在某种意义上,这可以看作是一个微分方程。您知道命令列表中的状态是如何变化的,但不知道边界条件,也就是说,当您按执行顺序进入和退出命令列表时,每个资源都处于哪个状态。

应用程序必须为每个资源提供边界条件。有两种方法可以做到这一点:

Explicit:打开命令列表后使用beginTrackingTextureState和beginTrackingBufferState功能,关闭命令列表前使用setTextureState和setBufferState功能。

Automatic:创建资源时使用TextureDesc和BufferDesc结构的initialState和keepInitialState字段。然后,使用资源的每个命令列表在进入命令列表时都假定它处于初始状态,并在离开命令列表之前将其转换回初始状态。

在这里,您 MIG 想知道如何避免资源状态跟踪的 CPU 开销,或者手动优化屏障放置。好吧,你可以!命令列表具有setEnableAutomaticBarriers功能,可完全禁用自动安全栅。在此模式下,在需要屏障的位置使用setTextureState和setBufferState功能。它仍然使用相同的状态跟踪逻辑,但频率可能更低。

上传管理

NVRHI 自动化了现代图形 API 的另一个方面,这一点通常很烦人。这就是 GPU 对上传缓冲区的管理和对其使用情况的跟踪。

通常,当必须从 CPU 对每帧或每帧多次更新某些纹理或缓冲区时,会分配一个分级缓冲区,其大小比资源内存需求大数倍。这将在 GPU 上启用多个正在运行的帧。或者,大型暂存缓冲区的部分在运行时进行子分配。使用 NVRHI 实现相同的策略是可能的,但是有一个内置的实现可以很好地适用于大多数用例。

每个 NVRHI 命令列表都有自己的上载管理器。调用writeBuffer或writeTexture时,上载管理器会尝试查找 GPU 不再使用的现有缓冲区,该缓冲区可以容纳必要的数据。如果没有可用的缓冲区,将创建一个新的缓冲区并将其添加到上载管理器的池中。将提供的数据复制到该缓冲区中,然后将复制命令添加到命令列表中。 GPU 使用的缓冲区的跟踪是自动执行的。

ConstantBufferStruct myConstants;myConstants.member = value; // This is all that's necessary to fill the constant buffer with data and have it ready for rendering.commandList->writeBuffer(constantBuffer, myConstants, sizeof(myConstants));

上载管理器从不释放其缓冲区,也不会与其他命令列表共享缓冲区。也许一个应用程序正在进行大量的上传,例如在场景加载期间,然后切换到上传强度较小的操作模式。在这种情况下,最好为上传活动创建一个单独的命令列表,并在上传完成后释放它。这将释放与命令列表关联的上载缓冲区。

无需等待 GPU 完成从上载缓冲区复制数据。在复制完成之前,前面描述的资源生存期跟踪系统不会释放上载缓冲区。

与图形 API 的交互

有时,有必要避开抽象层,直接使用底层图形 API 进行操作。也许您必须使用 NVRHI 不支持的某些功能,在示例应用程序中演示一些 API 用法,或者使可移植呈现代码与来自其他地方的本机资源一起工作。 NVRHI 使做这些事情相对容易。

每个 NVRHI 对象都有一个getNativeObject函数,该函数返回所需类型的底层 API 资源。预期的类型被传递给该函数,如果该类型可用,它只返回非 NULL 值,以提供某种类型安全性。

支持的类型包括ID3D11Device或ID3D12Resource等接口和vk::Image等句柄。此外, NVRHI 纹理对象具有getNativeView功能,可以创建和返回纹理视图,如 SRV 或 UAV 。

例如,为了在 NVRHI 命令列表的中间发布一些本地的 D3D12 渲染命令,您 MIG HT 使用代码,如下面的示例:

着色器置换

这里要提到的最后一个生产力特性是 NVRHI 附带的批处理着色器编译器。这是一项可选功能,没有它, NVRHI 完全可以正常工作。 NVRHI 接受通过其他方式编译的着色器。尽管如此,它还是一个有用的工具。

通常需要使用多个预处理器定义组合编译同一着色器。但是,例如, VisualStudio 为着色器编译提供的本机工具根本无法轻松完成此任务。

应用程序可以加载包含所有着色器排列的文件,并将其连同预处理器定义及其值的列表一起传递给nvrhi::utils::createShaderPermutation或nvrhi::utils::createShaderLibraryPermutation。如果文件中存在请求的置换,则会创建相应的着色器对象。如果没有,将生成一条错误消息。

结论

在这篇文章中,我介绍了 NVRHI 的一些最重要的功能,在我看来,使用这些功能是一种乐趣。

Alexey Panteleev 是 NVIDIA 开发人员和性能技术团队的杰出工程师,他专注于新渲染技术的优化、产品化和集成。他最近的工作包括地震 II RTX 、带有 RTX 的地雷探测器以及各种技术演示和样品,如 ReSTIR 和小行星。亚历克赛拥有博士学位。莫斯科工程和物理研究所(梅菲州立大学)计算机科学专业。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:通过使用Byte Buddy便捷创建Java Agent
下一篇:Docker部署searchcode
相关文章

 发表评论

暂时没有评论,来抢沙发吧~