在专业软件开发中,最重要的一个方面就是尽早发现错误。当然,最好的情况是我们甚至不能编写错误的代码。其次最好的是编译器可以检测到的错误。
最坏的情况是运行时错误。最难的部分隐藏在只在特定情况下运行的代码中。墨菲定律说,这种情况首次发生在顾客的环境中。
如果您使用的是 vulkan ,有几种方法可以创建运行时错误。即使 vulkan 提供了很好的验证层,您也必须运行这部分代码来检测此类错误。顺便说一句,我建议你不要在没有使用验证层的情况下用 vulkan 编程!
当使用 vulkan – hpp 时,一些运行时错误变成编译时错误 。 vulkan -hpp 是针对 vulkan api 的头报头 c ++绑定。它由 khronos 维护,作为 vulkan 生态系统的一部分,可以在 github 上找到 khronos group / vulkan – hpp 。它也是 lunargvulkan sdk 的一部分。
有助于将错误转移到编译时的特性
vulkan -hpp 通过以下功能帮助消除运行时错误:
枚举类与普通枚举比较
帮助程序类 vk::flags
结构 常量成员 stype
vk::structurechain
处理 32 位版本中的 类型安全
枚举类
使用 vulkan ,可以得到很多枚举类型。除了 vkresult ,它们都是使用以下命名方案构造的:
typedef enum vkenumname { vk_enum_name_value_a = 0, vk_enum_name_value_b = 1, …} vkenumname; 使用 vulkan -hpp ,可以为这些枚举类型中的每一种获得一个枚举类:
namespace vk{ … enum class enumname { evaluea = vk_enum_name_value_a, evalueb = vk_enum_name_value_b, … }; …} 首先,它们都位于名称空间 vk 。您可以通过定义 vulkan_hpp_namespace 来调整该命名空间。 enum 类本身没有前缀 vk ,因为这对于命名空间来说是多余的。最后,一个 enum 类的每个值都跳过前缀 vk_enum_name ,因为这个前缀又与命名空间和枚举类名冗余。它们以小写字母“ e ”作为前缀,并包含实际枚举值名称的 camelcase 版本。枚举类值不允许以数字开头,因此“ e ”前缀阻止了这一点。例如,无论你在 c 代码中使用 vk_enum_name_value_a ,都使用 vk::enumname::evaluea 代替 c ++代码。
那么,你从 vulkan -hpp 中的 enum 类得到了什么呢?毕竟,由于 vulkan 中的枚举值命名方案,不可能有两个同名的枚举值。这根本不是你的问题,而是 khronos 的 vulkan 人的问题。此外,您不太可能希望将变量或函数作为枚举值之一命名,即使这些名称已导出到全局范围。谁知道呢?有人喜欢函数名,比如 mig 。
这里重要的一点是,在 vulkan -hpp 中,没有隐式转换到 int 。不能将枚举类值赋给 int 类型,至少不会意外。也不能比较来自不同枚举类的两个枚举类值。当您比较两个枚举器中的两个枚举值时,会产生警告。 mig 是依赖于编译器的,当然,一个警告比错误更容易被忽略。
助手类 vk :: flags
对于 vulkan ,有两个数据类型对,使用以下命名方案:
typedef enum vkenumnameflagbits = { vk_enum_name_value_a_bit = 0x00000001, vk_enum_name_value_b_bit = 0x00000002, …} vkenumnameflagbits;typedef vkflags vkenumnameflags; 这里, vkflags 只是一个 uint32_t ,并且 vkenumnameflags 应该通过从 vkenumnameflagbits 中对适当的枚举值进行排序来保存相应枚举 vkenumnameflagbits 的零个或多个值。由于* flagbits 和* flags 之间除了它们的公共名称部分之外,没有真正的联系,编译器对此无能为力。允许对任意枚举值或整数应用位运算符。如果错误组合的* flags 值恰好是位的有效组合,即使它们可能不是您所希望的那样,即使验证层 mig ht 也无法捕捉到这一点。它 mig 感觉你像是在未定义的行为领域,即使程序完全按照你告诉它做的去做。这不是你想让它做的。
使用 vulkan -hpp ,可以得到相应的对:
namespace vk{ … enum class enumnameflagbits : vkenumnameflagbits { evaluea = vk_enum_name_value_a_bit, evalueb = vk_enum_name_value_b_bit, … }; using enumnameflags = flags; …} 有了这个结构,这样的尴尬局面就不会发生了。不能对枚举类值应用位运算符。 vk::enumnameflags 枚举知道相应的 vk::enumnameflagbits 。 helper 类 vk::flags 提供的功能允许您对来自同一个枚举类的枚举类值应用位运算符,但仅对这些值应用这些值。不能将它们与来自不同枚举类的值组合。在编译时,只使用允许的标志位构造标志。
结构的 stype 成员
稍微远离枚举, vulkan 中有许多结构将枚举类型 vkstructuretype 的成员 stype 作为第一个元素。对于这些结构中的每一个,都必须将该成员设置为为为该结构指定的值。在下面的代码示例中,成员 stype 必须设置为 vk_structure_type_struct_name 。
typedef struct vkstructname { vkstructuretype stype; …} vkstructname; 没那么难,但你必须做对。不要忘记设置它,也不要通过从代码中的另一个位置复制来将其设置为错误的值。例如,以下值在视觉上接近:
vk_structure_type_graphics_pipeline_create_info vk_structure_type_compute_pipeline_create_info 对于 vulkan -hpp ,在结构上有以下限制:
namespace vk{ … struct structname { … const vk::structuretype stype = vk::structuretype::estructname; … }; …} 就这么简单。当您实例化类型为 structname 的结构时,您不必担心为成员 stype 设置正确的值,因为它已经设置好了。因为它是常量成员,所以不能意外地覆盖它。
对于感兴趣的模板元程序员来说: struct structname 还提供了一个静态成员 structuretype ,即 vk::structuretype 值。有一个名为 cpptype 的类型特征,它从 vk::structuretype 值中获取结构的类型。
帮助程序类 vk :: structurechain
vulkan 中的许多结构都有 pnext 作为第二个成员:
typedef struct vkstructname { vkstructuretype stype; const void* pnext; …} vkstructname; 某些结构被指定为延伸其他结构。 pnext 指针用于创建结构链。有些结构可以多次成为该链的一部分,具有不同的实例。您的代码 mig ht 如下所示:
chainedstruct chained = {};chained.stype = vk_structure_type_chained_struct;// set other values of chainedanchorstruct anchor = {};anchor.stype = vk_structure_type_anchor_struct;anchor.pnext = &chained;// set other values of anchor 使用这种方法,有几个潜在的错误。例如, mig ht 将一个结构链到 anchorstruct 实例,该实例没有指定在链中。或者您意外地链接了一个结构的多个实例,其中只允许一个实例。甚至内存管理也会导致意外行为。例如,当您有一个使用局部变量创建链的函数时,就像前面的代码示例中那样,当该函数最终按值返回锚点时,您已经注定要失败了。 mig 指出的链式结构已经消失。
对于 vulkan -hpp ,该代码看起来几乎相同:
namespace vk{ … struct structname { … const vk::structuretype stype = vk::structuretype::estructname; const void * pnext = {}; … } …} 您可以使用与 vulkan 相同的方法来使用它,潜在错误的来源相同。 helper 类 vk::structurechain 在这里帮助编译器,如下面的代码示例所示:
vk::structurechain chain( { /* set other values of anchor */ } { /* set other values of chained */ }); 当然,这条链会变得任意长。编译器可以检查是否所有链式结构都指定为扩展 mig 。如果同一个链式结构多次出现,编译器会检查是否允许。访问这样一个链的元素将与您习惯于使用纯 c 型结构链略有不同:
vk::anchorstruct const & anchorstruct = chain.get();vk::chainedstruct const & chainedstruct = chain.get(); 如果必须在运行时从结构链中删除元素,可以使用成员函数 vk::structurechain::unlink 来执行此操作。这样,结构链的内存占用不会改变,但是现在未使用的部分将被跳过,因为该链中的任何 pnext 指针都不会指向这些部分。要重新链接,请使用 vk::structurechain::relink 。
型式安全
最后,我们来看一个稍微不同的主题,类型安全。这是 vulkan 的一个问题,尤其是对于 32 位构建。在 32 位内部版本中,所有不可分派的句柄(如 vkbuffer 、 vkimage 和 vksemaphore 都只是 uint64_t 上的 typedef :
#define vk_define_non_dispatchable_handle(object) typedef uint64_t object;…vk_define_non_dispatchable_handle(vkbuffer)vk_define_non_dispatchable_handle(vkimage) 您可以调用 vkcreatebuffer 并传入指向 vkimage 的指针作为最后一个参数。您还可以将 vkimage 分配给 vkbuffer 或对它们进行比较,没有任何错误!
由于 vulkan -hpp 中的相应类型是独立的类,没有任何继承关系,编译器不允许这些操作中的任何一个。你不会弄错的。
与运行时错误相比,更倾向于编辑时错误预防
正如我前面所说,最好的情况是甚至不能编写错误的代码。 vulkan -hpp ,可能与所有现代 ide 一起帮助您设置结构的成员。因为每个 vk :: struct 都有一个构造函数,该构造函数的每个成员都有一个参数列表,除了前面提到的 stype 和 pnext , ide 可能会在编辑时引导您遍历所有这些参数,从而更难忽略任何错误。
把您的 vulkan 的项目切换到 c ++
这些是 vulkan -hpp 的一些特性,它们极大地简化了用 vulkan 进行编码的工作。这些特性在运行时开销为零的情况下可用。只是编译器需要多工作一点。您仍然必须(几乎)像使用 plainvulkan 一样显式地编程,但是编译器可以更好地检查代码。这样可以节省你很多时间!
关于作者
andreas süßenbach 是 nvidia 的高级软件开发人员。他是 vulkan.hpp 的发明者之一,并不断努力使之更进一步。
泛在电力物联网的建设关键点在哪里
想打赢MCU市场的争夺战 首先要最大化满足客户需求
华为创始人任正非表示与美国公司合作将会采取谨慎的态度向前发展
涂鸦智能推进AIoT行业生态的迅速壮大,实现环环相扣的价值增迁
PLC输入输出的连接方式
在Vulkan-hpp中有助于将错误转移到编译时的特性
VR技术助力养元饮品,实现在线工厂VR体验
听说住携住智慧客房,用smart魔方,幸福来得特别快!
BUCK的功率电感怎么选型
工信部表示要求三大运营商在全国正式提供手机号异地销户服务
荣耀Play手机发布,一款搭载着“很吓人的技术”的游戏手机
android模拟器获取小程序包流程
光伏太阳能发电有辐射吗
四大晶圆代工厂齐预警!两大因素增添半导体行业不确定性
中国信通院主办的云计算产业盛会在北京拉开帷幕
变频器开关电源维修技巧
国科微高质量发展 2022上半年营业收入166722.46万元同比增长75.16%
基于无线传感器网络的嵌入式远程测控系统研究
节卡发布的新一代共融系列协作机器人之一JAKA Ai 7
秘密武器:TI DLP显示技术