关于 Vulkan 中的函数加载器

About function loader in Vulkan

在 Vulkan 中,我们有两个全局函数需要使用 dlopen/LoadLibrary 加载。它们是 vkGetInstanceProcAddr 和 vkGetDeviceProcAddr。

我有一个 gpu 并安装了 vulkan 驱动程序。我应该更好地在运行时或链接时加载库吗?我将不同的逻辑设备(从相同的 gpu 创建)提供给 vkGetDeviceProcAddr 以查询相同的功能。他们都 return 同一个地址给我。我认为重新加载可能很浪费。

我的问题是这个想法是怎么来的?是多实现还是多GPU?

我的加载器函数目前看起来像这样:

class VulkanDevice
{
public:
    VkDevice m_Device;
    void LoadAllCoreFunctions();
    void LoadExtension(const char *name);
    void LoadExtensions(const char *postfix); // for example: "KHR"

    PFN_vkCreateCommandBuffers vkCreateCommandBuffers;
    // Then a lot of function pointers.....
}

int main() {
    // After creating instance and creating device with vkCreateDevice
    VulkanDevice vkd(device);
    vkd.LoadAllCoreFunctions();
    vkd.vkCreateCommandBuffers(vkd.m_Device, ....);
}

如您所见,如果我有多个设备,重新加载会非常浪费...而且函数指针也会占用大量内存....

"instance function pointers" 和 "device function pointers" 之间的区别适用于需要更快的函数调用性能的人。

您只需 vkGetInstanceProcAddr 就可以很好地使用 Vulkan。此函数将检索 all Vulkan 函数的函数指针。这些函数指针将使用存储在您传递的各种 Vulkan 对象中的调度信息,以确定您正在与哪个设备通话。这些指针可以与 任何 实例、设备或依赖于设备的对象一起使用。

您从 vkGetDeviceProcAddr 获得的指示知道 它们适用于特定设备。他们不需要使用调度逻辑来调用该设备。所以他们的水平略低。缺点是您只能将它们与特定的VkDevice或设备派生的对象一起使用。

对于大多数人来说,调度开销可能不会大到不值得为之操心。但是,如果你真的关心这些事情,你可以选择避免它。

if I have multiple device it will be pretty wasteful to reload... and the function pointer could use a lot of memory too....

无论你是否查询它们的指针,这些函数都存在;因此,获取它们的指针只会占用您用来存储指向它们的指针的内存。 Vulkan 的 API 包含大约 140 个函数。每个函数指针 8 个字节,即 1120 字节,刚刚超过 1KB。

至于加载它们所花费的时间...如果加载 140 个函数指针花费的时间超过几 微秒,我会感到震惊。您在启动时一次

In Vulkan, we have two global functions that need to load with dlopen/LoadLibrary. They are vkGetInstanceProcAddr and vkGetDeviceProcAddr.

错了。只有 vkGetInstanceProcAddrvkGetDeviceProcAddr 本身可以从 vkGetInstanceProcAddr.

加载

Should I better load library at runtime or linktime? I give difference logical devices(created from the same gpu) to vkGetDeviceProcAddr to query the same function. They both return the same address to me.

VkInstance 获取从 vkGetInstanceProcAddr 获得的命令被限制为与用于获取它们的 instance 完全相同。

类似地,从 vkGetDeviceProcAddr 获得的命令只能与用来获得它们的 device(VkDevice 类型)一起使用。

它们可能并且经常是相同的,但您无法事先知道并在每个 platform/PC 上都以这种方式工作。您可以只加载单个命令,以测试它是同一个指针并推断其他命令也是如此 — 但这是如履薄冰,没有任何合理的好处。

为方便起见,请在 link 时间使用官方加载器执行此操作,除非您有理由不这样做。

I think the reload could be wasteful.

除非你打算用数百万个不断连接和断开连接的 GPU 制作硬件,否则不要担心它并正确加载命令。这是一次(或很少)一次的小成本,它会在您之后无疑会做的所有渲染中迅速摊销。

也没有"reload"。新指针可以与旧指针和平共处。在 C++ 中,您可能会将这些成员函数设为实例或设备...

My question is how this idea come from? Is it for multi-implementation or multi-gpus?

是的,有点。

你肯定会看到,如果你有两个 GPU(来自不同供应商),具有不同的驱动程序文件(通常是一些 *.dll 或其他平台上的等效文件),则必须选择指向正确文件的 fpointer不知何故。

vkGetInstanceProcAddr 解决它,以便它为您提供指向另一个选择和调用正确指针的函数的指针。 ("Everything can be solved by another level of indirection",对吧?)。静态加载 Khronos/Official/LunargSDK 加载程序可能会做类似的事情。

对于 vkGetDeviceProcAddr,您只需提供确切的设备,然后它就会简单地为您提供指向该特定 GPU 驱动程序的直接函数指针。

实例也可能发生同样的情况(加载程序必须只导出 vkGetInstanceProcAddr,其余的可以在其他地方)。尽管通常(如 The Loader 的情况)它会导出所有实例级甚至间接设备级函数指针以方便使用。

As you can see, if I have multiple device it will be pretty wasteful to reload... and the function pointer could use a lot of memory too....

真的没那么多CPU-浪费时间,如前所述。

如果您没有那几 kB 的指针,您可能无论如何都无法创建可行的基于 Vulkan 的应用程序。您可以只加载您实际使用的那些命令,但我看不出这种过早优化的原因。您是否有一些特殊的硬件需要如此绝望的措施或进行 64K 演示?