*
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,141 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
//using VulkanWrapperGenerator;
|
||||
using System.Linq;
|
||||
|
||||
class Program {
|
||||
static string ResolveVkXmlPath(string[] args) {
|
||||
// 1) Explicit path via arg
|
||||
if (args.Length > 0 && File.Exists(args[0])) return args[0];
|
||||
static void Main(string[] args) {
|
||||
// Resolve vk.xml path
|
||||
string vkXml =
|
||||
args.FirstOrDefault(a => a.EndsWith("vk.xml", StringComparison.OrdinalIgnoreCase))
|
||||
?? GuessVkXmlFromEnv()
|
||||
?? @"C:\VulkanSDK\1.4.313.0\share\vulkan\registry\vk.xml";
|
||||
|
||||
// 2) VULKAN_SDK based locations
|
||||
var sdk = Environment.GetEnvironmentVariable("VULKAN_SDK");
|
||||
if (!string.IsNullOrWhiteSpace(sdk)) {
|
||||
var p1 = Path.Combine(sdk, "share", "vulkan", "registry", "vk.xml");
|
||||
if (File.Exists(p1)) return p1;
|
||||
var p2 = Path.Combine(sdk, "Include", "vulkan", "vk.xml");
|
||||
if (File.Exists(p2)) return p2;
|
||||
if (!File.Exists(vkXml)) {
|
||||
Console.Error.WriteLine($"vk.xml not found: {vkXml}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
// 3) Typical Windows SDK layout (adjust if needed)
|
||||
var winShare = Path.Combine(@"C:\VulkanSDK", "1.4.313.0", "share", "vulkan", "registry", "vk.xml");
|
||||
if (File.Exists(winShare)) return winShare;
|
||||
|
||||
throw new FileNotFoundException(
|
||||
"vk.xml not found. Pass a path as the first argument or set VULKAN_SDK.\n" +
|
||||
"Searched: <arg0>, " +
|
||||
(sdk != null ? $"{sdk}\\share\\vulkan\\registry\\vk.xml, {sdk}\\Include\\vulkan\\vk.xml" : "<VULKAN_SDK unset>") +
|
||||
", C:\\VulkanSDK\\1.4.313.0\\share\\vulkan\\registry\\vk.xml");
|
||||
}
|
||||
|
||||
static void Main(string[] args) {
|
||||
var xmlPath = ResolveVkXmlPath(args);
|
||||
var spec = VkSpec.Load(xmlPath);
|
||||
|
||||
// External "sugar" methods you want to inject into wrappers
|
||||
var sugar = new List<SugarClassSpec>
|
||||
{
|
||||
new SugarClassSpec("Buffer")
|
||||
{
|
||||
Includes = { "<cstring>" },
|
||||
Methods =
|
||||
{
|
||||
new SugarMethodSpec
|
||||
{
|
||||
Name = "uploadData",
|
||||
ReturnType = "void",
|
||||
Parameters = {
|
||||
"const void* data",
|
||||
"size_t size",
|
||||
"IntrusivePtr<DeviceMemory> memory",
|
||||
"VkDeviceSize offset = 0"
|
||||
},
|
||||
RequiresFunctions = { "vkMapMemory", "vkUnmapMemory", "vkBindBufferMemory" },
|
||||
BodyLines = {
|
||||
"void* _p = nullptr;",
|
||||
"auto _r = ${vk}.vkMapMemory(${owner0.handle}, ${memory.handle}, offset, size, 0, &_p);",
|
||||
"if (_r != VK_SUCCESS) throw std::runtime_error(\"vkMapMemory failed\");",
|
||||
"std::memcpy(_p, data, size);",
|
||||
"${vk}.vkUnmapMemory(${owner0.handle});",
|
||||
"(void)${vk}.vkBindBufferMemory(${owner0.handle}, ${handle}, ${memory.handle}, 0);"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
new SugarClassSpec("Image")
|
||||
{
|
||||
Methods =
|
||||
{
|
||||
new SugarMethodSpec
|
||||
{
|
||||
Name = "transitionLayout",
|
||||
ReturnType = "void",
|
||||
Parameters = {
|
||||
"IntrusivePtr<CommandBuffer> cmd",
|
||||
"VkImageLayout oldLayout",
|
||||
"VkImageLayout newLayout",
|
||||
"VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT"
|
||||
},
|
||||
RequiresFunctions = { "vkCmdPipelineBarrier" },
|
||||
RequiresExtensions = { "VK_KHR_synchronization2" }, // example of extension guard
|
||||
BodyLines = {
|
||||
"VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };",
|
||||
"barrier.oldLayout = oldLayout;",
|
||||
"barrier.newLayout = newLayout;",
|
||||
"barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;",
|
||||
"barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;",
|
||||
"barrier.image = ${handle};",
|
||||
"barrier.subresourceRange.aspectMask = aspect;",
|
||||
"barrier.subresourceRange.baseMipLevel = 0;",
|
||||
"barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;",
|
||||
"barrier.subresourceRange.baseArrayLayer = 0;",
|
||||
"barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;",
|
||||
"VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;",
|
||||
"VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;",
|
||||
"${vk}.vkCmdPipelineBarrier(${cmd.handle}, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
new SugarClassSpec("Device")
|
||||
{
|
||||
Includes = { "<vector>", "<fstream>" },
|
||||
Methods =
|
||||
{
|
||||
new SugarMethodSpec
|
||||
{
|
||||
Name = "createShaderModuleFromFile",
|
||||
ReturnType = "IntrusivePtr<ShaderModule>",
|
||||
Parameters = { "const char* path" },
|
||||
RequiresFunctions = { "vkCreateShaderModule" },
|
||||
BodyLines = {
|
||||
"std::ifstream f(path, std::ios::binary | std::ios::ate);",
|
||||
"if (!f) throw std::runtime_error(\"Can't open SPV file\");",
|
||||
"auto sz = (size_t)f.tellg(); f.seekg(0);",
|
||||
"std::vector<uint32_t> code((sz + 3) / 4);",
|
||||
"f.read(reinterpret_cast<char*>(code.data()), sz);",
|
||||
"VkShaderModuleCreateInfo ci{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };",
|
||||
"ci.codeSize = sz;",
|
||||
"ci.pCode = code.data();",
|
||||
"VkShaderModule h = VK_NULL_HANDLE;",
|
||||
"auto r = ${vk}.vkCreateShaderModule(${handle}, &ci, nullptr, &h);",
|
||||
"if (r != VK_SUCCESS) throw std::runtime_error(\"vkCreateShaderModule failed\");",
|
||||
"return IntrusivePtr<ShaderModule>(new ShaderModule(${selfOwners}, h));"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var outDir = "Generated";
|
||||
// Output dir (.../out/VulkanPP)
|
||||
string outRoot = args.FirstOrDefault(a => a.StartsWith("--out="))?.Substring("--out=".Length)
|
||||
?? Path.Combine(Environment.CurrentDirectory, "out");
|
||||
string outDir = Path.Combine(outRoot, "VulkanPP");
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
Console.WriteLine($"Parsing: {vkXml}");
|
||||
var spec = VkSpec.Load(vkXml);
|
||||
|
||||
// Optional sugar methods (empty by default)
|
||||
var sugar = Array.Empty<SugarClassSpec>();
|
||||
|
||||
Console.WriteLine($"Generating to: {outDir}");
|
||||
var gen = new Generator(spec, outDir, sugar);
|
||||
gen.GenerateAll();
|
||||
|
||||
Console.WriteLine("✅ Generated into ./Generated");
|
||||
Console.WriteLine("vk.xml used: " + xmlPath);
|
||||
Console.WriteLine("Done.");
|
||||
}
|
||||
|
||||
static string? GuessVkXmlFromEnv() {
|
||||
var sdk = Environment.GetEnvironmentVariable("VULKAN_SDK");
|
||||
if (string.IsNullOrWhiteSpace(sdk)) return null;
|
||||
string candidate = Path.Combine(sdk, "share", "vulkan", "registry", "vk.xml");
|
||||
return File.Exists(candidate) ? candidate : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class SugarMethodSpec {
|
||||
public string Name { get; init; } = "";
|
||||
public string ReturnType { get; init; } = "void";
|
||||
public List<string> Parameters { get; init; } = new();
|
||||
public List<string> BodyLines { get; init; } = new();
|
||||
|
||||
// Optional compile-time guard (combined with RequiresExtensions)
|
||||
public string? Guard { get; init; }
|
||||
|
||||
// Requirements (checked against vk.xml). Method is generated only if all match.
|
||||
public List<string> RequiresFunctions { get; init; } = new();
|
||||
public List<string> RequiresExtensions { get; init; } = new();
|
||||
|
||||
// Method qualifiers
|
||||
public bool IsConst { get; init; } = false;
|
||||
public bool IsNoexcept { get; init; } = false;
|
||||
public bool IsStatic { get; init; } = false;
|
||||
}
|
||||
|
||||
public sealed class SugarClassSpec {
|
||||
public string TargetClass { get; init; } = "";
|
||||
public List<string> Includes { get; init; } = new();
|
||||
public List<SugarMethodSpec> Methods { get; init; } = new();
|
||||
|
||||
public SugarClassSpec() { }
|
||||
public SugarClassSpec(string targetClass) { TargetClass = targetClass; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Declarative "sugar" description for a generated wrapper class.
|
||||
/// You can provide several specs with the same TargetClass; they will be merged.
|
||||
/// </summary>
|
||||
public sealed class SugarClassSpec {
|
||||
public string TargetClass { get; }
|
||||
public List<string> Includes { get; set; } = new(); // reserved for future use
|
||||
public List<SugarMethodSpec> Methods { get; set; } = new();
|
||||
|
||||
public SugarClassSpec(string targetClass) => TargetClass = targetClass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declarative description of one sugar method to inject into a wrapper.
|
||||
/// </summary>
|
||||
public sealed class SugarMethodSpec {
|
||||
public string Name { get; set; } = ""; // C++ method name
|
||||
public string ReturnType { get; set; } = "void"; // C++ return type
|
||||
public List<string> Parameters { get; set; } = new(); // e.g., "int x", "IntrusivePtr<Buffer> buf"
|
||||
public bool IsStatic { get; set; } = false;
|
||||
public bool IsConst { get; set; } = false;
|
||||
public bool IsNoexcept { get; set; } = false;
|
||||
|
||||
// Body lines with placeholders:
|
||||
// ${handle} -> _handle
|
||||
// ${vk} -> vk
|
||||
// ${owner0.handle} -> first primary owner handle, etc.
|
||||
// For IntrusivePtr<T> params: ${param.handle}
|
||||
// For vector<IntrusivePtr<T>> params: ${param.handles}, ${param.size}/${param.count}
|
||||
public List<string> BodyLines { get; set; } = new();
|
||||
|
||||
// Optional guard expression (e.g., "defined(VK_KHR_swapchain)")
|
||||
public string? Guard { get; set; }
|
||||
|
||||
// Require presence of vk* function names and extension names
|
||||
public List<string> RequiresFunctions { get; set; } = new();
|
||||
public List<string> RequiresExtensions { get; set; } = new();
|
||||
}
|
||||
@@ -1,342 +1,473 @@
|
||||
using System;
|
||||
// VkSpec.cs
|
||||
// Robust vk.xml parser + semantic model for the generator.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
public sealed class VkSpec {
|
||||
// ---------- Data model ----------
|
||||
// ==========================
|
||||
// Public data model
|
||||
// ==========================
|
||||
|
||||
public sealed record Handle(string Name, bool Dispatchable);
|
||||
public sealed record Param(string Raw, string Type, string Name, bool IsPointer, bool IsConst, string? LenAttr, bool IsHandle);
|
||||
public sealed record Command(string Name, string ReturnType, List<Param> Params);
|
||||
|
||||
public sealed record StructMember(string Type, string Name, bool IsPointer, string? LenAttr, bool IsHandle);
|
||||
public sealed record StructDef(string Name, List<StructMember> Members);
|
||||
|
||||
// Owned pair (created/destroyed or allocated/freed)
|
||||
public sealed record Pair(
|
||||
string HandleType,
|
||||
string CreateOrAllocate, string DestroyOrFree,
|
||||
string ParentType,
|
||||
Param OutHandleParam,
|
||||
List<string> ExtraOwnersForDestroy
|
||||
);
|
||||
|
||||
// Simple producer (Get*/Enumerate* with single parent handle and a single out handle param)
|
||||
public sealed record SimpleProducer(
|
||||
string HandleType,
|
||||
string CommandName,
|
||||
string ParentType,
|
||||
Param OutHandleParam,
|
||||
bool IsMany
|
||||
);
|
||||
|
||||
public sealed record OwnerUse(string Type, bool IsMany, string SourceStruct, string SourceMember);
|
||||
|
||||
public Dictionary<string, Handle> Handles { get; private init; } = new();
|
||||
public Dictionary<string, Command> Commands { get; private init; } = new();
|
||||
public Dictionary<string, StructDef> Structs { get; private init; } = new();
|
||||
public HashSet<string> Extensions { get; private init; } = new();
|
||||
public List<Pair> Pairs { get; private init; } = new();
|
||||
public List<SimpleProducer> Producers { get; private init; } = new();
|
||||
|
||||
// ---------- Load & parse ----------
|
||||
|
||||
public static VkSpec Load(string xmlPath) {
|
||||
var doc = XDocument.Load(xmlPath);
|
||||
|
||||
var handles = ParseHandles(doc);
|
||||
var structs = ParseStructs(doc, handles);
|
||||
var (real, aliasElems) = ParseCommandsPre(doc);
|
||||
var cmds = BuildCommands(real, handles);
|
||||
ApplyAliases(cmds, aliasElems, handles);
|
||||
var exts = ParseExtensions(doc);
|
||||
var pairs = BuildPairs(cmds, handles);
|
||||
var producers = BuildSimpleProducers(cmds, handles);
|
||||
|
||||
return new VkSpec {
|
||||
Handles = handles,
|
||||
Structs = structs,
|
||||
Commands = cmds,
|
||||
Extensions = exts,
|
||||
Pairs = pairs,
|
||||
Producers = producers
|
||||
};
|
||||
public sealed class Handle {
|
||||
public string Name = ""; // e.g., "VkBuffer"
|
||||
public bool IsDispatchable; // VK_DEFINE_HANDLE vs NON_DISPATCHABLE
|
||||
public string? Parent; // optional vk.xml "parent" attr
|
||||
}
|
||||
|
||||
static Dictionary<string, Handle> ParseHandles(XDocument doc) {
|
||||
var dict = new Dictionary<string, Handle>();
|
||||
var types = doc.Root!.Element("types")!.Elements("type")
|
||||
.Where(t => (string?)t.Attribute("category") == "handle");
|
||||
|
||||
foreach (var t in types) {
|
||||
var name = t.Element("name")?.Value;
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
var txt = (t.Value ?? "");
|
||||
var m = Regex.Match(txt, @"VK_DEFINE_(?:NON_)?DISPATCHABLE_HANDLE\((Vk\w+)\)");
|
||||
if (m.Success) name = m.Groups[1].Value;
|
||||
}
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
|
||||
var body = t.Value ?? string.Empty;
|
||||
bool dispatchable =
|
||||
body.Contains("VK_DEFINE_HANDLE(") ||
|
||||
body.Contains("VK_DEFINE_DISPATCHABLE_HANDLE(");
|
||||
dict[name!] = new Handle(name!, dispatchable);
|
||||
}
|
||||
return dict;
|
||||
public sealed class Param {
|
||||
public string Name = ""; // e.g., "pCreateInfo"
|
||||
public string Type = ""; // e.g., "VkBufferCreateInfo"
|
||||
public bool IsPointer; // '*' in declaration
|
||||
public bool IsConst; // "const "
|
||||
public string? LenAttr; // len="..."
|
||||
public bool IsHandle; // Type is a known handle
|
||||
}
|
||||
|
||||
static Dictionary<string, StructDef> ParseStructs(XDocument doc, Dictionary<string, Handle> handles) {
|
||||
var dict = new Dictionary<string, StructDef>();
|
||||
var xs = doc.Root!.Element("types")!.Elements("type")
|
||||
.Where(t => (string?)t.Attribute("category") == "struct");
|
||||
|
||||
foreach (var t in xs) {
|
||||
var name = t.Element("name")?.Value ?? (string?)t.Attribute("name");
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
|
||||
var members = new List<StructMember>();
|
||||
foreach (var m in t.Elements("member")) {
|
||||
var type = m.Element("type")?.Value ?? "void";
|
||||
var mname = m.Element("name")?.Value ?? "";
|
||||
var raw = Regex.Replace(m.Value ?? "", @"\s+", " ");
|
||||
bool isPtr = raw.Contains("*");
|
||||
string? len = (string?)m.Attribute("len");
|
||||
bool isHandle = handles.ContainsKey(type);
|
||||
members.Add(new StructMember(type, mname, isPtr, len, isHandle));
|
||||
}
|
||||
dict[name!] = new StructDef(name!, members);
|
||||
}
|
||||
|
||||
return dict;
|
||||
public sealed class Command {
|
||||
public string Name = ""; // e.g., "vkCreateBuffer"
|
||||
public string ReturnType = "void"; // e.g., "VkResult"
|
||||
public List<Param> Params = new(); // parameters in order
|
||||
public HashSet<string> ProtectMacros { get; } = new(StringComparer.Ordinal); // VK_USE_PLATFORM_*
|
||||
public string? AliasOf; // if this command is alias of another
|
||||
}
|
||||
|
||||
static (List<XElement> real, List<XElement> aliases) ParseCommandsPre(XDocument doc) {
|
||||
var all = doc.Root!.Element("commands")!.Elements("command").ToList();
|
||||
var real = new List<XElement>();
|
||||
var aliases = new List<XElement>();
|
||||
foreach (var c in all) {
|
||||
if (c.Attribute("alias") != null) aliases.Add(c);
|
||||
else if (c.Element("proto") != null) real.Add(c);
|
||||
}
|
||||
return (real, aliases);
|
||||
public sealed class StructMember {
|
||||
public string Name = "";
|
||||
public string Type = "";
|
||||
public bool IsPointer;
|
||||
public bool IsConst;
|
||||
public string? LenAttr;
|
||||
public bool IsHandle;
|
||||
public bool IsStruct;
|
||||
}
|
||||
|
||||
static string Cleanup(string s) => Regex.Replace(s.Trim(), @"\s+", " ");
|
||||
|
||||
static Dictionary<string, Command> BuildCommands(List<XElement> realCmds, Dictionary<string, Handle> handles) {
|
||||
bool IsPointer(string raw) => raw.Contains("*");
|
||||
bool IsConst(string raw) => raw.Contains("const ");
|
||||
|
||||
var dict = new Dictionary<string, Command>();
|
||||
|
||||
foreach (var c in realCmds) {
|
||||
var proto = c.Element("proto")!;
|
||||
var name = proto.Element("name")!.Value;
|
||||
|
||||
string retType = string.Concat(proto.Nodes().Select(n => {
|
||||
if (n is XElement e) return e.Name == "name" ? "" : e.Value;
|
||||
return ((XText)n).Value;
|
||||
}));
|
||||
retType = Cleanup(retType.Replace(name, ""));
|
||||
|
||||
var prms = new List<Param>();
|
||||
foreach (var p in c.Elements("param")) {
|
||||
var type = p.Element("type")?.Value ?? "void";
|
||||
var pname = p.Element("name")?.Value ?? "";
|
||||
var raw = Cleanup(p.Value);
|
||||
var len = (string?)p.Attribute("len");
|
||||
bool isPtr = IsPointer(raw);
|
||||
bool isConst = IsConst(raw);
|
||||
bool isHandle = handles.ContainsKey(type);
|
||||
prms.Add(new Param(raw, type, pname, isPtr, isConst, len, isHandle));
|
||||
}
|
||||
|
||||
dict[name] = new Command(name, retType, prms);
|
||||
}
|
||||
|
||||
return dict;
|
||||
public sealed class VkStruct {
|
||||
public string Name = "";
|
||||
public List<StructMember> Members = new();
|
||||
}
|
||||
|
||||
static void ApplyAliases(Dictionary<string, Command> dict, List<XElement> aliases, Dictionary<string, Handle> handles) {
|
||||
foreach (var c in aliases) {
|
||||
var name = (string?)c.Attribute("name");
|
||||
var aliasOf = (string?)c.Attribute("alias");
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(aliasOf)) continue;
|
||||
|
||||
if (dict.TryGetValue(aliasOf!, out var baseCmd)) {
|
||||
var clone = new Command(name!, baseCmd.ReturnType,
|
||||
baseCmd.Params.Select(p => new Param(p.Raw, p.Type, p.Name, p.IsPointer, p.IsConst, p.LenAttr, p.IsHandle)).ToList());
|
||||
dict[name!] = clone;
|
||||
}
|
||||
}
|
||||
public sealed class Extension {
|
||||
public string Name = "";
|
||||
public string? Protect; // VK_USE_PLATFORM_* if present
|
||||
public string? Platform; // platform="android"/"win32"/...
|
||||
public string? Supported; // "vulkan", "vulkansc", "disabled", ...
|
||||
public List<string> RequireCommands = new();
|
||||
}
|
||||
|
||||
static HashSet<string> ParseExtensions(XDocument doc) {
|
||||
var set = new HashSet<string>(StringComparer.Ordinal);
|
||||
var exts = doc.Root!.Element("extensions")!.Elements("extension");
|
||||
foreach (var e in exts) {
|
||||
var name = (string?)e.Attribute("name");
|
||||
if (!string.IsNullOrWhiteSpace(name)) set.Add(name!);
|
||||
}
|
||||
return set;
|
||||
public sealed class Pair {
|
||||
public string HandleType = ""; // e.g., "VkBuffer"
|
||||
public string ParentType = ""; // creation/alloc parent (may be empty for Instance)
|
||||
public string CreateOrAllocate = ""; // e.g., "vkCreateBuffer" or "vkAllocateCommandBuffers"
|
||||
public string DestroyOrFree = ""; // e.g., "vkDestroyBuffer" or "vkFreeCommandBuffers"
|
||||
public Param OutHandleParam = new(); // pBuffer / pCommandBuffers
|
||||
public List<string> ExtraOwnersForDestroy = new();
|
||||
}
|
||||
|
||||
static List<Pair> BuildPairs(Dictionary<string, Command> cmds, Dictionary<string, Handle> handles) {
|
||||
var list = new List<Pair>();
|
||||
|
||||
// vkCreateX / vkDestroyX
|
||||
foreach (var create in cmds.Values.Where(x => x.Name.StartsWith("vkCreate"))) {
|
||||
var obj = create.Name.Substring("vkCreate".Length);
|
||||
var destroyName = "vkDestroy" + obj;
|
||||
if (!cmds.TryGetValue(destroyName, out var destroy)) continue;
|
||||
|
||||
var parent = create.Params.First().Type;
|
||||
var outParam = create.Params.LastOrDefault(p => p.IsPointer && handles.ContainsKey(p.Type));
|
||||
if (outParam is null) continue;
|
||||
|
||||
var handleType = outParam.Type;
|
||||
var extraOwners = destroy.Params
|
||||
.Where(p => p.IsHandle && p.Type != handleType)
|
||||
.Select(p => p.Type).Distinct().ToList();
|
||||
|
||||
list.Add(new Pair(handleType, create.Name, destroyName, parent, outParam, extraOwners));
|
||||
}
|
||||
|
||||
// vkAllocateX / vkFreeX
|
||||
foreach (var alloc in cmds.Values.Where(x => x.Name.StartsWith("vkAllocate"))) {
|
||||
var obj = alloc.Name.Substring("vkAllocate".Length);
|
||||
var freeName = "vkFree" + obj;
|
||||
if (!cmds.TryGetValue(freeName, out var free)) continue;
|
||||
|
||||
var parent = alloc.Params.First().Type;
|
||||
var outParam = alloc.Params.LastOrDefault(p => p.IsPointer && handles.ContainsKey(p.Type));
|
||||
if (outParam is null) continue;
|
||||
|
||||
var handleType = outParam.Type;
|
||||
var extraOwners = free.Params
|
||||
.Where(p => p.IsHandle && p.Type != handleType)
|
||||
.Select(p => p.Type).Distinct().ToList();
|
||||
|
||||
list.Add(new Pair(handleType, alloc.Name, freeName, parent, outParam, extraOwners));
|
||||
}
|
||||
|
||||
return list
|
||||
.GroupBy(p => p.HandleType)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
public sealed class Producer {
|
||||
public string CommandName = ""; // e.g., "vkGetDeviceQueue"
|
||||
public string ParentType = ""; // first param type
|
||||
public string HandleType = ""; // produced handle type
|
||||
public Param OutHandleParam = new(); // pQueue / pPhysicalDevices
|
||||
public bool IsMany; // has len=""
|
||||
}
|
||||
|
||||
// Build "simple producers": vkGet*/vkEnumerate* that output a handle with exactly one parent handle argument
|
||||
static List<SimpleProducer> BuildSimpleProducers(Dictionary<string, Command> cmds, Dictionary<string, Handle> handles) {
|
||||
var list = new List<SimpleProducer>();
|
||||
|
||||
foreach (var c in cmds.Values) {
|
||||
if (!(c.Name.StartsWith("vkGet") || c.Name.StartsWith("vkEnumerate")))
|
||||
continue;
|
||||
|
||||
// pick first handle output param (pointer to handle)
|
||||
int outIdx = c.Params.FindIndex(p => p.IsPointer && handles.ContainsKey(p.Type));
|
||||
if (outIdx < 0) continue;
|
||||
var outParam = c.Params[outIdx];
|
||||
|
||||
// consider handle params BEFORE out param as potential parents
|
||||
var parents = c.Params.Take(outIdx).Where(p => p.IsHandle).ToList();
|
||||
if (parents.Count != 1) continue; // only "simple" case supported: a single parent
|
||||
|
||||
var parent = parents[0].Type;
|
||||
bool isMany = outParam.LenAttr != null;
|
||||
|
||||
list.Add(new SimpleProducer(outParam.Type, c.Name, parent, outParam, isMany));
|
||||
}
|
||||
|
||||
return list;
|
||||
public sealed class OwnerUse {
|
||||
public string Type = ""; // handle type to keep
|
||||
public bool IsMany; // array vs single
|
||||
public string SourceStruct = "";
|
||||
public string SourceMember = "";
|
||||
}
|
||||
|
||||
public enum LoadLevel { Global, Instance, Device }
|
||||
public LoadLevel ClassifyLoadLevel(Command c) {
|
||||
if (c.Params.Count == 0) return LoadLevel.Global;
|
||||
var first = c.Params[0].Type;
|
||||
if (first == "VkInstance" || first == "VkPhysicalDevice") return LoadLevel.Instance;
|
||||
if (first == "VkDevice" || first == "VkQueue" || first == "VkCommandBuffer") return LoadLevel.Device;
|
||||
return LoadLevel.Global;
|
||||
|
||||
public Dictionary<string, Handle> Handles { get; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, Command> Commands { get; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, VkStruct> Structs { get; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, Extension> Extensions { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
public List<Pair> Pairs { get; } = new();
|
||||
public List<Producer> Producers { get; } = new();
|
||||
|
||||
public Dictionary<string, string> PlatformGuards { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
public HashSet<string> ReachableCommands { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
// ==========================
|
||||
// Load
|
||||
// ==========================
|
||||
public static VkSpec Load(string vkXmlPath) {
|
||||
if (!File.Exists(vkXmlPath))
|
||||
throw new FileNotFoundException("vk.xml not found", vkXmlPath);
|
||||
|
||||
var xdoc = XDocument.Load(vkXmlPath,
|
||||
LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
|
||||
|
||||
var spec = new VkSpec();
|
||||
var registry = xdoc.Root ?? throw new InvalidDataException("Malformed vk.xml: missing <registry>");
|
||||
|
||||
spec.ParseTypes(registry);
|
||||
spec.ParseCommands(registry);
|
||||
spec.ParsePlatforms(registry);
|
||||
spec.ParseExtensions(registry);
|
||||
spec.ParseFeatures(registry, "vulkan");
|
||||
spec.ComputeReachableCommands("vulkan");
|
||||
spec.BuildPairsAndProducers();
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
// ---------- Deterministic keep filter ----------
|
||||
public static class LifetimeFilter {
|
||||
// Explicit "do not keep" cases (semantically not required after creation)
|
||||
private static readonly HashSet<(string st, string mem, string ht)> Exact = new()
|
||||
{
|
||||
("VkPipelineShaderStageCreateInfo", "module", "VkShaderModule"),
|
||||
("VkFramebufferCreateInfo", "renderPass", "VkRenderPass"),
|
||||
("VkGraphicsPipelineCreateInfo", "renderPass", "VkRenderPass"),
|
||||
("VkSwapchainCreateInfoKHR", "oldSwapchain", "VkSwapchainKHR"),
|
||||
};
|
||||
// --------------------------
|
||||
// <types>
|
||||
// --------------------------
|
||||
void ParseTypes(XElement registry) {
|
||||
var types = registry.Element("types") ?? throw new InvalidDataException("<types> missing");
|
||||
|
||||
private static readonly string[] NameHints = { "old", "scratch", "staging" };
|
||||
foreach (var t in types.Elements("type")) {
|
||||
var category = (string?)t.Attribute("category") ?? "";
|
||||
|
||||
public static bool ShouldKeep(string structName, string memberName, string handleType) {
|
||||
if (Exact.Contains((structName, memberName, handleType)))
|
||||
return false;
|
||||
if (category == "handle") {
|
||||
var name = (string?)t.Element("name") ?? (string?)t.Attribute("name") ?? "";
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
|
||||
var mn = memberName.ToLowerInvariant();
|
||||
if (NameHints.Any(h => mn.Contains(h)))
|
||||
return false;
|
||||
string inner = string.Concat(t.Nodes().OfType<XText>().Select(n => n.Value));
|
||||
bool isDispatchable = inner.Contains("VK_DEFINE_HANDLE", StringComparison.Ordinal);
|
||||
|
||||
// Immutable samplers ARE kept
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Handles[name] = new Handle {
|
||||
Name = name,
|
||||
IsDispatchable = isDispatchable,
|
||||
Parent = (string?)t.Attribute("parent")
|
||||
};
|
||||
} else if (category == "struct") {
|
||||
var name = (string?)t.Attribute("name") ?? "";
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
|
||||
// Traverse CreateInfo/AllocateInfo trees and collect keep-alive owners
|
||||
public List<OwnerUse> AnalyzeCreateInfoOwners(Command create, string handleType) {
|
||||
var owners = new Dictionary<(string Type, string Struct, string Member), OwnerUse>();
|
||||
var s = new VkStruct { Name = name };
|
||||
foreach (var mem in t.Elements("member")) {
|
||||
var m = new StructMember {
|
||||
Name = (string?)mem.Element("name") ?? "",
|
||||
Type = (string?)mem.Element("type") ?? ""
|
||||
};
|
||||
m.IsHandle = Handles.ContainsKey(m.Type);
|
||||
|
||||
void Mark(string t, bool many, string srcStruct, string srcMember) {
|
||||
if (t == handleType) return; // do not keep self
|
||||
if (!LifetimeFilter.ShouldKeep(srcStruct, srcMember, t)) return;
|
||||
|
||||
var key = (t, srcStruct, srcMember);
|
||||
if (owners.TryGetValue(key, out var ex))
|
||||
owners[key] = new OwnerUse(t, ex.IsMany || many, srcStruct, srcMember);
|
||||
else
|
||||
owners[key] = new OwnerUse(t, many, srcStruct, srcMember);
|
||||
}
|
||||
|
||||
var stack = new Stack<(string type, bool many)>();
|
||||
|
||||
foreach (var p in create.Params.Where(p => p.Name.Contains("CreateInfo") || p.Name.Contains("AllocateInfo"))) {
|
||||
if (Structs.ContainsKey(p.Type))
|
||||
stack.Push((p.Type, p.IsPointer && p.LenAttr != null));
|
||||
}
|
||||
|
||||
var visited = new HashSet<string>();
|
||||
while (stack.Count > 0) {
|
||||
var (stype, parentMany) = stack.Pop();
|
||||
if (!visited.Add(stype)) { /* ok */ }
|
||||
|
||||
if (!Structs.TryGetValue(stype, out var s)) continue;
|
||||
foreach (var m in s.Members) {
|
||||
bool manyHere = parentMany || (m.IsPointer && m.LenAttr != null);
|
||||
|
||||
if (Handles.ContainsKey(m.Type)) {
|
||||
Mark(m.Type, manyHere, stype, m.Name);
|
||||
} else if (Structs.ContainsKey(m.Type)) {
|
||||
stack.Push((m.Type, manyHere));
|
||||
string decl = GetNodeText(mem);
|
||||
m.IsPointer = decl.Contains("*");
|
||||
m.IsConst = decl.Contains("const ");
|
||||
m.LenAttr = (string?)mem.Attribute("len");
|
||||
m.IsStruct = (!m.IsHandle && Structs.ContainsKey(m.Type));
|
||||
s.Members.Add(m);
|
||||
}
|
||||
Structs[name] = s;
|
||||
}
|
||||
}
|
||||
|
||||
return owners.Values
|
||||
.GroupBy(o => o.Type)
|
||||
.Select(g => new OwnerUse(
|
||||
g.Key,
|
||||
g.Any(x => x.IsMany),
|
||||
g.First().SourceStruct,
|
||||
g.First().SourceMember
|
||||
))
|
||||
.ToList();
|
||||
// second pass mark struct-refs
|
||||
foreach (var s in Structs.Values)
|
||||
foreach (var m in s.Members)
|
||||
if (!m.IsHandle && Structs.ContainsKey(m.Type))
|
||||
m.IsStruct = true;
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// <commands>
|
||||
// --------------------------
|
||||
void ParseCommands(XElement registry) {
|
||||
var commands = registry.Element("commands") ?? throw new InvalidDataException("<commands> missing");
|
||||
var aliasToTarget = new List<(string alias, string target)>();
|
||||
|
||||
foreach (var ce in commands.Elements("command")) {
|
||||
var aliasAttr = (string?)ce.Attribute("alias");
|
||||
var nameAttr = (string?)ce.Attribute("name");
|
||||
if (!string.IsNullOrEmpty(aliasAttr) && !string.IsNullOrEmpty(nameAttr)) {
|
||||
Commands[nameAttr] = new Command { Name = nameAttr, AliasOf = aliasAttr };
|
||||
aliasToTarget.Add((nameAttr, aliasAttr));
|
||||
continue;
|
||||
}
|
||||
|
||||
var proto = ce.Element("proto");
|
||||
if (proto == null) continue;
|
||||
|
||||
var cmdName = (string?)proto.Element("name") ?? "";
|
||||
if (string.IsNullOrWhiteSpace(cmdName)) continue;
|
||||
|
||||
var retType = (string?)proto.Element("type") ?? "void";
|
||||
var c = new Command { Name = cmdName, ReturnType = retType };
|
||||
|
||||
foreach (var pe in ce.Elements("param")) {
|
||||
var p = new Param {
|
||||
Name = (string?)pe.Element("name") ?? "",
|
||||
Type = (string?)pe.Element("type") ?? ""
|
||||
};
|
||||
p.IsHandle = Handles.ContainsKey(p.Type);
|
||||
|
||||
string decl = GetNodeText(pe);
|
||||
p.IsPointer = decl.Contains("*");
|
||||
p.IsConst = decl.Contains("const ");
|
||||
p.LenAttr = (string?)pe.Attribute("len");
|
||||
|
||||
c.Params.Add(p);
|
||||
}
|
||||
|
||||
Commands[cmdName] = c;
|
||||
}
|
||||
|
||||
// resolve aliases
|
||||
foreach (var (alias, target) in aliasToTarget) {
|
||||
if (!Commands.TryGetValue(alias, out var a) || !Commands.TryGetValue(target, out var t)) continue;
|
||||
a.ReturnType = t.ReturnType;
|
||||
a.Params = t.Params.Select(pp => new Param {
|
||||
Name = pp.Name,
|
||||
Type = pp.Type,
|
||||
IsConst = pp.IsConst,
|
||||
IsPointer = pp.IsPointer,
|
||||
LenAttr = pp.LenAttr,
|
||||
IsHandle = pp.IsHandle
|
||||
}).ToList();
|
||||
a.ProtectMacros.UnionWith(t.ProtectMacros);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// <platforms>
|
||||
// --------------------------
|
||||
void ParsePlatforms(XElement registry) {
|
||||
var plats = registry.Element("platforms");
|
||||
if (plats == null) return;
|
||||
|
||||
foreach (var p in plats.Elements("platform")) {
|
||||
var name = (string?)p.Attribute("name");
|
||||
var protect = (string?)p.Attribute("protect");
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(protect)) continue;
|
||||
PlatformGuards[name] = protect; // android -> VK_USE_PLATFORM_ANDROID_KHR
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// <extensions>
|
||||
// --------------------------
|
||||
void ParseExtensions(XElement registry) {
|
||||
var exts = registry.Element("extensions");
|
||||
if (exts == null) return;
|
||||
|
||||
foreach (var xe in exts.Elements("extension")) {
|
||||
var ext = new Extension {
|
||||
Name = (string?)xe.Attribute("name") ?? "",
|
||||
Protect = (string?)xe.Attribute("protect"),
|
||||
Platform = (string?)xe.Attribute("platform"),
|
||||
Supported = (string?)xe.Attribute("supported")
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(ext.Name)) continue;
|
||||
|
||||
// If protect isn't set but platform is known, resolve guard
|
||||
if (ext.Protect == null && ext.Platform != null &&
|
||||
PlatformGuards.TryGetValue(ext.Platform, out var guardMacro))
|
||||
ext.Protect = guardMacro;
|
||||
|
||||
foreach (var req in xe.Elements("require")) {
|
||||
foreach (var cmd in req.Elements("command")) {
|
||||
var cmdName = (string?)cmd.Attribute("name") ?? "";
|
||||
if (string.IsNullOrWhiteSpace(cmdName)) continue;
|
||||
ext.RequireCommands.Add(cmdName);
|
||||
|
||||
if (ext.Protect != null && Commands.TryGetValue(cmdName, out var c))
|
||||
c.ProtectMacros.Add(ext.Protect);
|
||||
}
|
||||
}
|
||||
|
||||
Extensions[ext.Name] = ext;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// <feature api="...">
|
||||
// --------------------------
|
||||
void ParseFeatures(XElement registry, string apiFilter) {
|
||||
foreach (var feat in registry.Elements("feature")) {
|
||||
var api = (string?)feat.Attribute("api") ?? "vulkan";
|
||||
if (!api.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Contains(apiFilter, StringComparer.Ordinal))
|
||||
continue;
|
||||
|
||||
foreach (var req in feat.Elements("require"))
|
||||
foreach (var cmd in req.Elements("command")) {
|
||||
var name = (string?)cmd.Attribute("name");
|
||||
if (!string.IsNullOrWhiteSpace(name)) ReachableCommands.Add(name!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Compute enabled commands (core + enabled extensions)
|
||||
// --------------------------
|
||||
void ComputeReachableCommands(string apiFilter) {
|
||||
foreach (var ext in Extensions.Values) {
|
||||
var supported = (ext.Supported ?? "vulkan")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
bool ok = supported.Contains(apiFilter, StringComparer.Ordinal)
|
||||
&& !supported.Contains("disabled", StringComparer.Ordinal);
|
||||
if (!ok) continue;
|
||||
|
||||
foreach (var cmd in ext.RequireCommands)
|
||||
ReachableCommands.Add(cmd);
|
||||
}
|
||||
|
||||
// Alias coherence
|
||||
foreach (var c in Commands.Values)
|
||||
if (c.AliasOf != null && ReachableCommands.Contains(c.AliasOf))
|
||||
ReachableCommands.Add(c.Name);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Pairs + Producers
|
||||
// --------------------------
|
||||
void BuildPairsAndProducers() {
|
||||
// Create/Allocate
|
||||
foreach (var c in Commands.Values) {
|
||||
if (!ReachableCommands.Contains(c.Name)) continue;
|
||||
|
||||
bool isCreate = c.Name.StartsWith("vkCreate", StringComparison.Ordinal);
|
||||
bool isAlloc = c.Name.StartsWith("vkAllocate", StringComparison.Ordinal);
|
||||
if (!isCreate && !isAlloc) continue;
|
||||
|
||||
var outHandle = c.Params.FirstOrDefault(p => p.IsPointer && p.IsHandle);
|
||||
if (outHandle == null) continue;
|
||||
|
||||
var handleType = outHandle.Type;
|
||||
if (!Handles.ContainsKey(handleType)) continue;
|
||||
|
||||
string parentType = "";
|
||||
if (c.Params.Count > 0 && Handles.ContainsKey(c.Params[0].Type))
|
||||
parentType = c.Params[0].Type;
|
||||
|
||||
var destroy = FindDestroyOrFreeFor(handleType);
|
||||
|
||||
var extraOwners = new List<string>();
|
||||
if (destroy != null) {
|
||||
foreach (var p in destroy.Params) {
|
||||
if (!p.IsHandle) continue;
|
||||
if (p.Type == handleType) continue;
|
||||
if (parentType != "" && p.Type == parentType) continue;
|
||||
if (!extraOwners.Contains(p.Type)) extraOwners.Add(p.Type);
|
||||
}
|
||||
}
|
||||
|
||||
Pairs.Add(new Pair {
|
||||
HandleType = handleType,
|
||||
ParentType = parentType,
|
||||
CreateOrAllocate = c.Name,
|
||||
DestroyOrFree = destroy?.Name ?? "",
|
||||
OutHandleParam = outHandle,
|
||||
ExtraOwnersForDestroy = extraOwners
|
||||
});
|
||||
}
|
||||
|
||||
// Producers
|
||||
foreach (var c in Commands.Values) {
|
||||
if (!ReachableCommands.Contains(c.Name)) continue;
|
||||
bool isProducerName = c.Name.StartsWith("vkGet", StringComparison.Ordinal) ||
|
||||
c.Name.StartsWith("vkEnumerate", StringComparison.Ordinal);
|
||||
if (!isProducerName) continue;
|
||||
|
||||
if (c.Params.Count == 0) continue;
|
||||
var parent = c.Params[0];
|
||||
if (!Handles.ContainsKey(parent.Type)) continue;
|
||||
|
||||
var outHandle = c.Params.FirstOrDefault(p => p.IsPointer && Handles.ContainsKey(p.Type));
|
||||
if (outHandle == null) continue;
|
||||
|
||||
Producers.Add(new Producer {
|
||||
CommandName = c.Name,
|
||||
ParentType = parent.Type,
|
||||
HandleType = outHandle.Type,
|
||||
OutHandleParam = outHandle,
|
||||
IsMany = !string.IsNullOrEmpty(outHandle.LenAttr)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Command? FindDestroyOrFreeFor(string handleType) {
|
||||
foreach (var c in Commands.Values) {
|
||||
if (!(c.Name.Contains("vkDestroy", StringComparison.Ordinal) ||
|
||||
c.Name.Contains("vkFree", StringComparison.Ordinal)))
|
||||
continue;
|
||||
|
||||
if (!c.Params.Any(p => p.Type == handleType)) continue;
|
||||
if (c.Params.Count == 0 || !Handles.ContainsKey(c.Params[0].Type)) continue;
|
||||
if (!ReachableCommands.Contains(c.Name)) continue;
|
||||
|
||||
return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Semantics for generator
|
||||
// --------------------------
|
||||
public LoadLevel ClassifyLoadLevel(Command c) {
|
||||
if (c.Params.Count == 0) return LoadLevel.Global;
|
||||
|
||||
var firstType = c.Params[0].Type;
|
||||
if (!Handles.TryGetValue(firstType, out var hFirst) || !hFirst.IsDispatchable)
|
||||
return LoadLevel.Global;
|
||||
|
||||
if (firstType == "VkInstance" || firstType == "VkPhysicalDevice")
|
||||
return LoadLevel.Instance;
|
||||
|
||||
return LoadLevel.Device;
|
||||
}
|
||||
|
||||
public IEnumerable<OwnerUse> AnalyzeCreateInfoOwners(Command createOrAlloc, string handleType) {
|
||||
var infoParam = createOrAlloc.Params.FirstOrDefault(p =>
|
||||
p.Type.EndsWith("CreateInfo", StringComparison.Ordinal) ||
|
||||
p.Type.EndsWith("AllocateInfo", StringComparison.Ordinal));
|
||||
if (infoParam == null) return Enumerable.Empty<OwnerUse>();
|
||||
|
||||
if (!Structs.TryGetValue(infoParam.Type, out var infoStruct))
|
||||
return Enumerable.Empty<OwnerUse>();
|
||||
|
||||
var results = new List<OwnerUse>();
|
||||
var visited = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
void Recurse(string structName, bool arrayCtx) {
|
||||
if (!Structs.TryGetValue(structName, out var s)) return;
|
||||
if (!visited.Add(structName)) return;
|
||||
|
||||
foreach (var m in s.Members) {
|
||||
if (m.IsHandle) {
|
||||
if (m.Type != handleType) {
|
||||
bool isMany = arrayCtx || m.IsPointer || !string.IsNullOrEmpty(m.LenAttr);
|
||||
results.Add(new OwnerUse {
|
||||
Type = m.Type,
|
||||
IsMany = isMany,
|
||||
SourceStruct = structName,
|
||||
SourceMember = isMany ? m.Name + "[]" : m.Name
|
||||
});
|
||||
}
|
||||
} else if (m.IsStruct) {
|
||||
bool childArr = arrayCtx || m.IsPointer || !string.IsNullOrEmpty(m.LenAttr);
|
||||
Recurse(m.Type, childArr);
|
||||
}
|
||||
}
|
||||
|
||||
visited.Remove(structName);
|
||||
}
|
||||
|
||||
Recurse(infoStruct.Name, false);
|
||||
|
||||
return results
|
||||
.GroupBy(r => (r.Type, r.IsMany, r.SourceStruct, r.SourceMember))
|
||||
.Select(g => g.First());
|
||||
}
|
||||
|
||||
static string GetNodeText(XElement e) {
|
||||
return string.Concat(e.Nodes().Select(n => n switch {
|
||||
XText t => t.Value,
|
||||
XElement x => x.Value,
|
||||
_ => ""
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user