using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; public sealed class Generator { private readonly VkSpec _spec; private readonly string _outDir; private readonly ReadOnlyDictionary _sugar; // Per-class header/source builders private readonly Dictionary _headers = new(StringComparer.Ordinal); private readonly Dictionary _sources = new(StringComparer.Ordinal); // Forward declarations for headers, and includes needed in .cpp private readonly Dictionary> _headerForwards = new(StringComparer.Ordinal); private readonly Dictionary> _cppIncludes = new(StringComparer.Ordinal); public Generator(VkSpec spec, string outDir, IEnumerable? sugarSpecs = null) { _spec = spec; _outDir = outDir; _sugar = new ReadOnlyDictionary( (sugarSpecs ?? new List()) .Where(s => !string.IsNullOrWhiteSpace(s.TargetClass)) .GroupBy(s => s.TargetClass) .ToDictionary(g => g.Key, g => { var merged = new SugarClassSpec(g.Key) { Includes = g.SelectMany(x => x.Includes).Distinct().ToList(), Methods = g.SelectMany(x => x.Methods).ToList() }; return merged; }) ); } public void GenerateAll() { GenerateVulkanFunctions(); GenerateHandleWrappersSkeletons(); // class skeletons (.hpp) + ctor/dtor declarations GenerateFactories(); // ctor / createMany: prototypes (inside class) + .cpp bodies GenerateProducerMemberMethods(); // get*/enumerate*: prototypes (inside class) + .cpp bodies GenerateMemberMethods(); // other methods: prototypes (inside class) + .cpp bodies GenerateSugarMethods(); // sugar: prototypes (inside class) + .cpp bodies FinalizeHeadersAndSources(); // insert forwards; write .hpp/.cpp files } // ---------- helpers ---------- IEnumerable EnabledCommands() => _spec.Commands.Values.Where(c => _spec.ReachableCommands.Contains(c.Name)); static string ToClass(string vkType) => vkType.StartsWith("Vk") ? vkType.Substring(2) : vkType; static string MakeMethodName(string className, string vkFunc) { string s = vkFunc.StartsWith("vk") ? vkFunc.Substring(2) : vkFunc; var tokens = new List { className }; if (className == "CommandBuffer") tokens.Add("Cmd"); foreach (var t in tokens) if (s.StartsWith(t, StringComparison.Ordinal)) { s = s.Substring(t.Length); break; } foreach (var t in tokens) if (s.EndsWith(t, StringComparison.Ordinal)) { s = s.Substring(0, s.Length - t.Length); break; } int idx = s.IndexOf(className, StringComparison.Ordinal); if (idx >= 0) s = s.Remove(idx, className.Length); if (s.Length == 0) return ""; return char.ToLowerInvariant(s[0]) + s.Substring(1); } static string GuardExpr(HashSet macros) { if (macros == null || macros.Count == 0) return ""; return string.Join(" || ", macros.Select(m => "defined(" + m + ")")); } static string InsertIntoClassText(string text, string className, string snippet) { // find "class " then the opening '{' int cls = text.IndexOf("class " + className, StringComparison.Ordinal); if (cls < 0) return text; int open = text.IndexOf('{', cls); if (open < 0) return text; // walk and find the matching closing '}' for that class body int depth = 1; int i = open + 1; for (; i < text.Length; i++) { char c = text[i]; if (c == '{') depth++; else if (c == '}') { depth--; if (depth == 0) { // insert BEFORE this '}' (i points at the closing brace) var ins = snippet; if (!ins.StartsWith("\n")) ins = "\n" + ins; // keep formatting clean return text.Insert(i, ins); } } } // fallback: nothing inserted return text; } void AddToHeaderClass(string className, string snippet) { if (!_headers.TryGetValue(className, out var h)) return; var s = h.ToString(); s = InsertIntoClassText(s, className, snippet); _headers[className] = new StringBuilder(s); } void TouchHeader(string className) { if (_headers.ContainsKey(className)) return; var h = new StringBuilder(); h.AppendLine("#pragma once"); h.AppendLine("#include "); h.AppendLine("#include "); h.AppendLine("#include "); h.AppendLine("#include "); h.AppendLine(); h.AppendLine("namespace VulkanPP {"); h.AppendLine(); // forward declarations inserted later _headers[className] = h; // Init .cpp with own include; other includes inserted later var s = new StringBuilder(); s.AppendLine("#include "); s.AppendLine(); s.AppendLine("namespace VulkanPP {"); s.AppendLine(); _sources[className] = s; } void AddHeaderForward(string className, string fwd) { if (fwd == className) return; if (!_headerForwards.TryGetValue(className, out var set)) _headerForwards[className] = set = new(StringComparer.Ordinal); set.Add(fwd); } void AddCppInclude(string className, string need) { if (need == className) return; if (!_cppIncludes.TryGetValue(className, out var set)) _cppIncludes[className] = set = new(StringComparer.Ordinal); set.Add(need); } // ========================================= // VulkanFunctions — PFN wrappers + loaders with platform guards // ========================================= void GenerateVulkanFunctions() { var sbH = new StringBuilder(); sbH.AppendLine("#pragma once"); sbH.AppendLine("#include "); sbH.AppendLine("#include "); sbH.AppendLine("#include "); sbH.AppendLine("#include "); sbH.AppendLine(); sbH.AppendLine("namespace VulkanPP {"); sbH.AppendLine(); sbH.AppendLine("// Small callable wrapper around PFN_vk* that throws if not loaded."); sbH.AppendLine("template "); sbH.AppendLine("struct Fn {"); sbH.AppendLine(" PFN p = nullptr;"); sbH.AppendLine(" const char* name = nullptr;"); sbH.AppendLine(" bool loaded() const noexcept { return p != nullptr; }"); sbH.AppendLine(" template "); sbH.AppendLine(" auto operator()(Args&&... args) const"); sbH.AppendLine(" -> decltype(p(std::forward(args)...))"); sbH.AppendLine(" {"); sbH.AppendLine(" if (!p) throw std::runtime_error(std::string(\"Vulkan function not loaded: \") + (name ? name : \"\"));"); sbH.AppendLine(" return p(std::forward(args)...);"); sbH.AppendLine(" }"); sbH.AppendLine("};"); sbH.AppendLine(); sbH.AppendLine("struct VulkanFunctions {"); foreach (var cmd in EnabledCommands().OrderBy(c => c.Name)) { var guard = GuardExpr(cmd.ProtectMacros); if (guard.Length > 0) sbH.AppendLine("#if " + guard); sbH.AppendLine(" Fn " + cmd.Name + ";"); if (guard.Length > 0) sbH.AppendLine("#endif"); } sbH.AppendLine(); sbH.AppendLine(" void loadGlobal();"); sbH.AppendLine(" void loadInstance(VkInstance instance);"); sbH.AppendLine(" void loadDevice(VkDevice device);"); sbH.AppendLine("};"); sbH.AppendLine("extern VulkanFunctions vk;"); sbH.AppendLine(); sbH.AppendLine("} // namespace VulkanPP"); File.WriteAllText(Path.Combine(_outDir, "VulkanFunctions.hpp"), sbH.ToString()); var sbCpp = new StringBuilder(); sbCpp.AppendLine("#include "); sbCpp.AppendLine("#if defined(_WIN32)"); sbCpp.AppendLine("#include "); sbCpp.AppendLine("static HMODULE g_vkLib = LoadLibraryA(\"vulkan-1.dll\");"); sbCpp.AppendLine("static void* LoadSymbol(const char* n){ return (void*)GetProcAddress(g_vkLib, n); }"); sbCpp.AppendLine("#else"); sbCpp.AppendLine("#include "); sbCpp.AppendLine("static void* g_vkLib = dlopen(\"libvulkan.so.1\", RTLD_NOW | RTLD_LOCAL);"); sbCpp.AppendLine("static void* LoadSymbol(const char* n){ return dlsym(g_vkLib, n); }"); sbCpp.AppendLine("#endif"); sbCpp.AppendLine(); sbCpp.AppendLine("namespace VulkanPP {"); sbCpp.AppendLine(); sbCpp.AppendLine("VulkanFunctions vk;"); sbCpp.AppendLine(); sbCpp.AppendLine("void VulkanFunctions::loadGlobal(){"); sbCpp.AppendLine(" vkGetInstanceProcAddr.p = (PFN_vkGetInstanceProcAddr)LoadSymbol(\"vkGetInstanceProcAddr\");"); sbCpp.AppendLine(" vkGetInstanceProcAddr.name = \"vkGetInstanceProcAddr\";"); foreach (var cmd in EnabledCommands() .Where(c => _spec.ClassifyLoadLevel(c) == VkSpec.LoadLevel.Global) .OrderBy(c => c.Name)) { var guard = GuardExpr(cmd.ProtectMacros); if (guard.Length > 0) sbCpp.AppendLine("#if " + guard); sbCpp.AppendLine(" " + cmd.Name + ".p = (PFN_" + cmd.Name + ")vkGetInstanceProcAddr.p(nullptr, \"" + cmd.Name + "\");"); sbCpp.AppendLine(" " + cmd.Name + ".name = \"" + cmd.Name + "\";"); if (guard.Length > 0) sbCpp.AppendLine("#endif"); } sbCpp.AppendLine("}"); sbCpp.AppendLine(); sbCpp.AppendLine("void VulkanFunctions::loadInstance(VkInstance instance){"); foreach (var cmd in EnabledCommands() .Where(c => _spec.ClassifyLoadLevel(c) == VkSpec.LoadLevel.Instance) .OrderBy(c => c.Name)) { var guard = GuardExpr(cmd.ProtectMacros); if (guard.Length > 0) sbCpp.AppendLine("#if " + guard); sbCpp.AppendLine(" " + cmd.Name + ".p = (PFN_" + cmd.Name + ")vkGetInstanceProcAddr.p(instance, \"" + cmd.Name + "\");"); sbCpp.AppendLine(" " + cmd.Name + ".name = \"" + cmd.Name + "\";"); if (guard.Length > 0) sbCpp.AppendLine("#endif"); } sbCpp.AppendLine("}"); sbCpp.AppendLine(); sbCpp.AppendLine("void VulkanFunctions::loadDevice(VkDevice device){"); sbCpp.AppendLine(" vkGetDeviceProcAddr.p = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr.p(nullptr, \"vkGetDeviceProcAddr\");"); sbCpp.AppendLine(" vkGetDeviceProcAddr.name = \"vkGetDeviceProcAddr\";"); foreach (var cmd in EnabledCommands() .Where(c => _spec.ClassifyLoadLevel(c) == VkSpec.LoadLevel.Device) .OrderBy(c => c.Name)) { var guard = GuardExpr(cmd.ProtectMacros); if (guard.Length > 0) sbCpp.AppendLine("#if " + guard); sbCpp.AppendLine(" " + cmd.Name + ".p = (PFN_" + cmd.Name + ")vkGetDeviceProcAddr.p(device, \"" + cmd.Name + "\");"); sbCpp.AppendLine(" " + cmd.Name + ".name = \"" + cmd.Name + "\";"); if (guard.Length > 0) sbCpp.AppendLine("#endif"); } sbCpp.AppendLine("}"); sbCpp.AppendLine(); sbCpp.AppendLine("} // namespace VulkanPP"); File.WriteAllText(Path.Combine(_outDir, "VulkanFunctions.cpp"), sbCpp.ToString()); } // ========================================= // 1) Skeletons (.hpp classes) + ctor/dtor declarations // ========================================= void GenerateHandleWrappersSkeletons() { var handlesToGenerate = new HashSet(_spec.Pairs.Select(p => p.HandleType)); foreach (var pr in _spec.Producers) handlesToGenerate.Add(pr.HandleType); foreach (var handleType in handlesToGenerate) { var className = ToClass(handleType); TouchHeader(className); VkSpec.Pair? pair = _spec.Pairs.FirstOrDefault(p => p.HandleType == handleType); var hasOwned = pair != null; var primaryOwners = new List(); if (hasOwned) { if (!string.IsNullOrEmpty(pair!.ParentType)) primaryOwners.Add(pair!.ParentType); primaryOwners.AddRange(pair!.ExtraOwnersForDestroy.Where(o => o != pair!.ParentType)); } else { var parents = _spec.Producers.Where(p => p.HandleType == handleType) .Select(p => p.ParentType) .Distinct().ToList(); if (parents.Count > 0) primaryOwners.AddRange(parents); } primaryOwners = primaryOwners.Distinct().ToList(); var lifetimeUses = new List(); if (hasOwned) { var createCmd = _spec.Commands[pair!.CreateOrAllocate]; lifetimeUses = _spec.AnalyzeCreateInfoOwners(createCmd, handleType) .Where(u => !primaryOwners.Contains(u.Type)) .ToList(); } // record forward-decls (owners + lifetime-kept types) foreach (var o in primaryOwners) AddHeaderForward(className, ToClass(o)); foreach (var u in lifetimeUses) AddHeaderForward(className, ToClass(u.Type)); // record cpp includes for destructor/ctor usage foreach (var o in primaryOwners) AddCppInclude(className, ToClass(o)); foreach (var u in lifetimeUses) AddCppInclude(className, ToClass(u.Type)); // Header class skeleton var header = _headers[className]; header.AppendLine("// === Lifetime summary (deterministic) ==="); header.AppendLine("// Handle: " + handleType); var holdsStr = primaryOwners.Count > 0 ? string.Join(", ", primaryOwners.Select(ToClass)) : "—"; header.AppendLine("// Holds (destroy/free owners + creation parent, or producer parent): " + holdsStr); var keepList = lifetimeUses.Select(u => ToClass(u.Type) + (u.IsMany ? "[]" : "") + " via " + u.SourceStruct + "::" + u.SourceMember); var keepsStr = keepList.Any() ? string.Join("; ", keepList) : "—"; header.AppendLine("// Keeps (from *CreateInfo/*AllocateInfo): " + keepsStr); header.AppendLine("// Destructor calls vkDestroy*/vkFree* only if _owned == true."); header.AppendLine(); header.AppendLine("class " + className + " : public RefCounted {"); header.AppendLine("public:"); header.AppendLine(" " + handleType + " getHandle() const noexcept { return _handle; }"); // owners getters var usedGetterNames = new HashSet(StringComparer.Ordinal); for (int i = 0; i < primaryOwners.Count; i++) { var oc = ToClass(primaryOwners[i]); var getter = "get" + oc; var unique = getter; int k = 2; while (!usedGetterNames.Add(unique)) unique = getter + k++; header.AppendLine(" IntrusivePtr<" + oc + "> " + unique + "() const noexcept { return _owner" + i + "; }"); } foreach (var u in lifetimeUses) { var oc = ToClass(u.Type); var baseName = "get" + oc + (u.IsMany ? "s" : ""); var unique = baseName; int k = 2; while (!usedGetterNames.Add(unique)) unique = baseName + k++; if (u.IsMany) header.AppendLine(" const std::vector>& " + unique + "() const noexcept { return _lifetime_owners_" + oc + "; }"); else header.AppendLine(" IntrusivePtr<" + oc + "> " + unique + "() const noexcept { return _lifetime_owner_" + oc + "; }"); } // fromHandle stays inline (safe) header.Append("public:\n static IntrusivePtr<" + className + "> fromHandle("); for (int i = 0; i < primaryOwners.Count; i++) { if (i > 0) header.Append(", "); header.Append("IntrusivePtr<" + ToClass(primaryOwners[i]) + "> owner" + i); } if (primaryOwners.Count > 0) header.Append(", "); header.Append(handleType + " handle"); header.AppendLine(") {"); header.Append(" return IntrusivePtr<" + className + ">(new " + className + "("); for (int i = 0; i < primaryOwners.Count; i++) { if (i > 0) header.Append(", "); header.Append("owner" + i); } if (primaryOwners.Count > 0) header.Append(", "); header.Append("handle, false"); foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); header.Append(", "); header.Append(u.IsMany ? "std::vector>()" : "IntrusivePtr<" + cls + ">()"); } header.AppendLine("));"); header.AppendLine(" }"); // Private ctor/dtor declarations header.AppendLine("private:"); header.Append(" explicit " + className + "("); for (int i = 0; i < primaryOwners.Count; i++) { if (i > 0) header.Append(", "); header.Append("IntrusivePtr<" + ToClass(primaryOwners[i]) + "> owner" + i); } if (primaryOwners.Count > 0) header.Append(", "); header.Append(handleType + " handle, bool owned"); foreach (var u in lifetimeUses) { header.Append(", "); var cls = ToClass(u.Type); header.Append(u.IsMany ? "const std::vector>& owners_" + cls : "IntrusivePtr<" + cls + "> owner_" + cls); } header.AppendLine(");"); header.AppendLine(" ~" + className + "() override;"); // fields header.AppendLine("private:"); header.AppendLine(" " + handleType + " _handle{VK_NULL_HANDLE};"); header.AppendLine(" bool _owned{false};"); for (int i = 0; i < primaryOwners.Count; i++) header.AppendLine(" IntrusivePtr<" + ToClass(primaryOwners[i]) + "> _owner" + i + ";"); foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); header.AppendLine(u.IsMany ? " std::vector> _lifetime_owners_" + cls + ";" : " IntrusivePtr<" + cls + "> _lifetime_owner_" + cls + ";"); } header.AppendLine("};"); header.AppendLine("using P" + className + " = IntrusivePtr<" + className + ">;"); header.AppendLine(); header.AppendLine("} // namespace VulkanPP"); // .cpp: ctor definition var src = _sources[className]; src.Append(" " + className + "::" + className + "("); for (int i = 0; i < primaryOwners.Count; i++) { if (i > 0) src.Append(", "); src.Append("IntrusivePtr<" + ToClass(primaryOwners[i]) + "> owner" + i); } if (primaryOwners.Count > 0) src.Append(", "); src.Append(handleType + " handle, bool owned"); foreach (var u in lifetimeUses) { src.Append(", "); var cls = ToClass(u.Type); src.Append(u.IsMany ? "const std::vector>& owners_" + cls : "IntrusivePtr<" + cls + "> owner_" + cls); } src.AppendLine(")"); src.AppendLine(" : _handle(handle), _owned(owned)"); for (int i = 0; i < primaryOwners.Count; i++) src.AppendLine(" , _owner" + i + "(std::move(owner" + i + "))"); foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); src.AppendLine(u.IsMany ? " , _lifetime_owners_" + cls + "(owners_" + cls + ")" : " , _lifetime_owner_" + cls + "(std::move(owner_" + cls + "))"); } src.AppendLine("{ }"); src.AppendLine(); // .cpp: dtor definition src.AppendLine(" " + className + "::~" + className + "() {"); src.AppendLine(" if (_handle && _owned) {"); if (hasOwned && !string.IsNullOrEmpty(pair!.DestroyOrFree) && _spec.Commands.TryGetValue(pair!.DestroyOrFree, out var destroyCmd)) { var guard = GuardExpr(destroyCmd.ProtectMacros); if (guard.Length > 0) src.AppendLine("#if " + guard); var args = new List(); int ownerIdx = 0; foreach (var p in destroyCmd.Params) { if (p.Type == handleType && p.IsPointer) { args.Add("1"); args.Add("&_handle"); continue; } if (p.Type == handleType && !p.IsPointer) { args.Add("_handle"); continue; } if (p.Name == "pAllocator") { args.Add("nullptr"); continue; } if (p.IsHandle) { args.Add($"_owner{ownerIdx} ? _owner{ownerIdx}->getHandle() : ({p.Type})VK_NULL_HANDLE"); ownerIdx++; continue; } if (p.Name.EndsWith("Count")) args.Add("0"); else args.Add("0"); } src.AppendLine(" (void)vk." + destroyCmd.Name + "(" + string.Join(", ", args) + ");"); src.AppendLine(" _handle = VK_NULL_HANDLE;"); if (guard.Length > 0) src.AppendLine("#endif"); } src.AppendLine(" }"); src.AppendLine("}"); src.AppendLine(); } } // ========================================= // 2) Factories: ctor / createMany (prototypes inside class) + bodies in .cpp // ========================================= void GenerateFactories() { foreach (var pair in _spec.Pairs) { var handleType = pair.HandleType; var className = ToClass(handleType); var src = _sources[className]; var primaryOwners = new List(); if (!string.IsNullOrEmpty(pair.ParentType)) primaryOwners.Add(pair.ParentType); primaryOwners.AddRange(pair.ExtraOwnersForDestroy.Where(o => o != pair.ParentType)); primaryOwners = primaryOwners.Distinct().ToList(); var createCmd = _spec.Commands[pair.CreateOrAllocate]; var lifetimeUses = _spec.AnalyzeCreateInfoOwners(createCmd, handleType) .Where(u => !primaryOwners.Contains(u.Type)) .ToList(); bool multiOut = pair.OutHandleParam.LenAttr != null; bool hasParent = !string.IsNullOrEmpty(pair.ParentType); var parentClass = hasParent ? ToClass(pair.ParentType) : ""; // signature pieces var ps = createCmd.Params.ToList(); var outLenName = pair.OutHandleParam.LenAttr; var lenNamesToSkip = new HashSet(); var sigParams = new List(); var prep = new StringBuilder(); var callArgsPrepared = new Queue(); if (hasParent) { sigParams.Add($"IntrusivePtr<{parentClass}> parent"); AddHeaderForward(className, parentClass); AddCppInclude(className, parentClass); } foreach (var p in ps) { if (hasParent && p == ps.First()) continue; if (p.Name == "pAllocator") continue; if (p.IsPointer && p.Type == pair.HandleType) continue; if (outLenName != null && p.Name == outLenName) continue; if (p.IsHandle) { var cls = ToClass(p.Type); AddHeaderForward(className, cls); AddCppInclude(className, cls); if (p.IsPointer && p.LenAttr != null) { sigParams.Add($"const std::vector>& {p.Name}"); var rawVec = "_raw_" + p.Name; prep.AppendLine($" std::vector<{p.Type}> {rawVec}; {rawVec}.reserve({p.Name}.size());"); prep.AppendLine($" for (auto const& it : {p.Name}) {rawVec}.push_back(it->getHandle());"); lenNamesToSkip.Add(p.LenAttr!); callArgsPrepared.Enqueue($"{p.Name}.size()"); callArgsPrepared.Enqueue($"{rawVec}.data()"); } else { sigParams.Add($"IntrusivePtr<{cls}> {p.Name}"); callArgsPrepared.Enqueue($"{p.Name}->getHandle()"); } } else { if (lenNamesToSkip.Contains(p.Name)) continue; sigParams.Add($"{(p.IsConst ? "const " : "")}{p.Type}{(p.IsPointer ? "*" : "")} {p.Name}"); callArgsPrepared.Enqueue(p.Name); } } foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); AddHeaderForward(className, cls); AddCppInclude(className, cls); sigParams.Add(u.IsMany ? $"const std::vector>& owners_{cls}" : $"IntrusivePtr<{cls}> owner_{cls}"); } string ResolveOwnerExpr(string ownerVkType) { if (ownerVkType == pair.ParentType && hasParent) return "parent"; var pFound = createCmd.Params.FirstOrDefault(pp => pp.IsHandle && pp.Type == ownerVkType && (!pp.IsPointer || pp.LenAttr == null) && (!hasParent || pp != createCmd.Params.First())); if (pFound != null) return pFound.Name; var lu = lifetimeUses.FirstOrDefault(x => x.Type == ownerVkType && !x.IsMany); if (lu != null) return "owner_" + ToClass(ownerVkType); return hasParent ? "parent" : $"({ToClass(ownerVkType)})nullptr"; } var guardCreate = GuardExpr(createCmd.ProtectMacros); if (!multiOut) { // ---------- DECLARE: constructor (inside class) ---------- var decl = new StringBuilder(); if (guardCreate.Length > 0) decl.AppendLine("#if " + guardCreate); decl.AppendLine("public:"); decl.AppendLine("// Original: " + createCmd.Name); decl.AppendLine(" explicit " + className + "(" + string.Join(", ", sigParams) + ");"); // ---------- DECLARE: static create(...) (inside class) ---------- decl.AppendLine("// Original: " + createCmd.Name + " (factory)"); decl.AppendLine(" static IntrusivePtr<" + className + "> create(" + string.Join(", ", sigParams) + ");"); if (guardCreate.Length > 0) decl.AppendLine("#endif"); decl.AppendLine(); AddToHeaderClass(className, decl.ToString()); // ---------- DEFINE: constructor in .cpp ---------- if (guardCreate.Length > 0) src.AppendLine("#if " + guardCreate); src.AppendLine("// Original: " + createCmd.Name); src.Append(className + "::" + className + "(" + string.Join(", ", sigParams) + ")"); src.AppendLine(" : _handle(VK_NULL_HANDLE), _owned(true)"); for (int i = 0; i < primaryOwners.Count; ++i) src.AppendLine(" , _owner" + i + "(" + ResolveOwnerExpr(primaryOwners[i]) + ")"); foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); src.AppendLine(u.IsMany ? " , _lifetime_owners_" + cls + "(owners_" + cls + ")" : " , _lifetime_owner_" + cls + "(owner_" + cls + ")"); } src.AppendLine("{"); src.Append(prep.ToString()); var outName = "_out"; src.AppendLine(" " + pair.HandleType + " " + outName + " = VK_NULL_HANDLE;"); var argsFinal = new List(); foreach (var p in createCmd.Params) { if (hasParent && p == createCmd.Params.First()) { argsFinal.Add("parent->getHandle()"); continue; } if (!hasParent && p == createCmd.Params.First() && !p.IsHandle) { /* queued above */ } if (p.Name == "pAllocator") { argsFinal.Add("nullptr"); continue; } if (p.IsPointer && p.Type == pair.HandleType) { argsFinal.Add("&" + outName); continue; } if (outLenName != null && p.Name == outLenName) { argsFinal.Add("0"); continue; } if (callArgsPrepared.Count > 0) argsFinal.Add(callArgsPrepared.Dequeue()); } src.AppendLine(" auto _res = vk." + createCmd.Name + "(" + string.Join(", ", argsFinal) + ");"); src.AppendLine(" if (_res != VK_SUCCESS) throw std::runtime_error(\"" + createCmd.Name + " failed\");"); src.AppendLine(" _handle = " + outName + ";"); src.AppendLine("}"); // ---------- DEFINE: static create(...) in .cpp ---------- // derive argument names from the signature we just built var argNames = sigParams .Select(p => { // works for: "IntrusivePtr x", "const VkFoo* p", "const std::vector<...>& arr", etc. var parts = p.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); return parts[^1]; }) .ToList(); src.AppendLine("// Original: " + createCmd.Name + " (factory)"); src.Append("IntrusivePtr<" + className + "> " + className + "::create(" + string.Join(", ", sigParams) + ")"); src.AppendLine(" {"); src.Append(" return IntrusivePtr<" + className + ">(new " + className + "(" + string.Join(", ", argNames) + "));"); src.AppendLine(); src.AppendLine("}"); if (guardCreate.Length > 0) src.AppendLine("#endif"); src.AppendLine(); } else { // declare static createMany **inside class** var decl = new StringBuilder(); if (guardCreate.Length > 0) decl.AppendLine("#if " + guardCreate); decl.AppendLine("public:"); decl.AppendLine("// Original: " + createCmd.Name); decl.AppendLine(" static std::vector> createMany(" + string.Join(", ", sigParams) + ");"); if (guardCreate.Length > 0) decl.AppendLine("#endif"); decl.AppendLine(); AddToHeaderClass(className, decl.ToString()); // define in .cpp if (guardCreate.Length > 0) src.AppendLine("#if " + guardCreate); src.AppendLine("// Original: " + createCmd.Name); src.Append("std::vector> " + className + "::createMany(" + string.Join(", ", sigParams) + ")"); src.AppendLine(" {"); src.Append(prep.ToString()); var outName = "_out"; var lenName = outLenName ?? "count"; src.AppendLine(" std::vector<" + pair.HandleType + "> " + outName + ";"); src.AppendLine(" " + outName + ".resize(" + lenName + ");"); var argsFinal = new List(); foreach (var p in createCmd.Params) { if (hasParent && p == createCmd.Params.First()) { argsFinal.Add("parent->getHandle()"); continue; } if (p.Name == "pAllocator") { argsFinal.Add("nullptr"); continue; } if (p.IsPointer && p.Type == pair.HandleType) { argsFinal.Add(outName + ".data()"); continue; } if (outLenName != null && p.Name == outLenName) { argsFinal.Add(lenName); continue; } if (callArgsPrepared.Count > 0) argsFinal.Add(callArgsPrepared.Dequeue()); } src.AppendLine(" auto _res = vk." + createCmd.Name + "(" + string.Join(", ", argsFinal) + ");"); src.AppendLine(" if (_res != VK_SUCCESS) throw std::runtime_error(\"" + createCmd.Name + " failed\");"); src.AppendLine(" std::vector> _ret; _ret.reserve(" + outName + ".size());"); src.AppendLine(" for (auto h : " + outName + ") {"); var ctorOwners = new List(); for (int i = 0; i < primaryOwners.Count; ++i) ctorOwners.Add(ResolveOwnerExpr(primaryOwners[i])); src.Append(" _ret.emplace_back(new " + className + "(" + string.Join(", ", ctorOwners)); if (primaryOwners.Count > 0) src.Append(", "); src.Append("h, true"); foreach (var u in lifetimeUses) { var cls = ToClass(u.Type); src.Append(", "); src.Append(u.IsMany ? "owners_" + cls : "owner_" + cls); } src.AppendLine("));"); src.AppendLine(" }"); src.AppendLine(" return _ret;"); src.AppendLine("}"); if (guardCreate.Length > 0) src.AppendLine("#endif"); src.AppendLine(); } } } // ========================================= // 3) Producer methods (Get*/Enumerate*) // ========================================= void GenerateProducerMemberMethods() { foreach (var pr in _spec.Producers) { var parentClass = ToClass(pr.ParentType); var childClass = ToClass(pr.HandleType); TouchHeader(parentClass); var cmd = _spec.Commands[pr.CommandName]; var guard = GuardExpr(cmd.ProtectMacros); AddHeaderForward(parentClass, childClass); AddCppInclude(parentClass, childClass); foreach (var p in cmd.Params.Skip(1)) if (p.IsHandle) { AddHeaderForward(parentClass, ToClass(p.Type)); AddCppInclude(parentClass, ToClass(p.Type)); } var outParam = pr.OutHandleParam; var lenName = outParam.LenAttr; var sigParams = new List(); foreach (var p in cmd.Params.Skip(1)) { if (p.Name == outParam.Name) continue; if (!string.IsNullOrEmpty(lenName) && p.Name == lenName) continue; if (p.IsHandle) sigParams.Add("IntrusivePtr<" + ToClass(p.Type) + "> " + p.Name); else sigParams.Add((p.IsConst ? "const " : "") + p.Type + (p.IsPointer ? "*" : "") + " " + p.Name); } var retWrapper = pr.IsMany ? "std::vector>" : "IntrusivePtr<" + childClass + ">"; var methodName = "get" + childClass + (pr.IsMany ? "s" : ""); // header prototype **inside class** var decl = new StringBuilder(); if (guard.Length > 0) decl.AppendLine("#if " + guard); decl.AppendLine("public:"); decl.AppendLine("// Original: " + cmd.Name); decl.AppendLine(" " + retWrapper + " " + methodName + "(" + string.Join(", ", sigParams) + ");"); if (guard.Length > 0) decl.AppendLine("#endif"); decl.AppendLine(); AddToHeaderClass(parentClass, decl.ToString()); // Source definition var src = _sources[parentClass]; if (guard.Length > 0) src.AppendLine("#if " + guard); src.AppendLine("// Original: " + cmd.Name); src.Append(retWrapper + " " + parentClass + "::" + methodName + "(" + string.Join(", ", sigParams) + ")"); src.AppendLine(" {"); if (pr.IsMany) { var countParam = cmd.Params.First(p => p.Name == lenName); src.AppendLine(" " + countParam.Type + " _count = 0;"); var firstArgs = new List { "_handle" }; foreach (var p in cmd.Params.Skip(1)) { if (p.Name == lenName) firstArgs.Add("&_count"); else if (p.Name == outParam.Name) firstArgs.Add("nullptr"); else if (p.IsHandle) firstArgs.Add(p.Name + "->getHandle()"); else firstArgs.Add(p.Name); } var call1 = "vk." + cmd.Name + "(" + string.Join(", ", firstArgs) + ")"; if (cmd.ReturnType.Trim() == "VkResult") { src.AppendLine(" auto _r1 = " + call1 + ";"); src.AppendLine(" if (_r1 != VK_SUCCESS && _r1 != VK_INCOMPLETE) throw std::runtime_error(\"" + cmd.Name + " (count) failed\");"); } else src.AppendLine(" " + call1 + ";"); src.AppendLine(" std::vector<" + pr.HandleType + "> _out; _out.resize(_count);"); var secondArgs = new List { "_handle" }; foreach (var p in cmd.Params.Skip(1)) { if (p.Name == lenName) secondArgs.Add("&_count"); else if (p.Name == outParam.Name) secondArgs.Add("_out.data()"); else if (p.IsHandle) secondArgs.Add(p.Name + "->getHandle()"); else secondArgs.Add(p.Name); } var call2 = "vk." + cmd.Name + "(" + string.Join(", ", secondArgs) + ")"; if (cmd.ReturnType.Trim() == "VkResult") { src.AppendLine(" auto _r2 = " + call2 + ";"); src.AppendLine(" if (_r2 != VK_SUCCESS) throw std::runtime_error(\"" + cmd.Name + " (fill) failed\");"); } else src.AppendLine(" " + call2 + ";"); src.AppendLine(" std::vector> _ret; _ret.reserve(_out.size());"); src.AppendLine(" IntrusivePtr<" + parentClass + "> _self(this);"); src.AppendLine(" for (auto h : _out) { _ret.emplace_back(new " + childClass + "(_self, h, false)); }"); src.AppendLine(" return _ret;"); } else { src.AppendLine(" " + pr.HandleType + " _h = VK_NULL_HANDLE;"); var args = new List { "_handle" }; foreach (var p in cmd.Params.Skip(1)) { if (p.Name == outParam.Name) args.Add("&_h"); else if (p.IsHandle) args.Add(p.Name + "->getHandle()"); else args.Add(p.Name); } var call = "vk." + cmd.Name + "(" + string.Join(", ", args) + ")"; if (cmd.ReturnType.Trim() == "VkResult") { src.AppendLine(" auto _r = " + call + ";"); src.AppendLine(" if (_r != VK_SUCCESS) throw std::runtime_error(\"" + cmd.Name + " failed\");"); } else src.AppendLine(" " + call + ";"); src.AppendLine(" return IntrusivePtr<" + childClass + ">(new " + childClass + "(IntrusivePtr<" + parentClass + ">(this), _h, false));"); } src.AppendLine("}"); if (guard.Length > 0) src.AppendLine("#endif"); src.AppendLine(); } } // ========================================= // 4) Non-producer member methods // ========================================= void GenerateMemberMethods() { foreach (var cmd in EnabledCommands()) { if (cmd.Name.StartsWith("vkCreate") || cmd.Name.StartsWith("vkDestroy") || cmd.Name.StartsWith("vkAllocate") || cmd.Name.StartsWith("vkFree")) continue; bool isProducerOutHandle = (cmd.Name.StartsWith("vkGet") || cmd.Name.StartsWith("vkEnumerate")) && cmd.Params.Any(p => p.IsPointer && _spec.Handles.ContainsKey(p.Type)); if (isProducerOutHandle) continue; if (cmd.Params.Count == 0) continue; var first = cmd.Params[0]; if (!_spec.Handles.ContainsKey(first.Type)) continue; var className = ToClass(first.Type); TouchHeader(className); var guard = GuardExpr(cmd.ProtectMacros); var methodName = MakeMethodName(className, cmd.Name); var retType = string.IsNullOrWhiteSpace(cmd.ReturnType) ? "void" : cmd.ReturnType; var ps = cmd.Params.Skip(1).ToList(); var sigParams = new List(); var prep = new StringBuilder(); var callArgs = new List(); var lenNamesToSkip = new HashSet(); foreach (var p in ps) { if (p.IsHandle) { var cls = ToClass(p.Type); AddHeaderForward(className, cls); AddCppInclude(className, cls); if (p.IsPointer && p.LenAttr != null) { sigParams.Add("const std::vector>& " + p.Name); var raw = "_raw_" + p.Name; prep.AppendLine(" std::vector<" + p.Type + "> " + raw + "; " + raw + ".reserve(" + p.Name + ".size());"); prep.AppendLine(" for (auto const& it : " + p.Name + ") " + raw + ".push_back(it->getHandle());"); lenNamesToSkip.Add(p.LenAttr); callArgs.Add(p.Name + ".size()"); callArgs.Add(raw + ".data()"); } else { sigParams.Add("IntrusivePtr<" + cls + "> " + p.Name); callArgs.Add(p.Name + "->getHandle()"); } } else { if (lenNamesToSkip.Contains(p.Name)) continue; sigParams.Add((p.IsConst ? "const " : "") + p.Type + (p.IsPointer ? "*" : "") + " " + p.Name); callArgs.Add(p.Name); } } // header prototype inside class var decl = new StringBuilder(); if (guard.Length > 0) decl.AppendLine("#if " + guard); decl.AppendLine("public:"); decl.AppendLine("// Original: " + cmd.Name); decl.AppendLine(" " + retType + " " + methodName + "(" + string.Join(", ", sigParams) + ");"); if (guard.Length > 0) decl.AppendLine("#endif"); decl.AppendLine(); AddToHeaderClass(className, decl.ToString()); // source definition var src = _sources[className]; if (guard.Length > 0) src.AppendLine("#if " + guard); src.AppendLine("// Original: " + cmd.Name); src.Append(retType + " " + className + "::" + methodName + "(" + string.Join(", ", sigParams) + ")"); src.AppendLine(" {"); src.Append(prep.ToString()); src.Append(" "); if (retType != "void") src.Append("return "); var call = "vk." + cmd.Name + "(_handle"; if (callArgs.Count > 0) call += ", " + string.Join(", ", callArgs); call += ");"; src.AppendLine(call); src.AppendLine("}"); if (guard.Length > 0) src.AppendLine("#endif"); src.AppendLine(); } } // ========================================= // 5) Sugar methods (prototypes inside class + bodies) // ========================================= void GenerateSugarMethods() { foreach (var kv in _sugar) { var className = kv.Key; if (!_headers.ContainsKey(className)) TouchHeader(className); var spec = kv.Value; foreach (var m in spec.Methods) { if (!m.RequiresFunctions.All(fn => _spec.Commands.ContainsKey(fn))) continue; if (!m.RequiresExtensions.All(ext => _spec.Extensions.ContainsKey(ext))) continue; foreach (var p in m.Parameters) { var m1 = Regex.Match(p, @"IntrusivePtr<\s*(\w+)\s*>"); if (m1.Success) { AddHeaderForward(className, m1.Groups[1].Value); AddCppInclude(className, m1.Groups[1].Value); } var m2 = Regex.Match(p, @"std::vector\s*<\s*IntrusivePtr<\s*(\w+)\s*>\s*>"); if (m2.Success) { AddHeaderForward(className, m2.Groups[1].Value); AddCppInclude(className, m2.Groups[1].Value); } } var (sig, prelude, vars, guard) = BuildSugarSignatureAndContext(m, className); var decl = new StringBuilder(); if (!string.IsNullOrEmpty(guard)) decl.AppendLine("#if " + guard); decl.AppendLine("public:"); decl.AppendLine("// Original: (sugar)"); decl.AppendLine(" " + sig + ";"); if (!string.IsNullOrEmpty(guard)) decl.AppendLine("#endif"); decl.AppendLine(); AddToHeaderClass(className, decl.ToString()); var src = _sources[className]; if (!string.IsNullOrEmpty(guard)) src.AppendLine("#if " + guard); src.AppendLine("// Original: (sugar)"); var nameOnly = sig.Substring(sig.IndexOf(' ') + 1); var idxParen = nameOnly.IndexOf('('); var methodName = nameOnly.Substring(0, idxParen); var argsIn = nameOnly.Substring(idxParen); src.Append(m.ReturnType + " " + className + "::" + methodName + argsIn); src.AppendLine(" {"); if (!string.IsNullOrEmpty(prelude)) foreach (var line in prelude.Split('\n')) if (line.Length > 0) src.AppendLine(" " + line); foreach (var ln in m.BodyLines) src.AppendLine(" " + Render(ln, vars)); src.AppendLine("}"); if (!string.IsNullOrEmpty(guard)) src.AppendLine("#endif"); src.AppendLine(); } } (string sig, string prelude, Dictionary vars, string guard) BuildSugarSignatureAndContext(SugarMethodSpec m, string className) { var vars = new Dictionary { ["class"] = className, ["handle"] = "_handle", ["vk"] = "vk" }; var prelude = new StringBuilder(); var paramList = string.Join(", ", m.Parameters); var rxScalar = new Regex(@"\bIntrusivePtr<\s*(\w+)\s*>\s+(\w+)\b"); foreach (Match mt in rxScalar.Matches(paramList)) { var varName = mt.Groups[2].Value; vars[varName + ".handle"] = varName + "->getHandle()"; } var rxVector = new Regex(@"\bstd::vector\s*<\s*IntrusivePtr<\s*(\w+)\s*>\s*>\s*&?\s*(\w+)\b"); foreach (Match mt in rxVector.Matches(paramList)) { var elemClass = mt.Groups[1].Value; var varName = mt.Groups[2].Value; var vkType = "Vk" + elemClass; var rawName = "_raw_" + varName; prelude.AppendLine("std::vector<" + vkType + "> " + rawName + "; " + rawName + ".reserve(" + varName + ".size());"); prelude.AppendLine("for (auto const& it : " + varName + ") " + rawName + ".push_back(it->getHandle());"); vars[varName + ".handles"] = rawName + ".data()"; vars[varName + ".size"] = varName + ".size()"; vars[varName + ".count"] = varName + ".size()"; } var quals = new StringBuilder(); if (m.IsConst) quals.Append(" const"); if (m.IsNoexcept) quals.Append(" noexcept"); var sig = (m.IsStatic ? "static " : "") + m.ReturnType + " " + m.Name + "(" + string.Join(", ", m.Parameters) + ")" + quals.ToString(); string guard = ""; var parts = new List(); if (!string.IsNullOrWhiteSpace(m.Guard)) parts.Add(m.Guard!); if (m.RequiresExtensions.Count > 0) parts.Add(string.Join(" && ", m.RequiresExtensions.Select(e => "defined(" + e + ")"))); if (parts.Count > 0) guard = string.Join(" && ", parts); return (sig, prelude.ToString(), vars, guard); } static string Render(string tpl, Dictionary vars) { foreach (var kv in vars) tpl = tpl.Replace("${" + kv.Key + "}", kv.Value); return tpl; } } // ========================================= // 6) Finalize: add forwards/includes, close namespaces, write files // ========================================= void FinalizeHeadersAndSources() { foreach (var (className, h) in _headers.ToArray()) { var text = h.ToString(); if (_headerForwards.TryGetValue(className, out var fwds) && fwds.Count > 0) { var block = new StringBuilder(); foreach (var n in fwds.OrderBy(n => n)) block.AppendLine("class " + n + ";"); int posNs = text.IndexOf("namespace VulkanPP {", StringComparison.Ordinal); if (posNs >= 0) { int insertPos = posNs + "namespace VulkanPP {".Length; text = text.Insert(insertPos, "\n" + block.ToString() + "\n"); } else { text = block.ToString() + "\n" + text; } } if (!text.EndsWith("\n")) text += "\n"; File.WriteAllText(Path.Combine(_outDir, className + ".hpp"), text); } foreach (var (className, s) in _sources.ToArray()) { var includes = new StringBuilder(); if (_cppIncludes.TryGetValue(className, out var incs)) { foreach (var n in incs.OrderBy(n => n)) includes.AppendLine("#include "); includes.AppendLine(); } var text = s.ToString(); int firstNL = text.IndexOf('\n'); if (firstNL >= 0) text = text.Insert(firstNL + 1, includes.ToString()); if (!text.EndsWith("\n} // namespace VulkanPP\n")) { if (!text.EndsWith("\n")) text += "\n"; text += "} // namespace VulkanPP\n"; } File.WriteAllText(Path.Combine(_outDir, className + ".cpp"), text); } } }