Vulkan(1)用apispec生成Vulkan库


Vulkan(1)用apispec生成Vulkan库

我的Vulkan.net库已在(https://github.com/bitzhuwei/Vulkan.net)开源,欢迎交流。

apispec.html

在Vulkan SDK的安装文件夹里,有一个Documentation\apispec.html文件。这是一个由代码生成的对Vulkan API的说明。它包含了Vulkan API的枚举类型、结构体、函数声明以及这一切的详细注释

由于它是自动生成的,所以其格式非常规则。只需将少数几处
改为
,几处改为,就可以直接用 XElement 来加载和解析它。

由于它包含了每个枚举类型及其成员的注释,包含了每个结构体及其成员的注释,包含了每个函数声明及其参数的注释,我就想,如果我能将它转换为C#代码,那会是多么美妙的一个Vulkan库啊!

我在网上找到的几个Vulkan库,基本上都没有什么注释,这让我使用起来很不方便,严重妨碍了学习速度。很多结构体的成员类型都是粗糙的 IntPtr ,而不是具体类型的指针,这也使得用起来很麻烦。

那么就动手做自己的Vulkan库吧!

分类

首先,要将巨大的apispec.html文件里的内容分为几个类别,即C宏定义、Command(函数声明)、Enum、Extension、Flag、Handle、PFN、Scalar Type和Struct。其中的C宏定义和Extension暂时用不到,就不管了,Scalar Type数量很少,又不包含实质内容,直接手工编写即可。

我们按照Enum、Handle、Flag、PFN、Struct和Command的顺序依次分析,因为后者可能依赖前者。

Enum

我们来观察apispec.html中对Enum的描述:

<h4 id="_name_798">Name
class="paragraph"> <p>VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type

class="sect3">

"_c_specification_798">C Specification

class="paragraph">

Possible values of type in VkAccelerationStructureMemoryRequirementsInfoNV are:,

"VkAccelerationStructureMemoryRequirementsTypeNV" class="listingblock">
class="content">
class="highlight"><code class="language-c++" data-lang="c++">typedef enum VkAccelerationStructureMemoryRequirementsTypeNV {
    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0,
    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1,
    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV = 2,
    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
} VkAccelerationStructureMemoryRequirementsTypeNV;
class="sect3"> <h4 id="_description_798">Description
class="ulist">
  • <p>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV requests the memory requirement for the VkAccelerationStructureNV backing store.

  • <p>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV requests the memory requirement for scratch space during the initial build.

  • <p>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV requests the memory requirement for scratch space during an update.

class="sect3"> <h4 id="_see_also_798">See Also

我们将发现,对于每个Enum类型,apispec都有这样的规律:从一个

Name

标签开始,接下来的

标签是对这个Enum的注释,接下来的标签是这个Enum的定义;然后,从

Descriptor

开始到

See Also

结束,这两个标签之间的所有

标签,分别是Enum的某个成员的注释,而且,这个注释都是以此成员的名字开头(这可以用于识别此注释属于哪个成员)。

有了这些规律,就可以将其解析为C#代码了。解析代码很简单,就不解释了。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Xml.Linq;
  4 
  5 namespace ApiSpec {
  6     class EnumsParser {
  7 
  8         static readonly char[] inLineSeparator = new char[] { ' ', '\t', '\r', '\n', };
  9         static readonly char[] lineSeparator = new char[] { '\r', '\n' };
 10         const string leftBrace = "{";
 11         const string rightBrace = "}";
 12 
 13         const string filename = "Enums.content.xml";
 14         const string strName = "Name";
 15         const string strCSpecification = "C Specification";
 16         const string strDescription = "Description";
 17         const string strSeeAlso = "See Also";
 18         const string strDocNotes = "Document Notes";
 19 
 20         class EnumDefinetion {
 21             /*typedef enum VkAccelerationStructureMemoryRequirementsTypeNV {
 22     VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0,
 23     VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1,
 24     VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV = 2,
 25     VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
 26 } VkAccelerationStructureMemoryRequirementsTypeNV;
 27              */
 28             public string raw;
 29 
 30             public string[] Dump() {
 31                 string[] lines = this.raw.Split(lineSeparator, StringSplitOptions.RemoveEmptyEntries);
 32                 if (lines == null || lines.Length < 2) { return lines; }
 33 
 34                 {
 35                     string[] parts = lines[0].Split(inLineSeparator, StringSplitOptions.RemoveEmptyEntries);
 36                     lines[0] = $"public enum {parts[2]} {leftBrace}";
 37                 }
 38                 {
 39                     int last = lines.Length - 1;
 40                     lines[last] = $"{rightBrace}";
 41                 }
 42 
 43                 return lines;
 44             }
 45         }
 46 
 47         class EnumItemComment {
 48             public List<string> lstComment = new List<string>();
 49 
 50             public Dictionary<string, string> Dump() {
 51                 Dictionary<string, string> dict = new Dictionary<string, string>();
 52                 foreach (var item in lstComment) {
 53                     int left = item.IndexOf("");
 54                     int right = item.IndexOf("");
 55                     if (left != -1 && right != -1) {
 56                         string key = item.Substring(left + "".Length, right - (left + "".Length));
 57                         if (!dict.ContainsKey(key)) {
 58                             dict.Add(key, item);
 59                         }
 60                     }
 61                 }
 62 
 63                 return dict;
 64             }
 65         }
 66 
 67         public static void DumpEnums() {
 68             XElement root = XElement.Load(filename);
 69             var lstDefinition = new List(); bool inside = false;
 70             TraverseNodesEnumDefinitions(root, lstDefinition, ref inside);
 71             var listEnumItemComment = new List(); inside = false;
 72             TraverseNodesEnumItemComments(root, listEnumItemComment, ref inside);
 73             var lstEnumComment = new List<string>(); inside = false;
 74             TraverseNodesEnumComments(root, lstEnumComment, ref inside);
 75 
 76             using (var sw = new System.IO.StreamWriter("Enums.gen.cs")) {
 77                 for (int i = 0; i < lstDefinition.Count; i++) {
 78                     EnumDefinetion definition = lstDefinition[i];
 79                     //sw.WriteLine(definition.raw);
 80                     string[] definitionLines = definition.Dump();
 81                     EnumItemComment itemComment = listEnumItemComment[i];
 82                     Dictionary<string, string> item2Comment = itemComment.Dump();
 83 
 84                     sw.WriteLine($"// Enum: {i}");
 85                     string enumComment = lstEnumComment[i];
 86                     sw.WriteLine($"/// {enumComment}");
 87                     {
 88                         string line = definitionLines[0];
 89                         if (line.Contains("FlagBits")) { sw.WriteLine("[Flags]"); }
 90                         sw.WriteLine(line);
 91                     }
 92                     for (int j = 1; j < definitionLines.Length - 1; j++) {
 93                         string line = definitionLines[j];
 94                         if (item2Comment != null) {
 95                             string strComment = ParseItemComment(line, item2Comment);
 96                             if (strComment != string.Empty) {
 97                                 strComment = strComment.Replace("\r\n", "\n");
 98                                 strComment = strComment.Replace("\r", "\n");
 99                                 strComment = strComment.Replace("\n", $"{Environment.NewLine}    /// ");
100                                 sw.WriteLine($"    /// {strComment}");
101                             }
102                         }
103                         sw.WriteLine(line);
104                     }
105                     {
106                         string line = definitionLines[definitionLines.Length - 1];
107                         sw.WriteLine(line); // }
108                     }
109                 }
110             }
111             Console.WriteLine("Done");
112         }
113 
114         /*

Name

115
116

VkAccessFlagBits - Bitmask specifying memory access types that will participate in a memory dependency

117
*/ 118 private static void TraverseNodesEnumComments(XElement node, List<string> list, ref bool inside) { 119 if (node.Name == "h4") { 120 if (node.Value == "Name") { 121 inside = true; 122 } 123 } 124 else if (node.Name == "p") { 125 if (inside) { 126 string text = node.ToString(); 127 text = text.Substring("

".Length, text.Length - "

".Length); 128 text = text.Trim(); 129 list.Add(text); 130 inside = false; 131 } 132 } 133 134 foreach (XElement item in node.Elements()) { 135 TraverseNodesEnumComments(item, list, ref inside); 136 } 137 } 138 139 /* line: VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV = 0, 140 * 141 comment: VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV is a top-level 142 acceleration structure containing instance data referring to 143 bottom-level level acceleration structures. 144 VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV is a bottom-level 145 acceleration structure containing the AABBs or geometry to be 146 intersected. 147 */ 148 static readonly char[] equalSeparator = new char[] { '=', ' ', '\t', '\r', '\n', }; 149 private static string ParseItemComment(string line, Dictionary<string, string> dict) { 150 string result = string.Empty; 151 string[] parts = line.Split(equalSeparator, StringSplitOptions.RemoveEmptyEntries); 152 if (parts.Length == 2) { 153 string key = parts[0]; 154 if (dict.ContainsKey(key)) { 155 result = dict[key]; 156 } 157 } 158 159 return result; 160 } 161 162 /// 163 /// 164 /// 165 /// 166 /// 167 /// 168 private static void TraverseNodesEnumItemComments(XElement node, List list, ref bool inside) { 169 if (node.Name == "h4") { 170 if (node.Value == "Description") { 171 inside = true; 172 var comment = new EnumItemComment(); 173 list.Add(comment); 174 } 175 else if (node.Value == "See Also") { 176 inside = false; 177 } 178 } 179 else if (node.Name == "p") { 180 if (inside) { 181 EnumItemComment comment = list[list.Count - 1]; 182 string text = node.ToString(); 183 text = text.Substring("

".Length, text.Length - "

".Length); 184 text = text.Trim(); 185 comment.lstComment.Add(text); 186 } 187 } 188 189 foreach (XElement item in node.Elements()) { 190 TraverseNodesEnumItemComments(item, list, ref inside); 191 } 192 } 193 194 195 private static void TraverseNodesEnumDefinitions(XElement node, List list, ref bool inside) { 196 if (node.Name == "h4") { 197 if (node.Value == "C Specification") { 198 inside = true; 199 } 200 } 201 else if (node.Name == "code") { 202 if (inside) { 203 XAttribute attrClass = node.Attribute("class"); 204 if (attrClass != null && attrClass.Value == "language-c++") { 205 string v = node.Value; 206 var item = new EnumDefinetion() { raw = v, }; 207 list.Add(item); 208 inside = false; 209 } 210 } 211 } 212 213 foreach (XElement item in node.Elements()) { 214 TraverseNodesEnumDefinitions(item, list, ref inside); 215 } 216 } 217 } 218 }
EnumsParser

解析得到了143个Enum类型,其中前2个如下:

 1     // Enum: 0
 2     /// VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type
 3     public enum VkAccelerationStructureMemoryRequirementsTypeNV {
 4         /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV
 5         /// requests the memory requirement for the VkAccelerationStructureNV
 6         /// backing store.
 7         VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0,
 8         /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV
 9         /// requests the memory requirement for scratch space during the initial
10         /// build.
11         VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1,
12         /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV
13         /// requests the memory requirement for scratch space during an update.
14         VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV = 2,
15         VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
16     }
17     // Enum: 1
18     /// VkAccelerationStructureTypeNV - Type of acceleration structure
19     public enum VkAccelerationStructureTypeNV {
20         /// VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV is a top-level
21         /// acceleration structure containing instance data referring to
22         /// bottom-level level acceleration structures.
23         VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV = 0,
24         /// VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV is a bottom-level
25         /// acceleration structure containing the AABBs or geometry to be
26         /// intersected.
27         VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV = 1,
28         VK_ACCELERATION_STRUCTURE_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
29     }

为了保持Vulkan API的原汁原味(也为了我自己省事),Enum的成员名字就保持这么长的大写+下划线版本好了。

Handle

这里的Handle指的是Vulkan中的不透明对象提供给程序员的句柄,例如一个VkInstance类型的对象,在程序员这里看到的只是一个UInt32的句柄,它的实际内容由Vulkan内部来管理。因此这里只需找到各个Handle的名字,将其改写为一个struct即可。

在apispec.html中对Handle的描述如下:

<h3 id="_vkaccelerationstructurenv3">VkAccelerationStructureNV(3)

只需找到各个

标签,就可以找到各个Handle的名字了。解析后得到37个Handle,其中的2个Handle如下:

 1     // Object Handles: 1
 2     /// VkBuffer - Opaque handle to a buffer object
 3     /// Buffers represent linear arrays of data which are used for various purposesby binding them to a graphics or compute pipeline via descriptor sets or viacertain commands, or by directly specifying them as parameters to certaincommands.
 4     /// Buffers are represented by VkBuffer handles:
 5     /// 
 6     public struct VkBuffer {
 7         public UInt64 handle;
 8     }
 9 
10     // Object Handles: 21
11     /// VkInstance - Opaque handle to an instance object
12     /// There is no global state in Vulkan and all per-application state is storedin a VkInstance object.Creating a VkInstance object initializes the Vulkan library and allowsthe application to pass information about itself to the implementation.
13     /// Instances are represented by VkInstance handles:
14     /// 
15     public struct VkInstance {
16         public UInt32 handle;
17     }

对于上述这样的struct,其长度等于内部成员的长度。因此,实际上VkInstance只是UInt32的一个别名,这样的别名大大强化了类型的作用,加快了编程速度。

要注意的是,有的Handle使用UInt64,有的使用UInt32,这是不可以随意改变的,否则Vulkan会卡住不动。当然,只要字节长度相同,就可以代替,例如可以用IntPtr代替UInt32,因为两者都是4字节的。

Flag

在apispec.html中,Flag实际上是一个别名,即C语言中用 typedef 定义的一个名字。2个例子如下:

1 <p>VkAccessFlags - Bitmask of VkAccessFlagBits

2 <p>VkBufferViewCreateFlags - Reserved for future use

这是目前的apispec中仅有的2种Flag的说明形式。对于它们,我们分别可以用下面的代码代替:

1 using VkAccessFlags = ApiSpec.Generated.VkAccessFlagBits;
2 // VkBufferViewCreateFlags - Reserved for future use

解析方法也很简单,用 string.Split() 拆分一下即可。

最后得到的这些using代码,将用于后面解析的Struct和Command中。

PFN

这里的PFN是函数指针的意思,也就是C#里的delegate那一套。其解析方式与Enum十分相似,不再赘述。解析后得到了8个函数指针的定义,其中几个如下:

 1     // PFN: 0
 2     /// PFN_vkAllocationFunction - Application-defined memory allocation function
 3     public unsafe delegate void* PFN_vkAllocationFunction(
 4     /// pUserData is the value specified for
 5     /// VkAllocationCallbacks::pUserData in the allocator specified
 6     /// by the application.
 7     void* pUserData,
 8     /// size is the size in bytes of the requested allocation.
 9     Int32 size,
10     /// alignment is the requested alignment of the allocation in bytes
11     /// and must be a power of two.
12     Int32 alignment,
13     /// allocationScope is a VkSystemAllocationScope value
14     /// specifying the allocation scope of the lifetime of the allocation, as
15     /// described here.
16     VkSystemAllocationScope allocationScope);
17     // PFN: 1
18     /// PFN_vkDebugReportCallbackEXT - Application-defined debug report callback function
19     public unsafe delegate VkBool32 PFN_vkDebugReportCallbackEXT(
20     /// flags specifies the VkDebugReportFlagBitsEXT that triggered
21     /// this callback.
22     VkDebugReportFlagBitsEXT flags,
23     /// objectType is a VkDebugReportObjectTypeEXT value specifying
24     /// the type of object being used or created at the time the event was
25     /// triggered.
26     VkDebugReportObjectTypeEXT _objectType,
27     /// object is the object where the issue was detected.
28     /// If objectType is VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT,
29     /// object is undefined.
30     UInt64 _object,
31     /// location is a component (layer, driver, loader) defined value that
32     /// specifies the location of the trigger.
33     /// This is an optional value.
34     Int32 location,
35     /// messageCode is a layer-defined value indicating what test
36     /// triggered this callback.
37     Int32 messageCode,
38     /// pLayerPrefix is a null-terminated string that is an abbreviation
39     /// of the name of the component making the callback.
40     /// pLayerPrefix is only valid for the duration of the callback.
41     IntPtr pLayerPrefix,
42     /// pMessage is a null-terminated string detailing the trigger
43     /// conditions.
44     /// pMessage is only valid for the duration of the callback.
45     IntPtr pMessage,
46     /// pUserData is the user data given when the
47     /// VkDebugReportCallbackEXT was created.
48     void* pUserData);

可以看到,函数注释和参数注释都十分详尽,看了就开心。

Struct

对于Struct的解析也与Enum类似,不再赘述。解析后得到434个结构体。其中几个如下:

 1     // Struct: 4
 2     /// VkAllocationCallbacks - Structure containing callback function pointers for memory allocation
 3     /// 
 4     public unsafe struct VkAllocationCallbacks {
 5         ///  pUserData is a value to be interpreted by the implementation of
 6         /// the callbacks.
 7         /// When any of the callbacks in VkAllocationCallbacks are called, the
 8         /// Vulkan implementation will pass this value as the first parameter to the
 9         /// callback.
10         /// This value can vary each time an allocator is passed into a command,
11         /// even when the same object takes an allocator in multiple commands.
12         public void* pUserData;
13         ///  pfnAllocation is a pointer to an application-defined memory
14         /// allocation function of type PFN_vkAllocationFunction.
15         public /*PFN_vkAllocationFunction*/IntPtr pfnAllocation;
16         ///  pfnReallocation is a pointer to an application-defined memory
17         /// reallocation function of type PFN_vkReallocationFunction.
18         public /*PFN_vkReallocationFunction*/IntPtr pfnReallocation;
19         ///  pfnFree is a pointer to an application-defined memory free
20         /// function of type PFN_vkFreeFunction.
21         public /*PFN_vkFreeFunction*/IntPtr pfnFree;
22         ///  pfnInternalAllocation is a pointer to an application-defined
23         /// function that is called by the implementation when the implementation
24         /// makes internal allocations, and it is of type
25         /// PFN_vkInternalAllocationNotification.
26         public /*PFN_vkInternalAllocationNotification*/IntPtr pfnInternalAllocation;
27         ///  pfnInternalFree is a pointer to an application-defined function
28         /// that is called by the implementation when the implementation frees
29         /// internal allocations, and it is of type
30         /// PFN_vkInternalFreeNotification.
31         public /*PFN_vkInternalFreeNotification*/IntPtr pfnInternalFree;
32 }
33     // Struct: 9
34     /// VkApplicationInfo - Structure specifying application info
35     /// 
36     public unsafe struct VkApplicationInfo {
37         ///  sType is the type of this structure.
38         public VkStructureType sType;
39         ///  pNext is NULL or a pointer to an extension-specific structure.
40         public /*-const-*/ void* pNext;
41         ///  pApplicationName is NULL or is a pointer to a null-terminated
42         /// UTF-8 string containing the name of the application.
43         public IntPtr pApplicationName;
44         ///  applicationVersion is an unsigned integer variable containing the
45         /// developer-supplied version number of the application.
46         public UInt32 applicationVersion;
47         ///  pEngineName is NULL or is a pointer to a null-terminated UTF-8
48         /// string containing the name of the engine (if any) used to create the
49         /// application.
50         public IntPtr pEngineName;
51         ///  engineVersion is an unsigned integer variable containing the
52         /// developer-supplied version number of the engine used to create the
53         /// application.
54         public UInt32 engineVersion;
55         ///  apiVersion
56         ///   must be the highest version of Vulkan that the
57         /// application is designed to use, encoded as described in
58         /// html/vkspec.html#extendingvulkan-coreversions-versionnumbers.
59         /// The patch version number specified in apiVersion is ignored when
60         /// creating an instance object.
61         /// Only the major and minor versions of the instance must match those
62         /// requested in apiVersion.
63         public UInt32 apiVersion;
64 }
65     // Struct: 193
66     /// VkInstanceCreateInfo - Structure specifying parameters of a newly created instance
67     /// 
68     public unsafe struct VkInstanceCreateInfo {
69         ///  sType is the type of this structure.
70         public VkStructureType sType;
71         ///  pNext is NULL or a pointer to an extension-specific structure.
72         public /*-const-*/ void* pNext;
73         ///  flags is reserved for future use.
74         public VkInstanceCreateFlags flags;
75         ///  pApplicationInfo is NULL or a pointer to an instance of
76         /// VkApplicationInfo.
77         /// If not NULL, this information helps implementations recognize behavior
78         /// inherent to classes of applications.
79         /// VkApplicationInfo is defined in detail below.
80         public /*-const-*/ VkApplicationInfo* pApplicationInfo;
81         ///  enabledLayerCount is the number of global layers to enable.
82         public UInt32 enabledLayerCount;
83         ///  ppEnabledLayerNames is a pointer to an array of
84         /// enabledLayerCount null-terminated UTF-8 strings containing the
85         /// names of layers to enable for the created instance.
86         /// See the html/vkspec.html#extendingvulkan-layers section for further details.
87         public IntPtr /*-const-*/ * ppEnabledLayerNames;
88         ///  enabledExtensionCount is the number of global extensions to
89         /// enable.
90         public UInt32 enabledExtensionCount;
91         ///  ppEnabledExtensionNames is a pointer to an array of
92         /// enabledExtensionCount null-terminated UTF-8 strings containing the
93         /// names of extensions to enable.
94         public IntPtr /*-const-*/ * ppEnabledExtensionNames;
95     }

这里有几点要注意。

函数委托用在struct中后,这个struct无法使用指针形式(SomeStruct*),所以这里不得不用IntPtr代替了具体的函数委托。

在 IntPtr pApplicationName 中应当用 Marshal.StringToHGlobalAnsi(string s) 为其赋值。函数 Marshal.StringToHGlobalAnsi(string s) 会在非托管内存中为s创建一个副本,然后返回此副本的指针。这样pApplicationName才会指向一个固定位置的字符串。当然,用完后,这个副本应当用 Marshal.FreeHGlobal(IntPtr hglobal) 释放掉。为了简化这一过程,我提供一个扩展函数:

 1         /// 
 2         /// Set a string to specified .
 3         /// 
 4         /// 
 5         /// address of string.
 6         public static void Set(this string value, ref IntPtr target) {
 7             {   // free unmanaged memory.
 8                 if (target != IntPtr.Zero) {
 9                     Marshal.FreeHGlobal(target);
10                     target = IntPtr.Zero;
11                 }
12             }
13             {
14                 if (value != null && value.Length > 0) {
15                     target = Marshal.StringToHGlobalAnsi(value);
16                 }
17                 else {
18                     target = IntPtr.Zero;
19                 }
20             }
21         }

这个扩展函数会将上一次 Marshal.StringToHGlobalAnsi() 的内存释放,但是无法保证这次的。也就是说,它可以保证,最多还只需调用1次内存释放函数Marshal.FreeHGlobal(IntPtr hglobal)。

在 public IntPtr /*-const-*/ * ppEnabledLayerNames; 中也有类似的问题,这个成员指向一个IntPtr数组,这个数组的每个成员都是一个IntPtr,每个IntPtr都指向一个由 Marshal.StringToHGlobalAnsi(string s) 提供的返回值。所以这需要另一个扩展函数来简化之:

 1         /// 
 2         /// Set an array of structs to specified  and .
 3         /// Enumeration types are not allowed to use this method.
 4         /// If you have to, convert them to byte/short/ushort/int/uint according to their underlying types first.
 5         /// 
 6         /// 
 7         /// address of first element/array.
 8         /// How many elements?
 9         public static void Set(this T[] value, ref IntPtr target, ref UInt32 count) where T : struct {
10             {   // free unmanaged memory.
11                 if (target != IntPtr.Zero) {
12                     Marshal.FreeHGlobal(target);
13                     target = IntPtr.Zero;
14                     count = 0;
15                 }
16             }
17             {
18                 count = (UInt32)value.Length;
19 
20                 int elementSize = Marshal.SizeOf();
21                 int byteLength = (int)(count * elementSize);
22                 IntPtr array = Marshal.AllocHGlobal(byteLength);
23                 var dst = (byte*)array;
24                 GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned);
25                 IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(value, 0);
26                 var src = (byte*)address;
27                 for (int i = 0; i < byteLength; i++) {
28                     dst[i] = src[i];
29                 }
30                 pin.Free();
31 
32                 target = array;
33             }
34         }

在此函数参数中,我使用 ref IntPtr target ,而不是 ref T* target ,是因为C#不允许这样。编译器说,无法获取托管类型(”T”)的大小,或声明指向它的指针。那么在调用此扩展函数时,就得先创建一个临时变量 IntPtr ptr = IntPtr.Zero ,调用完扩展函数后,再将ptr赋予具体类型的指针。例如:

1         var deviceInfo = new VkDeviceCreateInfo();
2         IntPtr ptr = IntPtr.Zero;
3         new VkDeviceQueueCreateInfo[] { queueInfo }.Set(ref ptr, ref deviceInfo.queueCreateInfoCount);
4         deviceInfo.pQueueCreateInfos = (VkDeviceQueueCreateInfo*)ptr;

好消息是,对于字符串数组string[]和(

boolbyteshortintlongcharsbyteushortuintulongfloatdouble

)这12种特殊基础类型的数组,可以直接使用Set扩展函数。因为我专门为它们编写了特定的扩展函数。

Command

对于Command的解析也与Struct类似,不再赘述。解析后得到326个command,几个例子如下:

 1         // Command: 4
 2         /// vkAllocateCommandBuffers - Allocate command buffers from an existing command pool
 3         /// 
 4         ///  device is the logical device that owns the command pool.
 5         ///  pAllocateInfo is a pointer to an instance of the
 6         /// VkCommandBufferAllocateInfo structure describing parameters of the
 7         /// allocation.
 8         ///  pCommandBuffers is a pointer to an array of VkCommandBuffer
 9         /// handles in which the resulting command buffer objects are returned.
10         /// The array must be at least the length specified by the
11         /// commandBufferCount member of pAllocateInfo.
12         /// Each allocated command buffer begins in the initial state.
13         [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
14         public static extern VkResult vkAllocateCommandBuffers(
15             VkDevice device,
16             /*-const-*/ VkCommandBufferAllocateInfo* pAllocateInfo,
17             VkCommandBuffer* pCommandBuffers);
18         // Command: 324
19         /// vkUpdateDescriptorSets - Update the contents of a descriptor set object
20         /// 
21         ///  device is the logical device that updates the descriptor sets.
22         ///  descriptorWriteCount is the number of elements in the
23         /// pDescriptorWrites array.
24         ///  pDescriptorWrites is a pointer to an array of
25         /// VkWriteDescriptorSet structures describing the descriptor sets to
26         /// write to.
27         ///  descriptorCopyCount is the number of elements in the
28         /// pDescriptorCopies array.
29         ///  pDescriptorCopies is a pointer to an array of
30         /// VkCopyDescriptorSet structures describing the descriptor sets to
31         /// copy between.
32         [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
33         public static extern void vkUpdateDescriptorSets(
34             VkDevice device,
35             UInt32 descriptorWriteCount,
36             /*-const-*/ VkWriteDescriptorSet* pDescriptorWrites,
37             UInt32 descriptorCopyCount,
38             /*-const-*/ VkCopyDescriptorSet* pDescriptorCopies);

其中有一个函数使用了 void** 这个二级指针,我觉得实在难看又难用,就用 IntPtr* 代替了。

对非托管内存的管理(释放)问题

每个struct都应该自己负责自己使用的非托管资源的释放问题。给这样的struct的指针成员 T* p; 赋值时,也应当为数据复制一个副本,将副本赋值给p。这样它释放资源时,就不会影响到其它地方了。实际上,在各个扩展函数 Set(..) 中,我就是用副本赋值的。

如果struct的指针成员 T* p; 实际上只需得到1个对象,也就是说,数组中的元素只有1个,那么可以直接将此元素的地址赋值给p,并且释放资源。例如:

 1     UInt32 index = 0;
 2     var info = new VkSwapchainCreateInfoKHR();
 3     {
 4         info.sType = VkStructureType.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
 5         // other stuff ..
 6         //new UInt32[] { 0 }.Set(ref info.QueueFamilyIndices, ref info.QueueFamilyIndexCount);
 7         info.pQueueFamilyIndices = &index; info.queueFamilyIndexCount = 1;
 8     }
 9     
10     VkSwapchainKHR swapchain;
11     vkAPI.vkCreateSwapchainKHR(device, &info, null, &swapchain);

这是稳妥、可移植、无需程序员直接写 Marshal. AllocHGlobal() 的内存管理方法。

那么,如果程序员忘记释放某些struct的资源了呢?Vulkan说,程序员应当清楚自己在做什么,不然他们何必用Vulkan。我觉得呢,这些struct不会被反复使用,因此,它们最多泄漏一点点内存,不会像服务器代码那样占用越来越多的内存,所以不碍事的。

总结

有了这么带劲的注释,整个档次都不一样了。