Vulkan(1)用apispec生成Vulkan庫
我的Vulkan.net庫已在(https://github.com/bitzhuwei/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的描述:
<h4 id="_name_798">Name</h4> <div class="paragraph"> <p>VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type</p> </div> </div> <div class="sect3"> <h4 id="_c_specification_798">C Specification</h4> <div class="paragraph"> <p>Possible values of <code>type</code> in <code>VkAccelerationStructureMemoryRequirementsInfoNV</code> are:,</p> </div> <div id="VkAccelerationStructureMemoryRequirementsTypeNV" class="listingblock"> <div class="content"> <pre 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;</code></pre> </div> </div> </div> <div class="sect3"> <h4 id="_description_798">Description</h4> <div class="ulist"> <ul> <li> <p><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV</code> requests the memory requirement for the <code>VkAccelerationStructureNV</code> backing store.</p> </li> <li> <p><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV</code> requests the memory requirement for scratch space during the initial build.</p> </li> <li> <p><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV</code> requests the memory requirement for scratch space during an update.</p> </li> </ul> </div> </div> <div class="sect3"> <h4 id="_see_also_798">See Also</h4>
我們將發現,對於每個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 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("<code>"); 54 int right = item.IndexOf("</code>"); 55 if (left != -1 && right != -1) { 56 string key = item.Substring(left + "<code>".Length, right - (left + "<code>".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<EnumDefinetion>(); bool inside = false; 70 TraverseNodesEnumDefinitions(root, lstDefinition, ref inside); 71 var listEnumItemComment = new List<EnumItemComment>(); 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($"/// <summary>{enumComment}</summary>"); 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($" /// <summary>{strComment}</summary>"); 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 /*<h4 id="_name_800">Name</h4> 115 <div class="paragraph"> 116 <p>VkAccessFlagBits - Bitmask specifying memory access types that will participate in a memory dependency</p> 117 </div>*/ 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("<p>".Length, text.Length - "<p></p>".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: <code>VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV</code> is a top-level 142 acceleration structure containing instance data referring to 143 bottom-level level acceleration structures. 144 <code>VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV</code> 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 /// <summary> 163 /// 164 /// </summary> 165 /// <param name="node"></param> 166 /// <param name="list"></param> 167 /// <param name="inside"></param> 168 private static void TraverseNodesEnumItemComments(XElement node, List<EnumItemComment> 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("<p>".Length, text.Length - "<p></p>".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<EnumDefinetion> 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 }
解析得到了143個Enum類型,其中前2個如下:
1 // Enum: 0 2 /// <summary>VkAccelerationStructureMemoryRequirementsTypeNV - Acceleration structure memory requirement type</summary> 3 public enum VkAccelerationStructureMemoryRequirementsTypeNV { 4 /// <summary><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV</code> 5 /// requests the memory requirement for the <code>VkAccelerationStructureNV</code> 6 /// backing store.</summary> 7 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0, 8 /// <summary><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV</code> 9 /// requests the memory requirement for scratch space during the initial 10 /// build.</summary> 11 VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1, 12 /// <summary><code>VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV</code> 13 /// requests the memory requirement for scratch space during an update.</summary> 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 /// <summary>VkAccelerationStructureTypeNV - Type of acceleration structure</summary> 19 public enum VkAccelerationStructureTypeNV { 20 /// <summary><code>VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV</code> is a top-level 21 /// acceleration structure containing instance data referring to 22 /// bottom-level level acceleration structures.</summary> 23 VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV = 0, 24 /// <summary><code>VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV</code> is a bottom-level 25 /// acceleration structure containing the AABBs or geometry to be 26 /// intersected.</summary> 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)</h3>
只需找到各個<h3></h3>標簽,就可以找到各個Handle的名字了。解析后得到37個Handle,其中的2個Handle如下:
1 // Object Handles: 1 2 /// <summary>VkBuffer - Opaque handle to a buffer object 3 /// <para>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.</para> 4 /// <para>Buffers are represented by VkBuffer handles:</para> 5 /// </summary> 6 public struct VkBuffer { 7 public UInt64 handle; 8 } 9 10 // Object Handles: 21 11 /// <summary>VkInstance - Opaque handle to an instance object 12 /// <para>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.</para> 13 /// <para>Instances are represented by VkInstance handles:</para> 14 /// </summary> 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</p> 2 <p>VkBufferViewCreateFlags - Reserved for future use</p>
這是目前的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 /// <summary>PFN_vkAllocationFunction - Application-defined memory allocation function</summary> 3 public unsafe delegate void* PFN_vkAllocationFunction( 4 /// <summary>pUserData is the value specified for 5 /// VkAllocationCallbacks::pUserData in the allocator specified 6 /// by the application.</summary> 7 void* pUserData, 8 /// <summary>size is the size in bytes of the requested allocation.</summary> 9 Int32 size, 10 /// <summary>alignment is the requested alignment of the allocation in bytes 11 /// and must be a power of two.</summary> 12 Int32 alignment, 13 /// <summary>allocationScope is a VkSystemAllocationScope value 14 /// specifying the allocation scope of the lifetime of the allocation, as 15 /// described here.</summary> 16 VkSystemAllocationScope allocationScope); 17 // PFN: 1 18 /// <summary>PFN_vkDebugReportCallbackEXT - Application-defined debug report callback function</summary> 19 public unsafe delegate VkBool32 PFN_vkDebugReportCallbackEXT( 20 /// <summary>flags specifies the VkDebugReportFlagBitsEXT that triggered 21 /// this callback.</summary> 22 VkDebugReportFlagBitsEXT flags, 23 /// <summary>objectType is a VkDebugReportObjectTypeEXT value specifying 24 /// the type of object being used or created at the time the event was 25 /// triggered.</summary> 26 VkDebugReportObjectTypeEXT _objectType, 27 /// <summary>object is the object where the issue was detected. 28 /// If objectType is VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 29 /// object is undefined.</summary> 30 UInt64 _object, 31 /// <summary>location is a component (layer, driver, loader) defined value that 32 /// specifies the location of the trigger. 33 /// This is an optional value.</summary> 34 Int32 location, 35 /// <summary>messageCode is a layer-defined value indicating what test 36 /// triggered this callback.</summary> 37 Int32 messageCode, 38 /// <summary>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.</summary> 41 IntPtr pLayerPrefix, 42 /// <summary>pMessage is a null-terminated string detailing the trigger 43 /// conditions. 44 /// pMessage is only valid for the duration of the callback.</summary> 45 IntPtr pMessage, 46 /// <summary>pUserData is the user data given when the 47 /// VkDebugReportCallbackEXT was created.</summary> 48 void* pUserData);
可以看到,函數注釋和參數注釋都十分詳盡,看了就開心。
Struct
對於Struct的解析也與Enum類似,不再贅述。解析后得到434個結構體。其中幾個如下:
1 // Struct: 4 2 /// <summary>VkAllocationCallbacks - Structure containing callback function pointers for memory allocation 3 /// </summary> 4 public unsafe struct VkAllocationCallbacks { 5 /// <summary> 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.</summary> 12 public void* pUserData; 13 /// <summary> pfnAllocation is a pointer to an application-defined memory 14 /// allocation function of type PFN_vkAllocationFunction.</summary> 15 public /*PFN_vkAllocationFunction*/IntPtr pfnAllocation; 16 /// <summary> pfnReallocation is a pointer to an application-defined memory 17 /// reallocation function of type PFN_vkReallocationFunction.</summary> 18 public /*PFN_vkReallocationFunction*/IntPtr pfnReallocation; 19 /// <summary> pfnFree is a pointer to an application-defined memory free 20 /// function of type PFN_vkFreeFunction.</summary> 21 public /*PFN_vkFreeFunction*/IntPtr pfnFree; 22 /// <summary> 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.</summary> 26 public /*PFN_vkInternalAllocationNotification*/IntPtr pfnInternalAllocation; 27 /// <summary> 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.</summary> 31 public /*PFN_vkInternalFreeNotification*/IntPtr pfnInternalFree; 32 } 33 // Struct: 9 34 /// <summary>VkApplicationInfo - Structure specifying application info 35 /// </summary> 36 public unsafe struct VkApplicationInfo { 37 /// <summary> sType is the type of this structure.</summary> 38 public VkStructureType sType; 39 /// <summary> pNext is NULL or a pointer to an extension-specific structure.</summary> 40 public /*-const-*/ void* pNext; 41 /// <summary> pApplicationName is NULL or is a pointer to a null-terminated 42 /// UTF-8 string containing the name of the application.</summary> 43 public IntPtr pApplicationName; 44 /// <summary> applicationVersion is an unsigned integer variable containing the 45 /// developer-supplied version number of the application.</summary> 46 public UInt32 applicationVersion; 47 /// <summary> 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.</summary> 50 public IntPtr pEngineName; 51 /// <summary> engineVersion is an unsigned integer variable containing the 52 /// developer-supplied version number of the engine used to create the 53 /// application.</summary> 54 public UInt32 engineVersion; 55 /// <summary> 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.</summary> 63 public UInt32 apiVersion; 64 } 65 // Struct: 193 66 /// <summary>VkInstanceCreateInfo - Structure specifying parameters of a newly created instance 67 /// </summary> 68 public unsafe struct VkInstanceCreateInfo { 69 /// <summary> sType is the type of this structure.</summary> 70 public VkStructureType sType; 71 /// <summary> pNext is NULL or a pointer to an extension-specific structure.</summary> 72 public /*-const-*/ void* pNext; 73 /// <summary> flags is reserved for future use.</summary> 74 public VkInstanceCreateFlags flags; 75 /// <summary> 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.</summary> 80 public /*-const-*/ VkApplicationInfo* pApplicationInfo; 81 /// <summary> enabledLayerCount is the number of global layers to enable.</summary> 82 public UInt32 enabledLayerCount; 83 /// <summary> 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.</summary> 87 public IntPtr /*-const-*/ * ppEnabledLayerNames; 88 /// <summary> enabledExtensionCount is the number of global extensions to 89 /// enable.</summary> 90 public UInt32 enabledExtensionCount; 91 /// <summary> ppEnabledExtensionNames is a pointer to an array of 92 /// enabledExtensionCount null-terminated UTF-8 strings containing the 93 /// names of extensions to enable.</summary> 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 /// <summary> 2 /// Set a string to specified <paramref name="target"/>. 3 /// </summary> 4 /// <param name="value"></param> 5 /// <param name="target">address of string.</param> 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 /// <summary> 2 /// Set an array of structs to specified <paramref name="target"/> and <paramref name="count"/>. 3 /// <para>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.</para> 5 /// </summary> 6 /// <param name="value"></param> 7 /// <param name="target">address of first element/array.</param> 8 /// <param name="count">How many elements?</param> 9 public static void Set<T>(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<T>(); 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 /// <summary>vkAllocateCommandBuffers - Allocate command buffers from an existing command pool 3 /// </summary> 4 /// <param name="device"> device is the logical device that owns the command pool.</param> 5 /// <param name="pAllocateInfo"> pAllocateInfo is a pointer to an instance of the 6 /// VkCommandBufferAllocateInfo structure describing parameters of the 7 /// allocation.</param> 8 /// <param name="pCommandBuffers"> 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.</param> 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 /// <summary>vkUpdateDescriptorSets - Update the contents of a descriptor set object 20 /// </summary> 21 /// <param name="device"> device is the logical device that updates the descriptor sets.</param> 22 /// <param name="descriptorWriteCount"> descriptorWriteCount is the number of elements in the 23 /// pDescriptorWrites array.</param> 24 /// <param name="pDescriptorWrites"> pDescriptorWrites is a pointer to an array of 25 /// VkWriteDescriptorSet structures describing the descriptor sets to 26 /// write to.</param> 27 /// <param name="descriptorCopyCount"> descriptorCopyCount is the number of elements in the 28 /// pDescriptorCopies array.</param> 29 /// <param name="pDescriptorCopies"> pDescriptorCopies is a pointer to an array of 30 /// VkCopyDescriptorSet structures describing the descriptor sets to 31 /// copy between.</param> 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不會被反復使用,因此,它們最多泄漏一點點內存,不會像服務器代碼那樣占用越來越多的內存,所以不礙事的。
總結
有了這么帶勁的注釋,整個檔次都不一樣了。