Vulkan(1)用apispec生成Vulkan库
我的Vulkan.net库已在()开源,欢迎交流。
apispec.html
在Vulkan SDK的安装文件夹里,有一个Documentation\apispec.html文件。这是一个由代码生成的对Vulkan API的说明。它包含了Vulkan API的枚举类型、结构体、函数声明以及这一切的详细注释。
由于它是自动生成的,所以其格式非常规则。只需将少数几处<br>改为<br />,几处<col .. >改为<col .. />,就可以直接用 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的描述:
Name
VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type
C Specification
Possible values of
type
inVkAccelerationStructureMemoryRequirementsInfoNV
are:,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;
Description
VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV
requests the memory requirement for theVkAccelerationStructureNV
backing store.
VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV
requests the memory requirement for scratch space during the initialbuild.
VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV
requests the memory requirement for scratch space during an update.See Also
我们将发现,对于每个Enum类型,apispec都有这样的规律:从一个<h4>Name</h4>标签开始,接下来的<p></p>标签是对这个Enum的注释,接下来的<code class="language-c++"></code>标签是这个Enum的定义;然后,从<h4>Descriptor</h4>开始到<h4>See Also</h4>结束,这两个标签之间的所有<p></p>标签,分别是Enum的某个成员的注释,而且,这个注释都是以<code>此成员的名字</code>开头(这可以用于识别此注释属于哪个成员)。
有了这些规律,就可以将其解析为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 ListlstComment = new List (); 49 50 public Dictionary Dump() { 51 Dictionary dict = new Dictionary (); 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 (); 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 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
115116*/118 private static void TraverseNodesEnumComments(XElement node, ListVkAccessFlagBits - Bitmask specifying memory access types that will participate in a memory dependency
117list, 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-level142 acceleration structure containing instance data referring to143 bottom-level level acceleration structures.144VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV
is a bottom-level145 acceleration structure containing the AABBs or geometry to be146 intersected.147 */148 static readonly char[] equalSeparator = new char[] { '=', ' ', '\t', '\r', '\n', };149 private static string ParseItemComment(string line, Dictionarydict) {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, Listlist, 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, Listlist, 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 }
解析得到了143个Enum类型,其中前2个如下:
1 // Enum: 0 2 ///VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type 3 public enum VkAccelerationStructureMemoryRequirementsTypeNV { 4 ///7 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0, 8 /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV
5 /// requests the memory requirement for theVkAccelerationStructureNV
6 /// backing store.11 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1,12 /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV
9 /// requests the memory requirement for scratch space during the initial10 /// build.14 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV = 2,15 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_MAX_ENUM_NV = 0x7FFFFFFF16 }17 // Enum: 118 /// VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV
13 /// requests the memory requirement for scratch space during an update.VkAccelerationStructureTypeNV - Type of acceleration structure 19 public enum VkAccelerationStructureTypeNV {20 ///23 VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV = 0,24 /// VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV
is a top-level21 /// acceleration structure containing instance data referring to22 /// bottom-level level acceleration structures.27 VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV = 1,28 VK_ACCELERATION_STRUCTURE_TYPE_MAX_ENUM_NV = 0x7FFFFFFF29 } VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV
is a bottom-level25 /// acceleration structure containing the AABBs or geometry to be26 /// intersected.
为了保持Vulkan API的原汁原味(也为了我自己省事),Enum的成员名字就保持这么长的大写+下划线版本好了。
Handle
这里的Handle指的是Vulkan中的不透明对象提供给程序员的句柄,例如一个VkInstance类型的对象,在程序员这里看到的只是一个UInt32的句柄,它的实际内容由Vulkan内部来管理。因此这里只需找到各个Handle的名字,将其改写为一个struct即可。
在apispec.html中对Handle的描述如下:
VkAccelerationStructureNV(3)
只需找到各个<h3></h3>标签,就可以找到各个Handle的名字了。解析后得到37个Handle,其中的2个Handle如下:
1 // Object Handles: 1 2 ///VkBuffer - Opaque handle to a buffer object 3 /// 6 public struct VkBuffer { 7 public UInt64 handle; 8 } 9 10 // Object Handles: 2111 ///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 ///VkInstance - Opaque handle to an instance object12 /// 15 public struct VkInstance {16 public UInt32 handle;17 }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 ///
对于上述这样的struct,其长度等于内部成员的长度。因此,实际上VkInstance只是UInt32的一个别名,这样的别名大大强化了类型的作用,加快了编程速度。
要注意的是,有的Handle使用UInt64,有的使用UInt32,这是不可以随意改变的,否则Vulkan会卡住不动。当然,只要字节长度相同,就可以代替,例如可以用IntPtr代替UInt32,因为两者都是4字节的。
Flag
在apispec.html中,Flag实际上是一个别名,即C语言中用 typedef 定义的一个名字。2个例子如下:
1VkAccessFlags - Bitmask of VkAccessFlagBits
2VkBufferViewCreateFlags - 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 bytes11 /// and must be a power of two. 12 Int32 alignment,13 ///allocationScope is a VkSystemAllocationScope value14 /// specifying the allocation scope of the lifetime of the allocation, as15 /// described here. 16 VkSystemAllocationScope allocationScope);17 // PFN: 118 ///PFN_vkDebugReportCallbackEXT - Application-defined debug report callback function 19 public unsafe delegate VkBool32 PFN_vkDebugReportCallbackEXT(20 ///flags specifies the VkDebugReportFlagBitsEXT that triggered21 /// this callback. 22 VkDebugReportFlagBitsEXT flags,23 ///objectType is a VkDebugReportObjectTypeEXT value specifying24 /// the type of object being used or created at the time the event was25 /// 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 that32 /// specifies the location of the trigger.33 /// This is an optional value. 34 Int32 location,35 ///messageCode is a layer-defined value indicating what test36 /// triggered this callback. 37 Int32 messageCode,38 ///pLayerPrefix is a null-terminated string that is an abbreviation39 /// 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 trigger43 /// conditions.44 /// pMessage is only valid for the duration of the callback. 45 IntPtr pMessage,46 ///pUserData is the user data given when the47 /// 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 memory14 /// allocation function of type PFN_vkAllocationFunction. 15 public /*PFN_vkAllocationFunction*/IntPtr pfnAllocation;16 ///pfnReallocation is a pointer to an application-defined memory17 /// reallocation function of type PFN_vkReallocationFunction. 18 public /*PFN_vkReallocationFunction*/IntPtr pfnReallocation;19 ///pfnFree is a pointer to an application-defined memory free20 /// function of type PFN_vkFreeFunction. 21 public /*PFN_vkFreeFunction*/IntPtr pfnFree;22 ///pfnInternalAllocation is a pointer to an application-defined23 /// function that is called by the implementation when the implementation24 /// makes internal allocations, and it is of type25 /// PFN_vkInternalAllocationNotification. 26 public /*PFN_vkInternalAllocationNotification*/IntPtr pfnInternalAllocation;27 ///pfnInternalFree is a pointer to an application-defined function28 /// that is called by the implementation when the implementation frees29 /// internal allocations, and it is of type30 /// PFN_vkInternalFreeNotification. 31 public /*PFN_vkInternalFreeNotification*/IntPtr pfnInternalFree;32 }33 // Struct: 934 ///VkApplicationInfo - Structure specifying application info35 /// 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-terminated42 /// UTF-8 string containing the name of the application. 43 public IntPtr pApplicationName;44 ///applicationVersion is an unsigned integer variable containing the45 /// developer-supplied version number of the application. 46 public UInt32 applicationVersion;47 ///pEngineName is NULL or is a pointer to a null-terminated UTF-848 /// string containing the name of the engine (if any) used to create the49 /// application. 50 public IntPtr pEngineName;51 ///engineVersion is an unsigned integer variable containing the52 /// developer-supplied version number of the engine used to create the53 /// application. 54 public UInt32 engineVersion;55 ///apiVersion56 /// must be the highest version of Vulkan that the57 /// application is designed to use, encoded as described in58 /// html/vkspec.html#extendingvulkan-coreversions-versionnumbers.59 /// The patch version number specified in apiVersion is ignored when60 /// creating an instance object.61 /// Only the major and minor versions of the instance must match those62 /// requested in apiVersion. 63 public UInt32 apiVersion;64 }65 // Struct: 19366 ///VkInstanceCreateInfo - Structure specifying parameters of a newly created instance67 /// 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 of76 /// VkApplicationInfo.77 /// If not NULL, this information helps implementations recognize behavior78 /// 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 of84 /// enabledLayerCount null-terminated UTF-8 strings containing the85 /// 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 to89 /// enable. 90 public UInt32 enabledExtensionCount;91 ///ppEnabledExtensionNames is a pointer to an array of92 /// enabledExtensionCount null-terminated UTF-8 strings containing the93 /// 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 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 }. 3 ///
这个扩展函数会将上一次 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 6 /// 7 /// address of first element/array. 8 /// How many elements? 9 public static void Setand . 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 ///(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[]和(
bool、byte、short、int、long、char、sbyte、ushort、uint、ulong、float、double
)这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 the11 /// 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: 32419 ///vkUpdateDescriptorSets - Update the contents of a descriptor set object20 /// 21 /// device is the logical device that updates the descriptor sets.22 /// descriptorWriteCount is the number of elements in the23 /// pDescriptorWrites array.24 /// pDescriptorWrites is a pointer to an array of25 /// VkWriteDescriptorSet structures describing the descriptor sets to26 /// write to.27 /// descriptorCopyCount is the number of elements in the28 /// pDescriptorCopies array.29 /// pDescriptorCopies is a pointer to an array of30 /// VkCopyDescriptorSet structures describing the descriptor sets to31 /// 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不会被反复使用,因此,它们最多泄漏一点点内存,不会像服务器代码那样占用越来越多的内存,所以不碍事的。
总结
有了这么带劲的注释,整个档次都不一样了。