vulkaninfo: new vkconfig_output backdend
authorCharles Giessen <charles@lunarg.com>
Thu, 17 Oct 2019 22:21:45 +0000 (16:21 -0600)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Wed, 12 Feb 2020 23:00:18 +0000 (16:00 -0700)
This commit adds a new backend, vkconfig_output, that prints the same information
as the text & html versions in a json format. This output will only be used in
vkconfig. Therefore it does not have a documented flag, but it is accesible with
'--vkconfig_output'.

Many parts of the code needed refactoring to handle the vkconfig_output output.
Replacing PrintElement with PrintString, requiring StartObject instead of
StartArray, and removing many branches to make the code simpler to read.

vkconfig_output does not nest json objects within json arrays, thus keeping every
non-leaf node of a json object named.

files modified
 - scripts/vulkaninfo_generator.py
 - vulkaninfo/generated/vulkaninfo.hpp
 - vulkaninfo/outputprinter.h
 - vulkaninfo/vulkaninfo.cpp
 - vulkaninfo/vulkaninfo.h

Change-Id: I33d036a75b65942db1ad62b2a1e0c341a2b5e36c

scripts/vulkaninfo_generator.py
vulkaninfo/generated/vulkaninfo.hpp
vulkaninfo/outputprinter.h
vulkaninfo/vulkaninfo.cpp
vulkaninfo/vulkaninfo.h

index 6f2d1d9..2d15526 100644 (file)
@@ -25,6 +25,7 @@ import string
 import xml.etree.ElementTree as etree
 import generator as gen
 import operator
+import json
 from collections import namedtuple
 from collections import OrderedDict
 from generator import *
@@ -57,10 +58,10 @@ license_header = '''
  */
 '''
 
-custom_formaters = '''
-std::ostream &operator<<(std::ostream &o, VkConformanceVersion &c) {
-    return o << std::to_string(c.major) << "." << std::to_string(c.minor) << "." << std::to_string(c.subminor) << "."
-             << std::to_string(c.patch);
+custom_formaters = r'''
+void DumpVkConformanceVersion(Printer &p, std::string name, VkConformanceVersion &c, int width = 0) {
+    p.PrintKeyString("conformanceVersion", std::to_string(c.major)+ "." + std::to_string(c.minor) + "." + std::to_string(c.subminor) + "."
+             + std::to_string(c.patch), width);
 }
 
 template <typename T>
@@ -74,17 +75,20 @@ template <typename T>
 std::string to_hex_str(Printer &p, T i) {
     if (p.Type() == OutputType::json)
         return std::to_string(i);
+    else if (p.Type() == OutputType::vkconfig_output)
+        return std::string("\"") + to_hex_str(i) + std::string("\"");
     else
         return to_hex_str(i);
 }
 '''
 
+
 # used in the .cpp code
 structures_to_gen = ['VkExtent3D', 'VkExtent2D', 'VkPhysicalDeviceLimits', 'VkPhysicalDeviceFeatures', 'VkPhysicalDeviceSparseProperties',
                      'VkSurfaceCapabilitiesKHR', 'VkSurfaceFormatKHR', 'VkLayerProperties', 'VkPhysicalDeviceToolPropertiesEXT']
 enums_to_gen = ['VkResult', 'VkFormat', 'VkPresentModeKHR',
                 'VkPhysicalDeviceType', 'VkImageTiling']
-flags_to_gen = ['VkSurfaceTransformFlagsKHR', 'VkCompositeAlphaFlagsKHR',
+flags_to_gen = ['VkSurfaceTransformFlagsKHR', 'VkCompositeAlphaFlagsKHR', 'VkSurfaceCounterFlagsEXT',
                 'VkDeviceGroupPresentModeFlagsKHR', 'VkFormatFeatureFlags', 'VkMemoryPropertyFlags', 'VkMemoryHeapFlags']
 flags_strings_to_gen = ['VkQueueFlags']
 
@@ -92,10 +96,12 @@ struct_short_versions_to_gen = ['VkExtent3D']
 
 struct_comparisons_to_gen = ['VkSurfaceFormatKHR', 'VkSurfaceFormat2KHR', 'VkSurfaceCapabilitiesKHR',
                              'VkSurfaceCapabilities2KHR', 'VkSurfaceCapabilities2EXT']
+# don't generate these structures
+struct_blacklist = ['VkConformanceVersion']
 
 # iostream or custom outputter handles these types
 predefined_types = ['char', 'VkBool32', 'uint32_t', 'uint8_t', 'int32_t',
-                    'float', 'uint64_t', 'size_t', 'VkDeviceSize', 'VkConformanceVersion']
+                    'float', 'uint64_t', 'size_t', 'VkDeviceSize']
 
 # Types that need pNext Chains built. 'extends' is the xml tag used in the structextends member. 'type' can be device, instance, or both
 EXTENSION_CATEGORIES = {'phys_device_props2': {'extends': 'VkPhysicalDeviceProperties2', 'type': 'both'},
@@ -257,14 +263,13 @@ class VulkanInfoGenerator(OutputGenerator):
         for flag in self.flags:
             if flag.name in types_to_gen:
                 for bitmask in (b for b in self.bitmasks if b.name == flag.enum):
-                    out += PrintFlags(flag, bitmask, self)
                     out += PrintBitMask(bitmask, flag.name, self)
 
             if flag.name in flags_strings_to_gen:
                 for bitmask in (b for b in self.bitmasks if b.name == flag.enum):
                     out += PrintBitMaskToString(bitmask, flag.name, self)
 
-        for s in (x for x in self.all_structures if x.name in types_to_gen):
+        for s in (x for x in self.all_structures if x.name in types_to_gen and x.name not in struct_blacklist):
             out += PrintStructure(s, types_to_gen, names_of_structures_to_gen)
 
         out += "pNextChainInfos get_chain_infos() {\n"
@@ -401,59 +406,70 @@ def PrintEnum(enum, gen):
         enum.name + " value, int width = 0) {\n"
     out += "    if (p.Type() == OutputType::json) {\n"
     out += "        p.PrintKeyValue(name, value, width);\n"
-    out += "        return;\n"
     out += "    } else {\n"
-    out += "        p.PrintKeyValue(name, " + \
+    out += "        p.PrintKeyString(name, " + \
         enum.name + "String(value), width);\n    }\n"
     out += "}\n"
     out += AddGuardFooter(GetExtension(enum.name, gen))
     return out
 
 
-def PrintFlags(flag, bitmask, gen):
+def PrintGetFlagStrings(name, bitmask):
     out = ''
-    out += AddGuardHeader(GetExtension(flag.name, gen))
+    out += "std::vector<const char *>" + name + \
+        "GetStrings(" + name + " value) {\n"
 
-    out += "void Dump" + flag.name + \
-        "(Printer &p, std::string name, " + \
-        flag.enum + " value, int width = 0) {\n"
-    out += "    if (value == 0) p.PrintElement(\"None\");\n"
+    out += "    std::vector<const char *> strings;\n"
+    out += "    if (value == 0) strings.push_back(\"None\");\n"
     for v in bitmask.options:
-        out += "    if (" + str(v.value) + \
-            " & value) p.SetAsType().PrintElement(\"" + \
+        val = v.value if isinstance(v.value, str) else str(hex(v.value))
+        out += "    if (" + val + " & value) strings.push_back(\"" + \
             str(v.name[3:]) + "\");\n"
-    out += "}\n"
-
-    out += AddGuardFooter(GetExtension(flag.name, gen))
+    out += "    return strings;\n}\n"
     return out
 
 
-def PrintBitMask(bitmask, name, gen):
-    out = ''
-    out += AddGuardHeader(GetExtension(bitmask.name, gen))
-    out += "void Dump" + name + \
+def PrintFlags(bitmask, name):
+    out = "void Dump" + name + \
         "(Printer &p, std::string name, " + name + " value, int width = 0) {\n"
     out += "    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }\n"
-    out += "    p.ObjectStart(name);\n"
-    out += "    Dump" + name + \
-        "(p, name, static_cast<" + bitmask.name + ">(value), width);\n"
-    out += "    p.ObjectEnd();\n"
+    out += "    auto strings = " + bitmask.name + \
+        "GetStrings(static_cast<" + bitmask.name + ">(value));\n"
+    out += "    if (static_cast<" + bitmask.name + ">(value) == 0) {\n"
+    out += "        ArrayWrapper arr(p, name, 0);\n"
+    out += "        p.SetAsType().PrintString(\"None\");\n"
+    out += "        return;\n"
+    out += "    }\n"
+    out += "    ArrayWrapper arr(p, name, strings.size());\n"
+    out += "    for(auto& str : strings){\n"
+    out += "        p.SetAsType().PrintString(str);\n"
+    out += "    }\n"
     out += "}\n"
-    out += "void Dump" + bitmask.name + \
+    return out
+
+
+def PrintFlagBits(bitmask):
+    out = "void Dump" + bitmask.name + \
         "(Printer &p, std::string name, " + \
         bitmask.name + " value, int width = 0) {\n"
-    out += "    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }\n"
-    out += "    p.ObjectStart(name);\n"
-    out += "    Dump" + name + "(p, name, value, width);\n"
-    out += "    p.ObjectEnd();\n"
+    out += "    auto strings = " + bitmask.name + "GetStrings(value);\n"
+    out += "    p.PrintKeyString(name, strings.at(0), width);\n"
     out += "}\n"
+    return out
+
+
+def PrintBitMask(bitmask, name, gen):
+    out = PrintGetFlagStrings(bitmask.name, bitmask)
+    out += AddGuardHeader(GetExtension(bitmask.name, gen))
+    out += PrintFlags(bitmask, name)
+    out += PrintFlagBits(bitmask)
     out += AddGuardFooter(GetExtension(bitmask.name, gen))
+    out += "\n"
     return out
 
 
 def PrintBitMaskToString(bitmask, name, gen):
-    out = ''
-    out += AddGuardHeader(GetExtension(bitmask.name, gen))
+    out = AddGuardHeader(GetExtension(bitmask.name, gen))
     out += "std::string " + name + \
         "String(" + name + " value, int width = 0) {\n"
     out += "    std::string out;\n"
@@ -480,7 +496,7 @@ def PrintStructure(struct, types_to_gen, structure_names):
         if v.arrayLength is not None:
             if len(v.name) + len(v.arrayLength) + 2 > max_key_len:
                 max_key_len = len(v.name) + len(v.arrayLength) + 2
-        elif v.typeID in predefined_types:
+        elif v.typeID in predefined_types or v.typeID in struct_blacklist:
             if len(v.name) > max_key_len:
                 max_key_len = len(v.name)
 
@@ -497,7 +513,7 @@ def PrintStructure(struct, types_to_gen, structure_names):
         out += "    else\n"
         out += "        p.SetSubHeader().ObjectStart(name);\n"
     else:
-        out += "    p.ObjectStart(name);\n"
+        out += "    ObjectWrapper object{p, name};\n"
 
     for v in struct.members:
         # arrays
@@ -515,14 +531,14 @@ def PrintStructure(struct, types_to_gen, structure_names):
                 out += " p.PrintKeyString(\"" + v.name + "\", to_string_8(obj." + \
                     v.name + "), " + str(max_key_len) + ");\n"
             elif v.arrayLength.isdigit():
-                out += "    p.ArrayStart(\"" + v.name + \
+                out += "    {   ArrayWrapper arr(p,\"" + v.name + \
                     "\", "+v.arrayLength+");\n"
                 for i in range(0, int(v.arrayLength)):
-                    out += "    p.PrintElement(obj." + \
+                    out += "        p.PrintElement(obj." + \
                         v.name + "[" + str(i) + "]);\n"
-                out += "    p.ArrayEnd();\n"
+                out += "    }\n"
             else:  # dynamic array length based on other member
-                out += "    p.ArrayStart(\"" + v.name + \
+                out += "    ArrayWrapper arr(p,\"" + v.name + \
                     "\", obj."+v.arrayLength+");\n"
                 out += "    for (uint32_t i = 0; i < obj." + \
                     v.arrayLength+"; i++) {\n"
@@ -534,10 +550,13 @@ def PrintStructure(struct, types_to_gen, structure_names):
                     out += "        }\n"
                 else:
                     out += "        p.PrintElement(obj." + v.name + "[i]);\n"
-                out += "    }\n    p.ArrayEnd();\n"
+                out += "    }\n"
         elif v.typeID == "VkBool32":
             out += "    p.PrintKeyBool(\"" + v.name + "\", static_cast<bool>(obj." + \
                 v.name + "), " + str(max_key_len) + ");\n"
+        elif v.typeID == "VkConformanceVersion":
+            out += "    DumpVkConformanceVersion(p, \"conformanceVersion\", obj." + \
+                v.name + ", " + str(max_key_len) + ");\n"
         elif v.typeID == "VkDeviceSize":
             out += "    p.PrintKeyValue(\"" + v.name + "\", to_hex_str(p, obj." + \
                 v.name + "), " + str(max_key_len) + ");\n"
@@ -553,8 +572,8 @@ def PrintStructure(struct, types_to_gen, structure_names):
             else:
                 out += "    Dump" + v.typeID + \
                     "(p, \"" + v.name + "\", obj." + v.name + ");\n"
-
-    out += "    p.ObjectEnd();\n"
+    if struct.name in ["VkPhysicalDeviceLimits", "VkPhysicalDeviceSparseProperties"]:
+        out += "    p.ObjectEnd();\n"
     out += "}\n"
 
     out += AddGuardFooter(struct)
@@ -691,28 +710,12 @@ def PrintStructComparison(structure):
     return out
 
 
-def isPow2(num):
-    return num != 0 and ((num & (num - 1)) == 0)
-
-
-def StrToInt(s):
-    try:
-        return int(s)
-    except ValueError:
-        return int(s, 16)
-
-
 class VulkanEnum:
     class Option:
 
         def __init__(self, name, value, bitpos, comment):
             self.name = name
             self.comment = comment
-            self.multiValue = None
-
-            if value is not None:
-
-                self.multiValue = not isPow2(StrToInt(value))
 
             if value == 0 or value is None:
                 value = 1 << int(bitpos)
@@ -724,7 +727,6 @@ class VulkanEnum:
                 'optName': self.name,
                 'optValue': self.value,
                 'optComment': self.comment,
-                'optMultiValue': self.multiValue,
             }
 
     def __init__(self, rootNode):
index b242f02..2dc25b9 100644 (file)
@@ -27,9 +27,9 @@
 #include "vulkaninfo.h"
 #include "outputprinter.h"
 
-std::ostream &operator<<(std::ostream &o, VkConformanceVersion &c) {
-    return o << std::to_string(c.major) << "." << std::to_string(c.minor) << "." << std::to_string(c.subminor) << "."
-             << std::to_string(c.patch);
+void DumpVkConformanceVersion(Printer &p, std::string name, VkConformanceVersion &c, int width = 0) {
+    p.PrintKeyString("conformanceVersion", std::to_string(c.major)+ "." + std::to_string(c.minor) + "." + std::to_string(c.subminor) + "."
+             + std::to_string(c.patch), width);
 }
 
 template <typename T>
@@ -43,6 +43,8 @@ template <typename T>
 std::string to_hex_str(Printer &p, T i) {
     if (p.Type() == OutputType::json)
         return std::to_string(i);
+    else if (p.Type() == OutputType::vkconfig_output)
+        return std::string("\"") + to_hex_str(i) + std::string("\"");
     else
         return to_hex_str(i);
 }
@@ -87,9 +89,8 @@ static const char *VkResultString(VkResult value) {
 void DumpVkResult(Printer &p, std::string name, VkResult value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkResultString(value), width);
+        p.PrintKeyString(name, VkResultString(value), width);
     }
 }
 static const char *VkFormatString(VkFormat value) {
@@ -341,9 +342,8 @@ static const char *VkFormatString(VkFormat value) {
 void DumpVkFormat(Printer &p, std::string name, VkFormat value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkFormatString(value), width);
+        p.PrintKeyString(name, VkFormatString(value), width);
     }
 }
 static const char *VkImageTilingString(VkImageTiling value) {
@@ -357,9 +357,8 @@ static const char *VkImageTilingString(VkImageTiling value) {
 void DumpVkImageTiling(Printer &p, std::string name, VkImageTiling value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkImageTilingString(value), width);
+        p.PrintKeyString(name, VkImageTilingString(value), width);
     }
 }
 static const char *VkPhysicalDeviceTypeString(VkPhysicalDeviceType value) {
@@ -375,9 +374,8 @@ static const char *VkPhysicalDeviceTypeString(VkPhysicalDeviceType value) {
 void DumpVkPhysicalDeviceType(Printer &p, std::string name, VkPhysicalDeviceType value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkPhysicalDeviceTypeString(value), width);
+        p.PrintKeyString(name, VkPhysicalDeviceTypeString(value), width);
     }
 }
 static const char *VkPointClippingBehaviorString(VkPointClippingBehavior value) {
@@ -390,9 +388,8 @@ static const char *VkPointClippingBehaviorString(VkPointClippingBehavior value)
 void DumpVkPointClippingBehavior(Printer &p, std::string name, VkPointClippingBehavior value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkPointClippingBehaviorString(value), width);
+        p.PrintKeyString(name, VkPointClippingBehaviorString(value), width);
     }
 }
 static const char *VkDriverIdString(VkDriverId value) {
@@ -415,9 +412,8 @@ static const char *VkDriverIdString(VkDriverId value) {
 void DumpVkDriverId(Printer &p, std::string name, VkDriverId value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkDriverIdString(value), width);
+        p.PrintKeyString(name, VkDriverIdString(value), width);
     }
 }
 static const char *VkShaderFloatControlsIndependenceString(VkShaderFloatControlsIndependence value) {
@@ -431,9 +427,8 @@ static const char *VkShaderFloatControlsIndependenceString(VkShaderFloatControls
 void DumpVkShaderFloatControlsIndependence(Printer &p, std::string name, VkShaderFloatControlsIndependence value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkShaderFloatControlsIndependenceString(value), width);
+        p.PrintKeyString(name, VkShaderFloatControlsIndependenceString(value), width);
     }
 }
 static const char *VkColorSpaceKHRString(VkColorSpaceKHR value) {
@@ -460,9 +455,8 @@ static const char *VkColorSpaceKHRString(VkColorSpaceKHR value) {
 void DumpVkColorSpaceKHR(Printer &p, std::string name, VkColorSpaceKHR value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkColorSpaceKHRString(value), width);
+        p.PrintKeyString(name, VkColorSpaceKHRString(value), width);
     }
 }
 static const char *VkPresentModeKHRString(VkPresentModeKHR value) {
@@ -479,98 +473,121 @@ static const char *VkPresentModeKHRString(VkPresentModeKHR value) {
 void DumpVkPresentModeKHR(Printer &p, std::string name, VkPresentModeKHR value, int width = 0) {
     if (p.Type() == OutputType::json) {
         p.PrintKeyValue(name, value, width);
-        return;
     } else {
-        p.PrintKeyValue(name, VkPresentModeKHRString(value), width);
-    }
-}
-void DumpVkFormatFeatureFlags(Printer &p, std::string name, VkFormatFeatureFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_BIT");
-    if (2 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_STORAGE_IMAGE_BIT");
-    if (4 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT");
-    if (8 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT");
-    if (16 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT");
-    if (32 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT");
-    if (64 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_VERTEX_BUFFER_BIT");
-    if (128 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_COLOR_ATTACHMENT_BIT");
-    if (256 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT");
-    if (512 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT");
-    if (1024 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_BLIT_SRC_BIT");
-    if (2048 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_BLIT_DST_BIT");
-    if (4096 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT");
-    if (16384 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_TRANSFER_SRC_BIT");
-    if (32768 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_TRANSFER_DST_BIT");
-    if (131072 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT");
-    if (262144 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT");
-    if (524288 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT");
-    if (1048576 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT");
-    if (2097152 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT");
-    if (4194304 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_DISJOINT_BIT");
-    if (8388608 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT");
-    if (65536 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT");
-    if (8192 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG");
-    if (16777216 & value) p.SetAsType().PrintElement("FORMAT_FEATURE_FRAGMENT_DENSITY_MAP_BIT_EXT");
+        p.PrintKeyString(name, VkPresentModeKHRString(value), width);
+    }
+}
+std::vector<const char *>VkFormatFeatureFlagBitsGetStrings(VkFormatFeatureFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_BIT");
+    if (0x2 & value) strings.push_back("FORMAT_FEATURE_STORAGE_IMAGE_BIT");
+    if (0x4 & value) strings.push_back("FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT");
+    if (0x8 & value) strings.push_back("FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT");
+    if (0x10 & value) strings.push_back("FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT");
+    if (0x20 & value) strings.push_back("FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT");
+    if (0x40 & value) strings.push_back("FORMAT_FEATURE_VERTEX_BUFFER_BIT");
+    if (0x80 & value) strings.push_back("FORMAT_FEATURE_COLOR_ATTACHMENT_BIT");
+    if (0x100 & value) strings.push_back("FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT");
+    if (0x200 & value) strings.push_back("FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT");
+    if (0x400 & value) strings.push_back("FORMAT_FEATURE_BLIT_SRC_BIT");
+    if (0x800 & value) strings.push_back("FORMAT_FEATURE_BLIT_DST_BIT");
+    if (0x1000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT");
+    if (0x4000 & value) strings.push_back("FORMAT_FEATURE_TRANSFER_SRC_BIT");
+    if (0x8000 & value) strings.push_back("FORMAT_FEATURE_TRANSFER_DST_BIT");
+    if (0x20000 & value) strings.push_back("FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT");
+    if (0x40000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT");
+    if (0x80000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT");
+    if (0x100000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT");
+    if (0x200000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT");
+    if (0x400000 & value) strings.push_back("FORMAT_FEATURE_DISJOINT_BIT");
+    if (0x800000 & value) strings.push_back("FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT");
+    if (0x10000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT");
+    if (0x2000 & value) strings.push_back("FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG");
+    if (0x1000000 & value) strings.push_back("FORMAT_FEATURE_FRAGMENT_DENSITY_MAP_BIT_EXT");
+    return strings;
 }
 void DumpVkFormatFeatureFlags(Printer &p, std::string name, VkFormatFeatureFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkFormatFeatureFlags(p, name, static_cast<VkFormatFeatureFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkFormatFeatureFlagBitsGetStrings(static_cast<VkFormatFeatureFlagBits>(value));
+    if (static_cast<VkFormatFeatureFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkFormatFeatureFlagBits(Printer &p, std::string name, VkFormatFeatureFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkFormatFeatureFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkFormatFeatureFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkImageUsageFlags(Printer &p, std::string name, VkImageUsageFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("IMAGE_USAGE_TRANSFER_SRC_BIT");
-    if (2 & value) p.SetAsType().PrintElement("IMAGE_USAGE_TRANSFER_DST_BIT");
-    if (4 & value) p.SetAsType().PrintElement("IMAGE_USAGE_SAMPLED_BIT");
-    if (8 & value) p.SetAsType().PrintElement("IMAGE_USAGE_STORAGE_BIT");
-    if (16 & value) p.SetAsType().PrintElement("IMAGE_USAGE_COLOR_ATTACHMENT_BIT");
-    if (32 & value) p.SetAsType().PrintElement("IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT");
-    if (64 & value) p.SetAsType().PrintElement("IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT");
-    if (128 & value) p.SetAsType().PrintElement("IMAGE_USAGE_INPUT_ATTACHMENT_BIT");
-    if (256 & value) p.SetAsType().PrintElement("IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV");
-    if (512 & value) p.SetAsType().PrintElement("IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT");
+
+std::vector<const char *>VkImageUsageFlagBitsGetStrings(VkImageUsageFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("IMAGE_USAGE_TRANSFER_SRC_BIT");
+    if (0x2 & value) strings.push_back("IMAGE_USAGE_TRANSFER_DST_BIT");
+    if (0x4 & value) strings.push_back("IMAGE_USAGE_SAMPLED_BIT");
+    if (0x8 & value) strings.push_back("IMAGE_USAGE_STORAGE_BIT");
+    if (0x10 & value) strings.push_back("IMAGE_USAGE_COLOR_ATTACHMENT_BIT");
+    if (0x20 & value) strings.push_back("IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT");
+    if (0x40 & value) strings.push_back("IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT");
+    if (0x80 & value) strings.push_back("IMAGE_USAGE_INPUT_ATTACHMENT_BIT");
+    if (0x100 & value) strings.push_back("IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV");
+    if (0x200 & value) strings.push_back("IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT");
+    return strings;
 }
 void DumpVkImageUsageFlags(Printer &p, std::string name, VkImageUsageFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkImageUsageFlags(p, name, static_cast<VkImageUsageFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkImageUsageFlagBitsGetStrings(static_cast<VkImageUsageFlagBits>(value));
+    if (static_cast<VkImageUsageFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkImageUsageFlagBits(Printer &p, std::string name, VkImageUsageFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkImageUsageFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkImageUsageFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkSampleCountFlags(Printer &p, std::string name, VkSampleCountFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_1_BIT");
-    if (2 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_2_BIT");
-    if (4 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_4_BIT");
-    if (8 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_8_BIT");
-    if (16 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_16_BIT");
-    if (32 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_32_BIT");
-    if (64 & value) p.SetAsType().PrintElement("SAMPLE_COUNT_64_BIT");
+
+std::vector<const char *>VkSampleCountFlagBitsGetStrings(VkSampleCountFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("SAMPLE_COUNT_1_BIT");
+    if (0x2 & value) strings.push_back("SAMPLE_COUNT_2_BIT");
+    if (0x4 & value) strings.push_back("SAMPLE_COUNT_4_BIT");
+    if (0x8 & value) strings.push_back("SAMPLE_COUNT_8_BIT");
+    if (0x10 & value) strings.push_back("SAMPLE_COUNT_16_BIT");
+    if (0x20 & value) strings.push_back("SAMPLE_COUNT_32_BIT");
+    if (0x40 & value) strings.push_back("SAMPLE_COUNT_64_BIT");
+    return strings;
 }
 void DumpVkSampleCountFlags(Printer &p, std::string name, VkSampleCountFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSampleCountFlags(p, name, static_cast<VkSampleCountFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkSampleCountFlagBitsGetStrings(static_cast<VkSampleCountFlagBits>(value));
+    if (static_cast<VkSampleCountFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkSampleCountFlagBits(Printer &p, std::string name, VkSampleCountFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSampleCountFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkSampleCountFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
+
 std::string VkQueueFlagsString(VkQueueFlags value, int width = 0) {
     std::string out;
     bool is_first = true;
@@ -596,207 +613,303 @@ std::string VkQueueFlagsString(VkQueueFlags value, int width = 0) {
     }
     return out;
 }
-void DumpVkMemoryPropertyFlags(Printer &p, std::string name, VkMemoryPropertyFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_DEVICE_LOCAL_BIT");
-    if (2 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_HOST_VISIBLE_BIT");
-    if (4 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_HOST_COHERENT_BIT");
-    if (8 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_HOST_CACHED_BIT");
-    if (16 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT");
-    if (32 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_PROTECTED_BIT");
-    if (64 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD");
-    if (128 & value) p.SetAsType().PrintElement("MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD");
+std::vector<const char *>VkMemoryPropertyFlagBitsGetStrings(VkMemoryPropertyFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("MEMORY_PROPERTY_DEVICE_LOCAL_BIT");
+    if (0x2 & value) strings.push_back("MEMORY_PROPERTY_HOST_VISIBLE_BIT");
+    if (0x4 & value) strings.push_back("MEMORY_PROPERTY_HOST_COHERENT_BIT");
+    if (0x8 & value) strings.push_back("MEMORY_PROPERTY_HOST_CACHED_BIT");
+    if (0x10 & value) strings.push_back("MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT");
+    if (0x20 & value) strings.push_back("MEMORY_PROPERTY_PROTECTED_BIT");
+    if (0x40 & value) strings.push_back("MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD");
+    if (0x80 & value) strings.push_back("MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD");
+    return strings;
 }
 void DumpVkMemoryPropertyFlags(Printer &p, std::string name, VkMemoryPropertyFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkMemoryPropertyFlags(p, name, static_cast<VkMemoryPropertyFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkMemoryPropertyFlagBitsGetStrings(static_cast<VkMemoryPropertyFlagBits>(value));
+    if (static_cast<VkMemoryPropertyFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkMemoryPropertyFlagBits(Printer &p, std::string name, VkMemoryPropertyFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkMemoryPropertyFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkMemoryPropertyFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkMemoryHeapFlags(Printer &p, std::string name, VkMemoryHeapFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("MEMORY_HEAP_DEVICE_LOCAL_BIT");
-    if (2 & value) p.SetAsType().PrintElement("MEMORY_HEAP_MULTI_INSTANCE_BIT");
+
+std::vector<const char *>VkMemoryHeapFlagBitsGetStrings(VkMemoryHeapFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("MEMORY_HEAP_DEVICE_LOCAL_BIT");
+    if (0x2 & value) strings.push_back("MEMORY_HEAP_MULTI_INSTANCE_BIT");
+    return strings;
 }
 void DumpVkMemoryHeapFlags(Printer &p, std::string name, VkMemoryHeapFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkMemoryHeapFlags(p, name, static_cast<VkMemoryHeapFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkMemoryHeapFlagBitsGetStrings(static_cast<VkMemoryHeapFlagBits>(value));
+    if (static_cast<VkMemoryHeapFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkMemoryHeapFlagBits(Printer &p, std::string name, VkMemoryHeapFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkMemoryHeapFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkMemoryHeapFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkShaderStageFlags(Printer &p, std::string name, VkShaderStageFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("SHADER_STAGE_VERTEX_BIT");
-    if (2 & value) p.SetAsType().PrintElement("SHADER_STAGE_TESSELLATION_CONTROL_BIT");
-    if (4 & value) p.SetAsType().PrintElement("SHADER_STAGE_TESSELLATION_EVALUATION_BIT");
-    if (8 & value) p.SetAsType().PrintElement("SHADER_STAGE_GEOMETRY_BIT");
-    if (16 & value) p.SetAsType().PrintElement("SHADER_STAGE_FRAGMENT_BIT");
-    if (32 & value) p.SetAsType().PrintElement("SHADER_STAGE_COMPUTE_BIT");
-    if (0x0000001F & value) p.SetAsType().PrintElement("SHADER_STAGE_ALL_GRAPHICS");
-    if (0x7FFFFFFF & value) p.SetAsType().PrintElement("SHADER_STAGE_ALL");
-    if (256 & value) p.SetAsType().PrintElement("SHADER_STAGE_RAYGEN_BIT_NV");
-    if (512 & value) p.SetAsType().PrintElement("SHADER_STAGE_ANY_HIT_BIT_NV");
-    if (1024 & value) p.SetAsType().PrintElement("SHADER_STAGE_CLOSEST_HIT_BIT_NV");
-    if (2048 & value) p.SetAsType().PrintElement("SHADER_STAGE_MISS_BIT_NV");
-    if (4096 & value) p.SetAsType().PrintElement("SHADER_STAGE_INTERSECTION_BIT_NV");
-    if (8192 & value) p.SetAsType().PrintElement("SHADER_STAGE_CALLABLE_BIT_NV");
-    if (64 & value) p.SetAsType().PrintElement("SHADER_STAGE_TASK_BIT_NV");
-    if (128 & value) p.SetAsType().PrintElement("SHADER_STAGE_MESH_BIT_NV");
+
+std::vector<const char *>VkShaderStageFlagBitsGetStrings(VkShaderStageFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("SHADER_STAGE_VERTEX_BIT");
+    if (0x2 & value) strings.push_back("SHADER_STAGE_TESSELLATION_CONTROL_BIT");
+    if (0x4 & value) strings.push_back("SHADER_STAGE_TESSELLATION_EVALUATION_BIT");
+    if (0x8 & value) strings.push_back("SHADER_STAGE_GEOMETRY_BIT");
+    if (0x10 & value) strings.push_back("SHADER_STAGE_FRAGMENT_BIT");
+    if (0x20 & value) strings.push_back("SHADER_STAGE_COMPUTE_BIT");
+    if (0x0000001F & value) strings.push_back("SHADER_STAGE_ALL_GRAPHICS");
+    if (0x7FFFFFFF & value) strings.push_back("SHADER_STAGE_ALL");
+    if (0x100 & value) strings.push_back("SHADER_STAGE_RAYGEN_BIT_NV");
+    if (0x200 & value) strings.push_back("SHADER_STAGE_ANY_HIT_BIT_NV");
+    if (0x400 & value) strings.push_back("SHADER_STAGE_CLOSEST_HIT_BIT_NV");
+    if (0x800 & value) strings.push_back("SHADER_STAGE_MISS_BIT_NV");
+    if (0x1000 & value) strings.push_back("SHADER_STAGE_INTERSECTION_BIT_NV");
+    if (0x2000 & value) strings.push_back("SHADER_STAGE_CALLABLE_BIT_NV");
+    if (0x40 & value) strings.push_back("SHADER_STAGE_TASK_BIT_NV");
+    if (0x80 & value) strings.push_back("SHADER_STAGE_MESH_BIT_NV");
+    return strings;
 }
 void DumpVkShaderStageFlags(Printer &p, std::string name, VkShaderStageFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkShaderStageFlags(p, name, static_cast<VkShaderStageFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkShaderStageFlagBitsGetStrings(static_cast<VkShaderStageFlagBits>(value));
+    if (static_cast<VkShaderStageFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkShaderStageFlagBits(Printer &p, std::string name, VkShaderStageFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkShaderStageFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkShaderStageFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkSubgroupFeatureFlags(Printer &p, std::string name, VkSubgroupFeatureFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_BASIC_BIT");
-    if (2 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_VOTE_BIT");
-    if (4 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_ARITHMETIC_BIT");
-    if (8 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_BALLOT_BIT");
-    if (16 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_SHUFFLE_BIT");
-    if (32 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT");
-    if (64 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_CLUSTERED_BIT");
-    if (128 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_QUAD_BIT");
-    if (256 & value) p.SetAsType().PrintElement("SUBGROUP_FEATURE_PARTITIONED_BIT_NV");
+
+std::vector<const char *>VkSubgroupFeatureFlagBitsGetStrings(VkSubgroupFeatureFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("SUBGROUP_FEATURE_BASIC_BIT");
+    if (0x2 & value) strings.push_back("SUBGROUP_FEATURE_VOTE_BIT");
+    if (0x4 & value) strings.push_back("SUBGROUP_FEATURE_ARITHMETIC_BIT");
+    if (0x8 & value) strings.push_back("SUBGROUP_FEATURE_BALLOT_BIT");
+    if (0x10 & value) strings.push_back("SUBGROUP_FEATURE_SHUFFLE_BIT");
+    if (0x20 & value) strings.push_back("SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT");
+    if (0x40 & value) strings.push_back("SUBGROUP_FEATURE_CLUSTERED_BIT");
+    if (0x80 & value) strings.push_back("SUBGROUP_FEATURE_QUAD_BIT");
+    if (0x100 & value) strings.push_back("SUBGROUP_FEATURE_PARTITIONED_BIT_NV");
+    return strings;
 }
 void DumpVkSubgroupFeatureFlags(Printer &p, std::string name, VkSubgroupFeatureFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSubgroupFeatureFlags(p, name, static_cast<VkSubgroupFeatureFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkSubgroupFeatureFlagBitsGetStrings(static_cast<VkSubgroupFeatureFlagBits>(value));
+    if (static_cast<VkSubgroupFeatureFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkSubgroupFeatureFlagBits(Printer &p, std::string name, VkSubgroupFeatureFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSubgroupFeatureFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkSubgroupFeatureFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkResolveModeFlags(Printer &p, std::string name, VkResolveModeFlagBits value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (0 & value) p.SetAsType().PrintElement("RESOLVE_MODE_NONE");
-    if (1 & value) p.SetAsType().PrintElement("RESOLVE_MODE_SAMPLE_ZERO_BIT");
-    if (2 & value) p.SetAsType().PrintElement("RESOLVE_MODE_AVERAGE_BIT");
-    if (4 & value) p.SetAsType().PrintElement("RESOLVE_MODE_MIN_BIT");
-    if (8 & value) p.SetAsType().PrintElement("RESOLVE_MODE_MAX_BIT");
+
+std::vector<const char *>VkResolveModeFlagBitsGetStrings(VkResolveModeFlagBits value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0 & value) strings.push_back("RESOLVE_MODE_NONE");
+    if (0x1 & value) strings.push_back("RESOLVE_MODE_SAMPLE_ZERO_BIT");
+    if (0x2 & value) strings.push_back("RESOLVE_MODE_AVERAGE_BIT");
+    if (0x4 & value) strings.push_back("RESOLVE_MODE_MIN_BIT");
+    if (0x8 & value) strings.push_back("RESOLVE_MODE_MAX_BIT");
+    return strings;
 }
 void DumpVkResolveModeFlags(Printer &p, std::string name, VkResolveModeFlags value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkResolveModeFlags(p, name, static_cast<VkResolveModeFlagBits>(value), width);
-    p.ObjectEnd();
+    auto strings = VkResolveModeFlagBitsGetStrings(static_cast<VkResolveModeFlagBits>(value));
+    if (static_cast<VkResolveModeFlagBits>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkResolveModeFlagBits(Printer &p, std::string name, VkResolveModeFlagBits value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkResolveModeFlags(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkResolveModeFlagBitsGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkSurfaceTransformFlagsKHR(Printer &p, std::string name, VkSurfaceTransformFlagBitsKHR value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_IDENTITY_BIT_KHR");
-    if (2 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_ROTATE_90_BIT_KHR");
-    if (4 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_ROTATE_180_BIT_KHR");
-    if (8 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_ROTATE_270_BIT_KHR");
-    if (16 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR");
-    if (32 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR");
-    if (64 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR");
-    if (128 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR");
-    if (256 & value) p.SetAsType().PrintElement("SURFACE_TRANSFORM_INHERIT_BIT_KHR");
+
+std::vector<const char *>VkSurfaceTransformFlagBitsKHRGetStrings(VkSurfaceTransformFlagBitsKHR value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("SURFACE_TRANSFORM_IDENTITY_BIT_KHR");
+    if (0x2 & value) strings.push_back("SURFACE_TRANSFORM_ROTATE_90_BIT_KHR");
+    if (0x4 & value) strings.push_back("SURFACE_TRANSFORM_ROTATE_180_BIT_KHR");
+    if (0x8 & value) strings.push_back("SURFACE_TRANSFORM_ROTATE_270_BIT_KHR");
+    if (0x10 & value) strings.push_back("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR");
+    if (0x20 & value) strings.push_back("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR");
+    if (0x40 & value) strings.push_back("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR");
+    if (0x80 & value) strings.push_back("SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR");
+    if (0x100 & value) strings.push_back("SURFACE_TRANSFORM_INHERIT_BIT_KHR");
+    return strings;
 }
 void DumpVkSurfaceTransformFlagsKHR(Printer &p, std::string name, VkSurfaceTransformFlagsKHR value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSurfaceTransformFlagsKHR(p, name, static_cast<VkSurfaceTransformFlagBitsKHR>(value), width);
-    p.ObjectEnd();
+    auto strings = VkSurfaceTransformFlagBitsKHRGetStrings(static_cast<VkSurfaceTransformFlagBitsKHR>(value));
+    if (static_cast<VkSurfaceTransformFlagBitsKHR>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkSurfaceTransformFlagBitsKHR(Printer &p, std::string name, VkSurfaceTransformFlagBitsKHR value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkSurfaceTransformFlagsKHR(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkSurfaceTransformFlagBitsKHRGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkCompositeAlphaFlagsKHR(Printer &p, std::string name, VkCompositeAlphaFlagBitsKHR value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("COMPOSITE_ALPHA_OPAQUE_BIT_KHR");
-    if (2 & value) p.SetAsType().PrintElement("COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR");
-    if (4 & value) p.SetAsType().PrintElement("COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR");
-    if (8 & value) p.SetAsType().PrintElement("COMPOSITE_ALPHA_INHERIT_BIT_KHR");
+
+std::vector<const char *>VkCompositeAlphaFlagBitsKHRGetStrings(VkCompositeAlphaFlagBitsKHR value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("COMPOSITE_ALPHA_OPAQUE_BIT_KHR");
+    if (0x2 & value) strings.push_back("COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR");
+    if (0x4 & value) strings.push_back("COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR");
+    if (0x8 & value) strings.push_back("COMPOSITE_ALPHA_INHERIT_BIT_KHR");
+    return strings;
 }
 void DumpVkCompositeAlphaFlagsKHR(Printer &p, std::string name, VkCompositeAlphaFlagsKHR value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkCompositeAlphaFlagsKHR(p, name, static_cast<VkCompositeAlphaFlagBitsKHR>(value), width);
-    p.ObjectEnd();
+    auto strings = VkCompositeAlphaFlagBitsKHRGetStrings(static_cast<VkCompositeAlphaFlagBitsKHR>(value));
+    if (static_cast<VkCompositeAlphaFlagBitsKHR>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkCompositeAlphaFlagBitsKHR(Printer &p, std::string name, VkCompositeAlphaFlagBitsKHR value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkCompositeAlphaFlagsKHR(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkCompositeAlphaFlagBitsKHRGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkDeviceGroupPresentModeFlagsKHR(Printer &p, std::string name, VkDeviceGroupPresentModeFlagBitsKHR value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR");
-    if (2 & value) p.SetAsType().PrintElement("DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR");
-    if (4 & value) p.SetAsType().PrintElement("DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR");
-    if (8 & value) p.SetAsType().PrintElement("DEVICE_GROUP_PRESENT_MODE_LOCAL_MULTI_DEVICE_BIT_KHR");
+
+std::vector<const char *>VkDeviceGroupPresentModeFlagBitsKHRGetStrings(VkDeviceGroupPresentModeFlagBitsKHR value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR");
+    if (0x2 & value) strings.push_back("DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR");
+    if (0x4 & value) strings.push_back("DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR");
+    if (0x8 & value) strings.push_back("DEVICE_GROUP_PRESENT_MODE_LOCAL_MULTI_DEVICE_BIT_KHR");
+    return strings;
 }
 void DumpVkDeviceGroupPresentModeFlagsKHR(Printer &p, std::string name, VkDeviceGroupPresentModeFlagsKHR value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkDeviceGroupPresentModeFlagsKHR(p, name, static_cast<VkDeviceGroupPresentModeFlagBitsKHR>(value), width);
-    p.ObjectEnd();
+    auto strings = VkDeviceGroupPresentModeFlagBitsKHRGetStrings(static_cast<VkDeviceGroupPresentModeFlagBitsKHR>(value));
+    if (static_cast<VkDeviceGroupPresentModeFlagBitsKHR>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkDeviceGroupPresentModeFlagBitsKHR(Printer &p, std::string name, VkDeviceGroupPresentModeFlagBitsKHR value, int width = 0) {
-    if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkDeviceGroupPresentModeFlagsKHR(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkDeviceGroupPresentModeFlagBitsKHRGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
 }
-void DumpVkToolPurposeFlagsEXT(Printer &p, std::string name, VkToolPurposeFlagBitsEXT value, int width = 0) {
-    if (value == 0) p.PrintElement("None");
-    if (1 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_VALIDATION_BIT_EXT");
-    if (2 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_PROFILING_BIT_EXT");
-    if (4 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_TRACING_BIT_EXT");
-    if (8 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_ADDITIONAL_FEATURES_BIT_EXT");
-    if (16 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_MODIFYING_FEATURES_BIT_EXT");
-    if (32 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_DEBUG_REPORTING_BIT_EXT");
-    if (64 & value) p.SetAsType().PrintElement("TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT");
+
+std::vector<const char *>VkToolPurposeFlagBitsEXTGetStrings(VkToolPurposeFlagBitsEXT value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("TOOL_PURPOSE_VALIDATION_BIT_EXT");
+    if (0x2 & value) strings.push_back("TOOL_PURPOSE_PROFILING_BIT_EXT");
+    if (0x4 & value) strings.push_back("TOOL_PURPOSE_TRACING_BIT_EXT");
+    if (0x8 & value) strings.push_back("TOOL_PURPOSE_ADDITIONAL_FEATURES_BIT_EXT");
+    if (0x10 & value) strings.push_back("TOOL_PURPOSE_MODIFYING_FEATURES_BIT_EXT");
+    if (0x20 & value) strings.push_back("TOOL_PURPOSE_DEBUG_REPORTING_BIT_EXT");
+    if (0x40 & value) strings.push_back("TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT");
+    return strings;
 }
 void DumpVkToolPurposeFlagsEXT(Printer &p, std::string name, VkToolPurposeFlagsEXT value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkToolPurposeFlagsEXT(p, name, static_cast<VkToolPurposeFlagBitsEXT>(value), width);
-    p.ObjectEnd();
+    auto strings = VkToolPurposeFlagBitsEXTGetStrings(static_cast<VkToolPurposeFlagBitsEXT>(value));
+    if (static_cast<VkToolPurposeFlagBitsEXT>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
 void DumpVkToolPurposeFlagBitsEXT(Printer &p, std::string name, VkToolPurposeFlagBitsEXT value, int width = 0) {
+    auto strings = VkToolPurposeFlagBitsEXTGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
+}
+
+std::vector<const char *>VkSurfaceCounterFlagBitsEXTGetStrings(VkSurfaceCounterFlagBitsEXT value) {
+    std::vector<const char *> strings;
+    if (value == 0) strings.push_back("None");
+    if (0x1 & value) strings.push_back("SURFACE_COUNTER_VBLANK_EXT");
+    return strings;
+}
+void DumpVkSurfaceCounterFlagsEXT(Printer &p, std::string name, VkSurfaceCounterFlagsEXT value, int width = 0) {
     if (p.Type() == OutputType::json) { p.PrintKeyValue(name, value); return; }
-    p.ObjectStart(name);
-    DumpVkToolPurposeFlagsEXT(p, name, value, width);
-    p.ObjectEnd();
+    auto strings = VkSurfaceCounterFlagBitsEXTGetStrings(static_cast<VkSurfaceCounterFlagBitsEXT>(value));
+    if (static_cast<VkSurfaceCounterFlagBitsEXT>(value) == 0) {
+        ArrayWrapper arr(p, name, 0);
+        p.SetAsType().PrintString("None");
+        return;
+    }
+    ArrayWrapper arr(p, name, strings.size());
+    for(auto& str : strings){
+        p.SetAsType().PrintString(str);
+    }
 }
+void DumpVkSurfaceCounterFlagBitsEXT(Printer &p, std::string name, VkSurfaceCounterFlagBitsEXT value, int width = 0) {
+    auto strings = VkSurfaceCounterFlagBitsEXTGetStrings(value);
+    p.PrintKeyString(name, strings.at(0), width);
+}
+
 void DumpVkPhysicalDeviceFeatures(Printer &p, std::string name, VkPhysicalDeviceFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("robustBufferAccess", static_cast<bool>(obj.robustBufferAccess), 39);
     p.PrintKeyBool("fullDrawIndexUint32", static_cast<bool>(obj.fullDrawIndexUint32), 39);
     p.PrintKeyBool("imageCubeArray", static_cast<bool>(obj.imageCubeArray), 39);
@@ -852,14 +965,12 @@ void DumpVkPhysicalDeviceFeatures(Printer &p, std::string name, VkPhysicalDevice
     p.PrintKeyBool("sparseResidencyAliased", static_cast<bool>(obj.sparseResidencyAliased), 39);
     p.PrintKeyBool("variableMultisampleRate", static_cast<bool>(obj.variableMultisampleRate), 39);
     p.PrintKeyBool("inheritedQueries", static_cast<bool>(obj.inheritedQueries), 39);
-    p.ObjectEnd();
 }
 void DumpVkExtent3D(Printer &p, std::string name, VkExtent3D &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("width", obj.width, 6);
     p.PrintKeyValue("height", obj.height, 6);
     p.PrintKeyValue("depth", obj.depth, 6);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceLimits(Printer &p, std::string name, VkPhysicalDeviceLimits &obj) {
     if (p.Type() == OutputType::json)
@@ -918,17 +1029,17 @@ void DumpVkPhysicalDeviceLimits(Printer &p, std::string name, VkPhysicalDeviceLi
     p.PrintKeyValue("maxFragmentDualSrcAttachments", obj.maxFragmentDualSrcAttachments, 47);
     p.PrintKeyValue("maxFragmentCombinedOutputResources", obj.maxFragmentCombinedOutputResources, 47);
     p.PrintKeyValue("maxComputeSharedMemorySize", obj.maxComputeSharedMemorySize, 47);
-    p.ArrayStart("maxComputeWorkGroupCount", 3);
-    p.PrintElement(obj.maxComputeWorkGroupCount[0]);
-    p.PrintElement(obj.maxComputeWorkGroupCount[1]);
-    p.PrintElement(obj.maxComputeWorkGroupCount[2]);
-    p.ArrayEnd();
+    {   ArrayWrapper arr(p,"maxComputeWorkGroupCount", 3);
+        p.PrintElement(obj.maxComputeWorkGroupCount[0]);
+        p.PrintElement(obj.maxComputeWorkGroupCount[1]);
+        p.PrintElement(obj.maxComputeWorkGroupCount[2]);
+    }
     p.PrintKeyValue("maxComputeWorkGroupInvocations", obj.maxComputeWorkGroupInvocations, 47);
-    p.ArrayStart("maxComputeWorkGroupSize", 3);
-    p.PrintElement(obj.maxComputeWorkGroupSize[0]);
-    p.PrintElement(obj.maxComputeWorkGroupSize[1]);
-    p.PrintElement(obj.maxComputeWorkGroupSize[2]);
-    p.ArrayEnd();
+    {   ArrayWrapper arr(p,"maxComputeWorkGroupSize", 3);
+        p.PrintElement(obj.maxComputeWorkGroupSize[0]);
+        p.PrintElement(obj.maxComputeWorkGroupSize[1]);
+        p.PrintElement(obj.maxComputeWorkGroupSize[2]);
+    }
     p.PrintKeyValue("subPixelPrecisionBits", obj.subPixelPrecisionBits, 47);
     p.PrintKeyValue("subTexelPrecisionBits", obj.subTexelPrecisionBits, 47);
     p.PrintKeyValue("mipmapPrecisionBits", obj.mipmapPrecisionBits, 47);
@@ -937,14 +1048,14 @@ void DumpVkPhysicalDeviceLimits(Printer &p, std::string name, VkPhysicalDeviceLi
     p.PrintKeyValue("maxSamplerLodBias", obj.maxSamplerLodBias, 47);
     p.PrintKeyValue("maxSamplerAnisotropy", obj.maxSamplerAnisotropy, 47);
     p.PrintKeyValue("maxViewports", obj.maxViewports, 47);
-    p.ArrayStart("maxViewportDimensions", 2);
-    p.PrintElement(obj.maxViewportDimensions[0]);
-    p.PrintElement(obj.maxViewportDimensions[1]);
-    p.ArrayEnd();
-    p.ArrayStart("viewportBoundsRange", 2);
-    p.PrintElement(obj.viewportBoundsRange[0]);
-    p.PrintElement(obj.viewportBoundsRange[1]);
-    p.ArrayEnd();
+    {   ArrayWrapper arr(p,"maxViewportDimensions", 2);
+        p.PrintElement(obj.maxViewportDimensions[0]);
+        p.PrintElement(obj.maxViewportDimensions[1]);
+    }
+    {   ArrayWrapper arr(p,"viewportBoundsRange", 2);
+        p.PrintElement(obj.viewportBoundsRange[0]);
+        p.PrintElement(obj.viewportBoundsRange[1]);
+    }
     p.PrintKeyValue("viewportSubPixelBits", obj.viewportSubPixelBits, 47);
     p.PrintKeyValue("minMemoryMapAlignment", obj.minMemoryMapAlignment, 47);
     p.PrintKeyValue("minTexelBufferOffsetAlignment", to_hex_str(p, obj.minTexelBufferOffsetAlignment), 47);
@@ -977,14 +1088,14 @@ void DumpVkPhysicalDeviceLimits(Printer &p, std::string name, VkPhysicalDeviceLi
     p.PrintKeyValue("maxCullDistances", obj.maxCullDistances, 47);
     p.PrintKeyValue("maxCombinedClipAndCullDistances", obj.maxCombinedClipAndCullDistances, 47);
     p.PrintKeyValue("discreteQueuePriorities", obj.discreteQueuePriorities, 47);
-    p.ArrayStart("pointSizeRange", 2);
-    p.PrintElement(obj.pointSizeRange[0]);
-    p.PrintElement(obj.pointSizeRange[1]);
-    p.ArrayEnd();
-    p.ArrayStart("lineWidthRange", 2);
-    p.PrintElement(obj.lineWidthRange[0]);
-    p.PrintElement(obj.lineWidthRange[1]);
-    p.ArrayEnd();
+    {   ArrayWrapper arr(p,"pointSizeRange", 2);
+        p.PrintElement(obj.pointSizeRange[0]);
+        p.PrintElement(obj.pointSizeRange[1]);
+    }
+    {   ArrayWrapper arr(p,"lineWidthRange", 2);
+        p.PrintElement(obj.lineWidthRange[0]);
+        p.PrintElement(obj.lineWidthRange[1]);
+    }
     p.PrintKeyValue("pointSizeGranularity", obj.pointSizeGranularity, 47);
     p.PrintKeyValue("lineWidthGranularity", obj.lineWidthGranularity, 47);
     p.PrintKeyBool("strictLines", static_cast<bool>(obj.strictLines), 47);
@@ -1007,96 +1118,82 @@ void DumpVkPhysicalDeviceSparseProperties(Printer &p, std::string name, VkPhysic
     p.ObjectEnd();
 }
 void DumpVkLayerProperties(Printer &p, std::string name, VkLayerProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyString("layerName", obj.layerName, 21);
     p.PrintKeyValue("specVersion", obj.specVersion, 21);
     p.PrintKeyValue("implementationVersion", obj.implementationVersion, 21);
     p.PrintKeyString("description", obj.description, 21);
-    p.ObjectEnd();
 }
 void DumpVkExtent2D(Printer &p, std::string name, VkExtent2D &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("width", obj.width, 6);
     p.PrintKeyValue("height", obj.height, 6);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSubgroupProperties(Printer &p, std::string name, VkPhysicalDeviceSubgroupProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("subgroupSize", obj.subgroupSize, 25);
     DumpVkShaderStageFlags(p, "supportedStages", obj.supportedStages, 25);
     DumpVkSubgroupFeatureFlags(p, "supportedOperations", obj.supportedOperations, 25);
     p.PrintKeyBool("quadOperationsInAllStages", static_cast<bool>(obj.quadOperationsInAllStages), 25);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevice16BitStorageFeatures(Printer &p, std::string name, VkPhysicalDevice16BitStorageFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("storageBuffer16BitAccess", static_cast<bool>(obj.storageBuffer16BitAccess), 34);
     p.PrintKeyBool("uniformAndStorageBuffer16BitAccess", static_cast<bool>(obj.uniformAndStorageBuffer16BitAccess), 34);
     p.PrintKeyBool("storagePushConstant16", static_cast<bool>(obj.storagePushConstant16), 34);
     p.PrintKeyBool("storageInputOutput16", static_cast<bool>(obj.storageInputOutput16), 34);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePointClippingProperties(Printer &p, std::string name, VkPhysicalDevicePointClippingProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkPointClippingBehavior(p, "pointClippingBehavior", obj.pointClippingBehavior, 0);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceMultiviewFeatures(Printer &p, std::string name, VkPhysicalDeviceMultiviewFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("multiview", static_cast<bool>(obj.multiview), 27);
     p.PrintKeyBool("multiviewGeometryShader", static_cast<bool>(obj.multiviewGeometryShader), 27);
     p.PrintKeyBool("multiviewTessellationShader", static_cast<bool>(obj.multiviewTessellationShader), 27);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceMultiviewProperties(Printer &p, std::string name, VkPhysicalDeviceMultiviewProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxMultiviewViewCount", obj.maxMultiviewViewCount, 25);
     p.PrintKeyValue("maxMultiviewInstanceIndex", obj.maxMultiviewInstanceIndex, 25);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVariablePointersFeatures(Printer &p, std::string name, VkPhysicalDeviceVariablePointersFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("variablePointersStorageBuffer", static_cast<bool>(obj.variablePointersStorageBuffer), 29);
     p.PrintKeyBool("variablePointers", static_cast<bool>(obj.variablePointers), 29);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceProtectedMemoryFeatures(Printer &p, std::string name, VkPhysicalDeviceProtectedMemoryFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("protectedMemory", static_cast<bool>(obj.protectedMemory), 15);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceProtectedMemoryProperties(Printer &p, std::string name, VkPhysicalDeviceProtectedMemoryProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("protectedNoFault", static_cast<bool>(obj.protectedNoFault), 16);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSamplerYcbcrConversionFeatures(Printer &p, std::string name, VkPhysicalDeviceSamplerYcbcrConversionFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("samplerYcbcrConversion", static_cast<bool>(obj.samplerYcbcrConversion), 22);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceIDProperties(Printer &p, std::string name, VkPhysicalDeviceIDProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyString("deviceUUID", to_string_16(obj.deviceUUID), 15);
     p.PrintKeyString("driverUUID", to_string_16(obj.driverUUID), 15);
     if (obj.deviceLUIDValid) p.PrintKeyString("deviceLUID", to_string_8(obj.deviceLUID), 15);
     p.PrintKeyValue("deviceNodeMask", obj.deviceNodeMask, 15);
     p.PrintKeyBool("deviceLUIDValid", static_cast<bool>(obj.deviceLUIDValid), 15);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceMaintenance3Properties(Printer &p, std::string name, VkPhysicalDeviceMaintenance3Properties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxPerSetDescriptors", obj.maxPerSetDescriptors, 23);
     p.PrintKeyValue("maxMemoryAllocationSize", to_hex_str(p, obj.maxMemoryAllocationSize), 23);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceShaderDrawParametersFeatures(Printer &p, std::string name, VkPhysicalDeviceShaderDrawParametersFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderDrawParameters", static_cast<bool>(obj.shaderDrawParameters), 20);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVulkan11Features(Printer &p, std::string name, VkPhysicalDeviceVulkan11Features &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("storageBuffer16BitAccess", static_cast<bool>(obj.storageBuffer16BitAccess), 34);
     p.PrintKeyBool("uniformAndStorageBuffer16BitAccess", static_cast<bool>(obj.uniformAndStorageBuffer16BitAccess), 34);
     p.PrintKeyBool("storagePushConstant16", static_cast<bool>(obj.storagePushConstant16), 34);
@@ -1109,10 +1206,9 @@ void DumpVkPhysicalDeviceVulkan11Features(Printer &p, std::string name, VkPhysic
     p.PrintKeyBool("protectedMemory", static_cast<bool>(obj.protectedMemory), 34);
     p.PrintKeyBool("samplerYcbcrConversion", static_cast<bool>(obj.samplerYcbcrConversion), 34);
     p.PrintKeyBool("shaderDrawParameters", static_cast<bool>(obj.shaderDrawParameters), 34);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVulkan11Properties(Printer &p, std::string name, VkPhysicalDeviceVulkan11Properties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyString("deviceUUID", to_string_16(obj.deviceUUID), 33);
     p.PrintKeyString("driverUUID", to_string_16(obj.driverUUID), 33);
     if (obj.deviceLUIDValid) p.PrintKeyString("deviceLUID", to_string_8(obj.deviceLUID), 33);
@@ -1128,10 +1224,9 @@ void DumpVkPhysicalDeviceVulkan11Properties(Printer &p, std::string name, VkPhys
     p.PrintKeyBool("protectedNoFault", static_cast<bool>(obj.protectedNoFault), 33);
     p.PrintKeyValue("maxPerSetDescriptors", obj.maxPerSetDescriptors, 33);
     p.PrintKeyValue("maxMemoryAllocationSize", to_hex_str(p, obj.maxMemoryAllocationSize), 33);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVulkan12Features(Printer &p, std::string name, VkPhysicalDeviceVulkan12Features &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("samplerMirrorClampToEdge", static_cast<bool>(obj.samplerMirrorClampToEdge), 50);
     p.PrintKeyBool("drawIndirectCount", static_cast<bool>(obj.drawIndirectCount), 50);
     p.PrintKeyBool("storageBuffer8BitAccess", static_cast<bool>(obj.storageBuffer8BitAccess), 50);
@@ -1179,14 +1274,13 @@ void DumpVkPhysicalDeviceVulkan12Features(Printer &p, std::string name, VkPhysic
     p.PrintKeyBool("shaderOutputViewportIndex", static_cast<bool>(obj.shaderOutputViewportIndex), 50);
     p.PrintKeyBool("shaderOutputLayer", static_cast<bool>(obj.shaderOutputLayer), 50);
     p.PrintKeyBool("subgroupBroadcastDynamicId", static_cast<bool>(obj.subgroupBroadcastDynamicId), 50);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVulkan12Properties(Printer &p, std::string name, VkPhysicalDeviceVulkan12Properties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkDriverId(p, "driverID", obj.driverID, 52);
     p.PrintKeyString("driverName", obj.driverName, 52);
     p.PrintKeyString("driverInfo", obj.driverInfo, 52);
-    p.PrintKeyValue("conformanceVersion", obj.conformanceVersion, 52);
+    DumpVkConformanceVersion(p, "conformanceVersion", obj.conformanceVersion, 52);
     DumpVkShaderFloatControlsIndependence(p, "denormBehaviorIndependence", obj.denormBehaviorIndependence, 52);
     DumpVkShaderFloatControlsIndependence(p, "roundingModeIndependence", obj.roundingModeIndependence, 52);
     p.PrintKeyBool("shaderSignedZeroInfNanPreserveFloat16", static_cast<bool>(obj.shaderSignedZeroInfNanPreserveFloat16), 52);
@@ -1235,37 +1329,32 @@ void DumpVkPhysicalDeviceVulkan12Properties(Printer &p, std::string name, VkPhys
     p.PrintKeyBool("filterMinmaxImageComponentMapping", static_cast<bool>(obj.filterMinmaxImageComponentMapping), 52);
     p.PrintKeyValue("maxTimelineSemaphoreValueDifference", obj.maxTimelineSemaphoreValueDifference, 52);
     DumpVkSampleCountFlags(p, "framebufferIntegerColorSampleCounts", obj.framebufferIntegerColorSampleCounts, 52);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevice8BitStorageFeatures(Printer &p, std::string name, VkPhysicalDevice8BitStorageFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("storageBuffer8BitAccess", static_cast<bool>(obj.storageBuffer8BitAccess), 33);
     p.PrintKeyBool("uniformAndStorageBuffer8BitAccess", static_cast<bool>(obj.uniformAndStorageBuffer8BitAccess), 33);
     p.PrintKeyBool("storagePushConstant8", static_cast<bool>(obj.storagePushConstant8), 33);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDriverProperties(Printer &p, std::string name, VkPhysicalDeviceDriverProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkDriverId(p, "driverID", obj.driverID, 18);
     p.PrintKeyString("driverName", obj.driverName, 18);
     p.PrintKeyString("driverInfo", obj.driverInfo, 18);
-    p.PrintKeyValue("conformanceVersion", obj.conformanceVersion, 18);
-    p.ObjectEnd();
+    DumpVkConformanceVersion(p, "conformanceVersion", obj.conformanceVersion, 18);
 }
 void DumpVkPhysicalDeviceShaderAtomicInt64Features(Printer &p, std::string name, VkPhysicalDeviceShaderAtomicInt64Features &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderBufferInt64Atomics", static_cast<bool>(obj.shaderBufferInt64Atomics), 24);
     p.PrintKeyBool("shaderSharedInt64Atomics", static_cast<bool>(obj.shaderSharedInt64Atomics), 24);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceShaderFloat16Int8Features(Printer &p, std::string name, VkPhysicalDeviceShaderFloat16Int8Features &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderFloat16", static_cast<bool>(obj.shaderFloat16), 13);
     p.PrintKeyBool("shaderInt8", static_cast<bool>(obj.shaderInt8), 13);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceFloatControlsProperties(Printer &p, std::string name, VkPhysicalDeviceFloatControlsProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkShaderFloatControlsIndependence(p, "denormBehaviorIndependence", obj.denormBehaviorIndependence, 37);
     DumpVkShaderFloatControlsIndependence(p, "roundingModeIndependence", obj.roundingModeIndependence, 37);
     p.PrintKeyBool("shaderSignedZeroInfNanPreserveFloat16", static_cast<bool>(obj.shaderSignedZeroInfNanPreserveFloat16), 37);
@@ -1283,10 +1372,9 @@ void DumpVkPhysicalDeviceFloatControlsProperties(Printer &p, std::string name, V
     p.PrintKeyBool("shaderRoundingModeRTZFloat16", static_cast<bool>(obj.shaderRoundingModeRTZFloat16), 37);
     p.PrintKeyBool("shaderRoundingModeRTZFloat32", static_cast<bool>(obj.shaderRoundingModeRTZFloat32), 37);
     p.PrintKeyBool("shaderRoundingModeRTZFloat64", static_cast<bool>(obj.shaderRoundingModeRTZFloat64), 37);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDescriptorIndexingFeatures(Printer &p, std::string name, VkPhysicalDeviceDescriptorIndexingFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderInputAttachmentArrayDynamicIndexing", static_cast<bool>(obj.shaderInputAttachmentArrayDynamicIndexing), 50);
     p.PrintKeyBool("shaderUniformTexelBufferArrayDynamicIndexing", static_cast<bool>(obj.shaderUniformTexelBufferArrayDynamicIndexing), 50);
     p.PrintKeyBool("shaderStorageTexelBufferArrayDynamicIndexing", static_cast<bool>(obj.shaderStorageTexelBufferArrayDynamicIndexing), 50);
@@ -1307,10 +1395,9 @@ void DumpVkPhysicalDeviceDescriptorIndexingFeatures(Printer &p, std::string name
     p.PrintKeyBool("descriptorBindingPartiallyBound", static_cast<bool>(obj.descriptorBindingPartiallyBound), 50);
     p.PrintKeyBool("descriptorBindingVariableDescriptorCount", static_cast<bool>(obj.descriptorBindingVariableDescriptorCount), 50);
     p.PrintKeyBool("runtimeDescriptorArray", static_cast<bool>(obj.runtimeDescriptorArray), 50);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDescriptorIndexingProperties(Printer &p, std::string name, VkPhysicalDeviceDescriptorIndexingProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxUpdateAfterBindDescriptorsInAllPools", obj.maxUpdateAfterBindDescriptorsInAllPools, 52);
     p.PrintKeyBool("shaderUniformBufferArrayNonUniformIndexingNative", static_cast<bool>(obj.shaderUniformBufferArrayNonUniformIndexingNative), 52);
     p.PrintKeyBool("shaderSampledImageArrayNonUniformIndexingNative", static_cast<bool>(obj.shaderSampledImageArrayNonUniformIndexingNative), 52);
@@ -1334,78 +1421,65 @@ void DumpVkPhysicalDeviceDescriptorIndexingProperties(Printer &p, std::string na
     p.PrintKeyValue("maxDescriptorSetUpdateAfterBindSampledImages", obj.maxDescriptorSetUpdateAfterBindSampledImages, 52);
     p.PrintKeyValue("maxDescriptorSetUpdateAfterBindStorageImages", obj.maxDescriptorSetUpdateAfterBindStorageImages, 52);
     p.PrintKeyValue("maxDescriptorSetUpdateAfterBindInputAttachments", obj.maxDescriptorSetUpdateAfterBindInputAttachments, 52);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDepthStencilResolveProperties(Printer &p, std::string name, VkPhysicalDeviceDepthStencilResolveProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkResolveModeFlags(p, "supportedDepthResolveModes", obj.supportedDepthResolveModes, 22);
     DumpVkResolveModeFlags(p, "supportedStencilResolveModes", obj.supportedStencilResolveModes, 22);
     p.PrintKeyBool("independentResolveNone", static_cast<bool>(obj.independentResolveNone), 22);
     p.PrintKeyBool("independentResolve", static_cast<bool>(obj.independentResolve), 22);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceScalarBlockLayoutFeatures(Printer &p, std::string name, VkPhysicalDeviceScalarBlockLayoutFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("scalarBlockLayout", static_cast<bool>(obj.scalarBlockLayout), 17);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSamplerFilterMinmaxProperties(Printer &p, std::string name, VkPhysicalDeviceSamplerFilterMinmaxProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("filterMinmaxSingleComponentFormats", static_cast<bool>(obj.filterMinmaxSingleComponentFormats), 34);
     p.PrintKeyBool("filterMinmaxImageComponentMapping", static_cast<bool>(obj.filterMinmaxImageComponentMapping), 34);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVulkanMemoryModelFeatures(Printer &p, std::string name, VkPhysicalDeviceVulkanMemoryModelFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("vulkanMemoryModel", static_cast<bool>(obj.vulkanMemoryModel), 45);
     p.PrintKeyBool("vulkanMemoryModelDeviceScope", static_cast<bool>(obj.vulkanMemoryModelDeviceScope), 45);
     p.PrintKeyBool("vulkanMemoryModelAvailabilityVisibilityChains", static_cast<bool>(obj.vulkanMemoryModelAvailabilityVisibilityChains), 45);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceImagelessFramebufferFeatures(Printer &p, std::string name, VkPhysicalDeviceImagelessFramebufferFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("imagelessFramebuffer", static_cast<bool>(obj.imagelessFramebuffer), 20);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceUniformBufferStandardLayoutFeatures(Printer &p, std::string name, VkPhysicalDeviceUniformBufferStandardLayoutFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("uniformBufferStandardLayout", static_cast<bool>(obj.uniformBufferStandardLayout), 27);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceShaderSubgroupExtendedTypesFeatures(Printer &p, std::string name, VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderSubgroupExtendedTypes", static_cast<bool>(obj.shaderSubgroupExtendedTypes), 27);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSeparateDepthStencilLayoutsFeatures(Printer &p, std::string name, VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("separateDepthStencilLayouts", static_cast<bool>(obj.separateDepthStencilLayouts), 27);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceHostQueryResetFeatures(Printer &p, std::string name, VkPhysicalDeviceHostQueryResetFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("hostQueryReset", static_cast<bool>(obj.hostQueryReset), 14);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTimelineSemaphoreFeatures(Printer &p, std::string name, VkPhysicalDeviceTimelineSemaphoreFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("timelineSemaphore", static_cast<bool>(obj.timelineSemaphore), 17);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTimelineSemaphoreProperties(Printer &p, std::string name, VkPhysicalDeviceTimelineSemaphoreProperties &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxTimelineSemaphoreValueDifference", obj.maxTimelineSemaphoreValueDifference, 35);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceBufferDeviceAddressFeatures(Printer &p, std::string name, VkPhysicalDeviceBufferDeviceAddressFeatures &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("bufferDeviceAddress", static_cast<bool>(obj.bufferDeviceAddress), 32);
     p.PrintKeyBool("bufferDeviceAddressCaptureReplay", static_cast<bool>(obj.bufferDeviceAddressCaptureReplay), 32);
     p.PrintKeyBool("bufferDeviceAddressMultiDevice", static_cast<bool>(obj.bufferDeviceAddressMultiDevice), 32);
-    p.ObjectEnd();
 }
 void DumpVkSurfaceCapabilitiesKHR(Printer &p, std::string name, VkSurfaceCapabilitiesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("minImageCount", obj.minImageCount, 19);
     p.PrintKeyValue("maxImageCount", obj.maxImageCount, 19);
     DumpVkExtent2D(p, "currentExtent", obj.currentExtent);
@@ -1416,21 +1490,18 @@ void DumpVkSurfaceCapabilitiesKHR(Printer &p, std::string name, VkSurfaceCapabil
     DumpVkSurfaceTransformFlagBitsKHR(p, "currentTransform", obj.currentTransform, 19);
     DumpVkCompositeAlphaFlagsKHR(p, "supportedCompositeAlpha", obj.supportedCompositeAlpha, 19);
     DumpVkImageUsageFlags(p, "supportedUsageFlags", obj.supportedUsageFlags, 19);
-    p.ObjectEnd();
 }
 void DumpVkSurfaceFormatKHR(Printer &p, std::string name, VkSurfaceFormatKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkFormat(p, "format", obj.format, 0);
     DumpVkColorSpaceKHR(p, "colorSpace", obj.colorSpace, 0);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDiscardRectanglePropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceDiscardRectanglePropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxDiscardRectangles", obj.maxDiscardRectangles, 20);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceConservativeRasterizationPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceConservativeRasterizationPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("primitiveOverestimationSize", obj.primitiveOverestimationSize, 43);
     p.PrintKeyValue("maxExtraPrimitiveOverestimationSize", obj.maxExtraPrimitiveOverestimationSize, 43);
     p.PrintKeyValue("extraPrimitiveOverestimationSizeGranularity", obj.extraPrimitiveOverestimationSizeGranularity, 43);
@@ -1440,285 +1511,247 @@ void DumpVkPhysicalDeviceConservativeRasterizationPropertiesEXT(Printer &p, std:
     p.PrintKeyBool("degenerateLinesRasterized", static_cast<bool>(obj.degenerateLinesRasterized), 43);
     p.PrintKeyBool("fullyCoveredFragmentShaderInputVariable", static_cast<bool>(obj.fullyCoveredFragmentShaderInputVariable), 43);
     p.PrintKeyBool("conservativeRasterizationPostDepthCoverage", static_cast<bool>(obj.conservativeRasterizationPostDepthCoverage), 43);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceDepthClipEnableFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceDepthClipEnableFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("depthClipEnable", static_cast<bool>(obj.depthClipEnable), 15);
-    p.ObjectEnd();
 }
 void DumpVkSharedPresentSurfaceCapabilitiesKHR(Printer &p, std::string name, VkSharedPresentSurfaceCapabilitiesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkImageUsageFlags(p, "sharedPresentSupportedUsageFlags", obj.sharedPresentSupportedUsageFlags, 0);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePerformanceQueryFeaturesKHR(Printer &p, std::string name, VkPhysicalDevicePerformanceQueryFeaturesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("performanceCounterQueryPools", static_cast<bool>(obj.performanceCounterQueryPools), 36);
     p.PrintKeyBool("performanceCounterMultipleQueryPools", static_cast<bool>(obj.performanceCounterMultipleQueryPools), 36);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePerformanceQueryPropertiesKHR(Printer &p, std::string name, VkPhysicalDevicePerformanceQueryPropertiesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("allowCommandBufferQueryCopies", static_cast<bool>(obj.allowCommandBufferQueryCopies), 29);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceInlineUniformBlockFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceInlineUniformBlockFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("inlineUniformBlock", static_cast<bool>(obj.inlineUniformBlock), 50);
     p.PrintKeyBool("descriptorBindingInlineUniformBlockUpdateAfterBind", static_cast<bool>(obj.descriptorBindingInlineUniformBlockUpdateAfterBind), 50);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceInlineUniformBlockPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceInlineUniformBlockPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxInlineUniformBlockSize", obj.maxInlineUniformBlockSize, 55);
     p.PrintKeyValue("maxPerStageDescriptorInlineUniformBlocks", obj.maxPerStageDescriptorInlineUniformBlocks, 55);
     p.PrintKeyValue("maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks", obj.maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks, 55);
     p.PrintKeyValue("maxDescriptorSetInlineUniformBlocks", obj.maxDescriptorSetInlineUniformBlocks, 55);
     p.PrintKeyValue("maxDescriptorSetUpdateAfterBindInlineUniformBlocks", obj.maxDescriptorSetUpdateAfterBindInlineUniformBlocks, 55);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSampleLocationsPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceSampleLocationsPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkSampleCountFlags(p, "sampleLocationSampleCounts", obj.sampleLocationSampleCounts, 32);
     DumpVkExtent2D(p, "maxSampleLocationGridSize", obj.maxSampleLocationGridSize);
-    p.ArrayStart("sampleLocationCoordinateRange", 2);
-    p.PrintElement(obj.sampleLocationCoordinateRange[0]);
-    p.PrintElement(obj.sampleLocationCoordinateRange[1]);
-    p.ArrayEnd();
+    {   ArrayWrapper arr(p,"sampleLocationCoordinateRange", 2);
+        p.PrintElement(obj.sampleLocationCoordinateRange[0]);
+        p.PrintElement(obj.sampleLocationCoordinateRange[1]);
+    }
     p.PrintKeyValue("sampleLocationSubPixelBits", obj.sampleLocationSubPixelBits, 32);
     p.PrintKeyBool("variableSampleLocations", static_cast<bool>(obj.variableSampleLocations), 32);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceBlendOperationAdvancedFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("advancedBlendCoherentOperations", static_cast<bool>(obj.advancedBlendCoherentOperations), 31);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceBlendOperationAdvancedPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("advancedBlendMaxColorAttachments", obj.advancedBlendMaxColorAttachments, 37);
     p.PrintKeyBool("advancedBlendIndependentBlend", static_cast<bool>(obj.advancedBlendIndependentBlend), 37);
     p.PrintKeyBool("advancedBlendNonPremultipliedSrcColor", static_cast<bool>(obj.advancedBlendNonPremultipliedSrcColor), 37);
     p.PrintKeyBool("advancedBlendNonPremultipliedDstColor", static_cast<bool>(obj.advancedBlendNonPremultipliedDstColor), 37);
     p.PrintKeyBool("advancedBlendCorrelatedOverlap", static_cast<bool>(obj.advancedBlendCorrelatedOverlap), 37);
     p.PrintKeyBool("advancedBlendAllOperations", static_cast<bool>(obj.advancedBlendAllOperations), 37);
-    p.ObjectEnd();
 }
 void DumpVkDrmFormatModifierPropertiesEXT(Printer &p, std::string name, VkDrmFormatModifierPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("drmFormatModifier", obj.drmFormatModifier, 27);
     p.PrintKeyValue("drmFormatModifierPlaneCount", obj.drmFormatModifierPlaneCount, 27);
     DumpVkFormatFeatureFlags(p, "drmFormatModifierTilingFeatures", obj.drmFormatModifierTilingFeatures, 27);
-    p.ObjectEnd();
 }
 void DumpVkDrmFormatModifierPropertiesListEXT(Printer &p, std::string name, VkDrmFormatModifierPropertiesListEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("drmFormatModifierCount", obj.drmFormatModifierCount, 52);
-    p.ArrayStart("pDrmFormatModifierProperties", obj.drmFormatModifierCount);
+    ArrayWrapper arr(p,"pDrmFormatModifierProperties", obj.drmFormatModifierCount);
     for (uint32_t i = 0; i < obj.drmFormatModifierCount; i++) {
         if (obj.pDrmFormatModifierProperties != nullptr) {
             p.SetElementIndex(i);
             DumpVkDrmFormatModifierPropertiesEXT(p, "pDrmFormatModifierProperties", obj.pDrmFormatModifierProperties[i]);
         }
     }
-    p.ArrayEnd();
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceExternalMemoryHostPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceExternalMemoryHostPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("minImportedHostPointerAlignment", to_hex_str(p, obj.minImportedHostPointerAlignment), 31);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceShaderClockFeaturesKHR(Printer &p, std::string name, VkPhysicalDeviceShaderClockFeaturesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderSubgroupClock", static_cast<bool>(obj.shaderSubgroupClock), 19);
     p.PrintKeyBool("shaderDeviceClock", static_cast<bool>(obj.shaderDeviceClock), 19);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVertexAttributeDivisorPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxVertexAttribDivisor", obj.maxVertexAttribDivisor, 22);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceVertexAttributeDivisorFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("vertexAttributeInstanceRateDivisor", static_cast<bool>(obj.vertexAttributeInstanceRateDivisor), 38);
     p.PrintKeyBool("vertexAttributeInstanceRateZeroDivisor", static_cast<bool>(obj.vertexAttributeInstanceRateZeroDivisor), 38);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePCIBusInfoPropertiesEXT(Printer &p, std::string name, VkPhysicalDevicePCIBusInfoPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("pciDomain", obj.pciDomain, 11);
     p.PrintKeyValue("pciBus", obj.pciBus, 11);
     p.PrintKeyValue("pciDevice", obj.pciDevice, 11);
     p.PrintKeyValue("pciFunction", obj.pciFunction, 11);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceFragmentDensityMapFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceFragmentDensityMapFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("fragmentDensityMap", static_cast<bool>(obj.fragmentDensityMap), 37);
     p.PrintKeyBool("fragmentDensityMapDynamic", static_cast<bool>(obj.fragmentDensityMapDynamic), 37);
     p.PrintKeyBool("fragmentDensityMapNonSubsampledImages", static_cast<bool>(obj.fragmentDensityMapNonSubsampledImages), 37);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceFragmentDensityMapPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceFragmentDensityMapPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     DumpVkExtent2D(p, "minFragmentDensityTexelSize", obj.minFragmentDensityTexelSize);
     DumpVkExtent2D(p, "maxFragmentDensityTexelSize", obj.maxFragmentDensityTexelSize);
     p.PrintKeyBool("fragmentDensityInvocations", static_cast<bool>(obj.fragmentDensityInvocations), 26);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSubgroupSizeControlFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceSubgroupSizeControlFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("subgroupSizeControl", static_cast<bool>(obj.subgroupSizeControl), 20);
     p.PrintKeyBool("computeFullSubgroups", static_cast<bool>(obj.computeFullSubgroups), 20);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceSubgroupSizeControlPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceSubgroupSizeControlPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("minSubgroupSize", obj.minSubgroupSize, 28);
     p.PrintKeyValue("maxSubgroupSize", obj.maxSubgroupSize, 28);
     p.PrintKeyValue("maxComputeWorkgroupSubgroups", obj.maxComputeWorkgroupSubgroups, 28);
     DumpVkShaderStageFlags(p, "requiredSubgroupSizeStages", obj.requiredSubgroupSizeStages, 28);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceMemoryBudgetPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceMemoryBudgetPropertiesEXT &obj) {
-    p.ObjectStart(name);
-    p.ArrayStart("heapBudget", 16);
-    p.PrintElement(obj.heapBudget[0]);
-    p.PrintElement(obj.heapBudget[1]);
-    p.PrintElement(obj.heapBudget[2]);
-    p.PrintElement(obj.heapBudget[3]);
-    p.PrintElement(obj.heapBudget[4]);
-    p.PrintElement(obj.heapBudget[5]);
-    p.PrintElement(obj.heapBudget[6]);
-    p.PrintElement(obj.heapBudget[7]);
-    p.PrintElement(obj.heapBudget[8]);
-    p.PrintElement(obj.heapBudget[9]);
-    p.PrintElement(obj.heapBudget[10]);
-    p.PrintElement(obj.heapBudget[11]);
-    p.PrintElement(obj.heapBudget[12]);
-    p.PrintElement(obj.heapBudget[13]);
-    p.PrintElement(obj.heapBudget[14]);
-    p.PrintElement(obj.heapBudget[15]);
-    p.ArrayEnd();
-    p.ArrayStart("heapUsage", 16);
-    p.PrintElement(obj.heapUsage[0]);
-    p.PrintElement(obj.heapUsage[1]);
-    p.PrintElement(obj.heapUsage[2]);
-    p.PrintElement(obj.heapUsage[3]);
-    p.PrintElement(obj.heapUsage[4]);
-    p.PrintElement(obj.heapUsage[5]);
-    p.PrintElement(obj.heapUsage[6]);
-    p.PrintElement(obj.heapUsage[7]);
-    p.PrintElement(obj.heapUsage[8]);
-    p.PrintElement(obj.heapUsage[9]);
-    p.PrintElement(obj.heapUsage[10]);
-    p.PrintElement(obj.heapUsage[11]);
-    p.PrintElement(obj.heapUsage[12]);
-    p.PrintElement(obj.heapUsage[13]);
-    p.PrintElement(obj.heapUsage[14]);
-    p.PrintElement(obj.heapUsage[15]);
-    p.ArrayEnd();
-    p.ObjectEnd();
+    ObjectWrapper object{p, name};
+    {   ArrayWrapper arr(p,"heapBudget", 16);
+        p.PrintElement(obj.heapBudget[0]);
+        p.PrintElement(obj.heapBudget[1]);
+        p.PrintElement(obj.heapBudget[2]);
+        p.PrintElement(obj.heapBudget[3]);
+        p.PrintElement(obj.heapBudget[4]);
+        p.PrintElement(obj.heapBudget[5]);
+        p.PrintElement(obj.heapBudget[6]);
+        p.PrintElement(obj.heapBudget[7]);
+        p.PrintElement(obj.heapBudget[8]);
+        p.PrintElement(obj.heapBudget[9]);
+        p.PrintElement(obj.heapBudget[10]);
+        p.PrintElement(obj.heapBudget[11]);
+        p.PrintElement(obj.heapBudget[12]);
+        p.PrintElement(obj.heapBudget[13]);
+        p.PrintElement(obj.heapBudget[14]);
+        p.PrintElement(obj.heapBudget[15]);
+    }
+    {   ArrayWrapper arr(p,"heapUsage", 16);
+        p.PrintElement(obj.heapUsage[0]);
+        p.PrintElement(obj.heapUsage[1]);
+        p.PrintElement(obj.heapUsage[2]);
+        p.PrintElement(obj.heapUsage[3]);
+        p.PrintElement(obj.heapUsage[4]);
+        p.PrintElement(obj.heapUsage[5]);
+        p.PrintElement(obj.heapUsage[6]);
+        p.PrintElement(obj.heapUsage[7]);
+        p.PrintElement(obj.heapUsage[8]);
+        p.PrintElement(obj.heapUsage[9]);
+        p.PrintElement(obj.heapUsage[10]);
+        p.PrintElement(obj.heapUsage[11]);
+        p.PrintElement(obj.heapUsage[12]);
+        p.PrintElement(obj.heapUsage[13]);
+        p.PrintElement(obj.heapUsage[14]);
+        p.PrintElement(obj.heapUsage[15]);
+    }
 }
 void DumpVkPhysicalDeviceMemoryPriorityFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceMemoryPriorityFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("memoryPriority", static_cast<bool>(obj.memoryPriority), 14);
-    p.ObjectEnd();
 }
 void DumpVkSurfaceProtectedCapabilitiesKHR(Printer &p, std::string name, VkSurfaceProtectedCapabilitiesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("supportsProtected", static_cast<bool>(obj.supportsProtected), 17);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceBufferDeviceAddressFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceBufferDeviceAddressFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("bufferDeviceAddress", static_cast<bool>(obj.bufferDeviceAddress), 32);
     p.PrintKeyBool("bufferDeviceAddressCaptureReplay", static_cast<bool>(obj.bufferDeviceAddressCaptureReplay), 32);
     p.PrintKeyBool("bufferDeviceAddressMultiDevice", static_cast<bool>(obj.bufferDeviceAddressMultiDevice), 32);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceToolPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceToolPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyString("name", obj.name, 16);
     p.PrintKeyString("version", obj.version, 16);
     DumpVkToolPurposeFlagsEXT(p, "purposes", obj.purposes, 16);
     p.PrintKeyString("description", obj.description, 16);
     p.PrintKeyString("layer", obj.layer, 16);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceFragmentShaderInterlockFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("fragmentShaderSampleInterlock", static_cast<bool>(obj.fragmentShaderSampleInterlock), 34);
     p.PrintKeyBool("fragmentShaderPixelInterlock", static_cast<bool>(obj.fragmentShaderPixelInterlock), 34);
     p.PrintKeyBool("fragmentShaderShadingRateInterlock", static_cast<bool>(obj.fragmentShaderShadingRateInterlock), 34);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceYcbcrImageArraysFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceYcbcrImageArraysFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("ycbcrImageArrays", static_cast<bool>(obj.ycbcrImageArrays), 16);
-    p.ObjectEnd();
 }
 #ifdef VK_USE_PLATFORM_WIN32_KHR
 void DumpVkSurfaceCapabilitiesFullScreenExclusiveEXT(Printer &p, std::string name, VkSurfaceCapabilitiesFullScreenExclusiveEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("fullScreenExclusiveSupported", static_cast<bool>(obj.fullScreenExclusiveSupported), 28);
-    p.ObjectEnd();
 }
 #endif  // VK_USE_PLATFORM_WIN32_KHR
 void DumpVkPhysicalDeviceLineRasterizationFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceLineRasterizationFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("rectangularLines", static_cast<bool>(obj.rectangularLines), 24);
     p.PrintKeyBool("bresenhamLines", static_cast<bool>(obj.bresenhamLines), 24);
     p.PrintKeyBool("smoothLines", static_cast<bool>(obj.smoothLines), 24);
     p.PrintKeyBool("stippledRectangularLines", static_cast<bool>(obj.stippledRectangularLines), 24);
     p.PrintKeyBool("stippledBresenhamLines", static_cast<bool>(obj.stippledBresenhamLines), 24);
     p.PrintKeyBool("stippledSmoothLines", static_cast<bool>(obj.stippledSmoothLines), 24);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceLineRasterizationPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceLineRasterizationPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("lineSubPixelPrecisionBits", obj.lineSubPixelPrecisionBits, 25);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceIndexTypeUint8FeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceIndexTypeUint8FeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("indexTypeUint8", static_cast<bool>(obj.indexTypeUint8), 14);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR(Printer &p, std::string name, VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("pipelineExecutableInfo", static_cast<bool>(obj.pipelineExecutableInfo), 22);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("shaderDemoteToHelperInvocation", static_cast<bool>(obj.shaderDemoteToHelperInvocation), 30);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTexelBufferAlignmentFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("texelBufferAlignment", static_cast<bool>(obj.texelBufferAlignment), 20);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTexelBufferAlignmentPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("storageTexelBufferOffsetAlignmentBytes", to_hex_str(p, obj.storageTexelBufferOffsetAlignmentBytes), 44);
     p.PrintKeyBool("storageTexelBufferOffsetSingleTexelAlignment", static_cast<bool>(obj.storageTexelBufferOffsetSingleTexelAlignment), 44);
     p.PrintKeyValue("uniformTexelBufferOffsetAlignmentBytes", to_hex_str(p, obj.uniformTexelBufferOffsetAlignmentBytes), 44);
     p.PrintKeyBool("uniformTexelBufferOffsetSingleTexelAlignment", static_cast<bool>(obj.uniformTexelBufferOffsetSingleTexelAlignment), 44);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTransformFeedbackFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceTransformFeedbackFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("transformFeedback", static_cast<bool>(obj.transformFeedback), 17);
     p.PrintKeyBool("geometryStreams", static_cast<bool>(obj.geometryStreams), 17);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTransformFeedbackPropertiesEXT(Printer &p, std::string name, VkPhysicalDeviceTransformFeedbackPropertiesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxTransformFeedbackStreams", obj.maxTransformFeedbackStreams, 42);
     p.PrintKeyValue("maxTransformFeedbackBuffers", obj.maxTransformFeedbackBuffers, 42);
     p.PrintKeyValue("maxTransformFeedbackBufferSize", to_hex_str(p, obj.maxTransformFeedbackBufferSize), 42);
@@ -1729,28 +1762,23 @@ void DumpVkPhysicalDeviceTransformFeedbackPropertiesEXT(Printer &p, std::string
     p.PrintKeyBool("transformFeedbackStreamsLinesTriangles", static_cast<bool>(obj.transformFeedbackStreamsLinesTriangles), 42);
     p.PrintKeyBool("transformFeedbackRasterizationStreamSelect", static_cast<bool>(obj.transformFeedbackRasterizationStreamSelect), 42);
     p.PrintKeyBool("transformFeedbackDraw", static_cast<bool>(obj.transformFeedbackDraw), 42);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("textureCompressionASTC_HDR", static_cast<bool>(obj.textureCompressionASTC_HDR), 26);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceASTCDecodeFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceASTCDecodeFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("decodeModeSharedExponent", static_cast<bool>(obj.decodeModeSharedExponent), 24);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDevicePushDescriptorPropertiesKHR(Printer &p, std::string name, VkPhysicalDevicePushDescriptorPropertiesKHR &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyValue("maxPushDescriptors", obj.maxPushDescriptors, 18);
-    p.ObjectEnd();
 }
 void DumpVkPhysicalDeviceConditionalRenderingFeaturesEXT(Printer &p, std::string name, VkPhysicalDeviceConditionalRenderingFeaturesEXT &obj) {
-    p.ObjectStart(name);
+    ObjectWrapper object{p, name};
     p.PrintKeyBool("conditionalRendering", static_cast<bool>(obj.conditionalRendering), 29);
     p.PrintKeyBool("inheritedConditionalRendering", static_cast<bool>(obj.inheritedConditionalRendering), 29);
-    p.ObjectEnd();
 }
 pNextChainInfos get_chain_infos() {
     pNextChainInfos infos;
index c5df2c2..80318e4 100644 (file)
@@ -63,7 +63,7 @@ std::string VkVersionString(VulkanVersion v) {
     return std::to_string(v.major) + "." + std::to_string(v.minor) + "." + std::to_string(v.patch);
 }
 
-enum class OutputType { text, html, json };
+enum class OutputType { text, html, json, vkconfig_output };
 
 class Printer {
    public:
@@ -182,6 +182,12 @@ class Printer {
                 indents++;
                 is_first_item.push(false);
                 break;
+            case (OutputType::vkconfig_output):
+                out << "{\n";
+                out << "\t\"Vulkan Instance Version\": \"" << VkVersionString(vulkan_version) << "\"";
+                indents++;
+                is_first_item.push(false);
+                break;
             default:
                 break;
         }
@@ -198,6 +204,7 @@ class Printer {
                 indents -= 3;
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 out << "\n}\n";
                 indents--;
                 is_first_item.pop();
@@ -246,17 +253,24 @@ class Printer {
         return *this;
     }
 
-    void ObjectStart(std::string object_name) {
+    void ObjectStart(std::string object_name, int32_t count_subobjects = -1) {
         switch (output_type) {
             case (OutputType::text): {
                 out << std::string(static_cast<size_t>(indents), '\t') << object_name;
                 if (element_index != -1) {
                     out << "[" << element_index << "]";
                 }
-                out << ":\n";
+                out << ":";
+                if (count_subobjects >= 0) {
+                    out << " count = " << count_subobjects;
+                }
+                out << "\n";
                 size_t headersize = object_name.size() + 1;
+                if (count_subobjects >= 0) {
+                    headersize += 9 + std::to_string(count_subobjects).size();
+                }
                 if (element_index != -1) {
-                    headersize += 1 + std::to_string(element_index).size();
+                    headersize += 2 + std::to_string(element_index).size();
                     element_index = -1;
                 }
                 PrintHeaderUnderlines(headersize);
@@ -281,6 +295,9 @@ class Printer {
                     out << "[<span class='val'>" << element_index << "</span>]";
                     element_index = -1;
                 }
+                if (count_subobjects >= 0) {
+                    out << ": count = <span class='val'>" << std::to_string(count_subobjects) << "</span>";
+                }
                 out << "</summary>\n";
                 break;
             case (OutputType::json):
@@ -300,6 +317,22 @@ class Printer {
 
                 is_first_item.push(true);
                 break;
+            case (OutputType::vkconfig_output):
+                if (!is_first_item.top()) {
+                    out << ",\n";
+                } else {
+                    is_first_item.top() = false;
+                }
+                out << std::string(static_cast<size_t>(indents), '\t');
+
+                if (element_index != -1) {
+                    out << "\"" << object_name << "[" << element_index << "]\": {\n";
+                    element_index = -1;
+                } else {
+                    out << "\"" << object_name << "\": {\n";
+                }
+                is_first_item.push(true);
+                break;
             default:
                 break;
         }
@@ -316,6 +349,7 @@ class Printer {
                 out << std::string(static_cast<size_t>(indents), '\t') << "</details>\n";
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 out << "\n" << std::string(static_cast<size_t>(indents), '\t') << "}";
                 is_first_item.pop();
                 break;
@@ -323,13 +357,19 @@ class Printer {
                 break;
         }
     }
-    void ArrayStart(std::string array_name, size_t element_count = 0) {
+    void ArrayStart(std::string array_name, int32_t element_count = 0) {
         switch (output_type) {
-            case (OutputType::text):
-                out << std::string(static_cast<size_t>(indents), '\t') << array_name << ": "
-                    << "count = " << element_count << "\n";
-                PrintHeaderUnderlines(array_name.size() + 1);
+            case (OutputType::text): {
+                out << std::string(static_cast<size_t>(indents), '\t') << array_name << ":";
+                size_t underline_count = array_name.size() + 1;
+                if (element_count >= 0) {
+                    out << " count = " << element_count;
+                    underline_count += 9 + std::to_string(element_count).size();
+                }
+                out << "\n";
+                PrintHeaderUnderlines(underline_count);
                 break;
+            }
             case (OutputType::html):
                 out << std::string(static_cast<size_t>(indents), '\t');
                 if (set_details_open) {
@@ -338,9 +378,14 @@ class Printer {
                 } else {
                     out << "<details>";
                 }
-                out << "<summary>" << array_name << ": count = <span class='val'>" << element_count << "</span></summary>\n";
+                out << "<summary>" << array_name;
+                if (element_count >= 0) {
+                    out << ": count = <span class='val'>" << element_count << "</span>";
+                }
+                out << "</summary>\n";
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 if (!is_first_item.top()) {
                     out << ",\n";
                 } else {
@@ -366,6 +411,7 @@ class Printer {
                 out << std::string(static_cast<size_t>(indents), '\t') << "</details>\n";
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 out << "\n" << std::string(static_cast<size_t>(indents), '\t') << "]";
                 is_first_item.pop();
                 break;
@@ -415,6 +461,19 @@ class Printer {
                     is_first_item.top() = false;
                 }
                 out << std::string(static_cast<size_t>(indents), '\t') << "\"" << key << "\": " << value;
+                break;
+            case (OutputType::vkconfig_output):
+                if (!is_first_item.top()) {
+                    out << ",\n";
+                } else {
+                    is_first_item.top() = false;
+                }
+                out << std::string(static_cast<size_t>(indents), '\t') << "\"" << key << "\": ";
+                if (value_description != "") {
+                    out << "\"" << value << " (" << value_description << ")\"";
+                } else {
+                    out << value;
+                }
             default:
                 break;
         }
@@ -428,6 +487,7 @@ class Printer {
                 PrintKeyValue(key, value, min_key_width, value_description);
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 PrintKeyValue(key, std::string("\"") + value + "\"", min_key_width, value_description);
                 break;
             default:
@@ -440,6 +500,7 @@ class Printer {
         switch (output_type) {
             case (OutputType::text):
             case (OutputType::html):
+            case (OutputType::vkconfig_output):
                 PrintKeyValue(key, value ? "true" : "false", min_key_width, value_description);
                 break;
             case (OutputType::json):
@@ -475,6 +536,7 @@ class Printer {
                 out << "</summary></details>\n";
                 break;
             case (OutputType::json):
+            case (OutputType::vkconfig_output):
                 if (!is_first_item.top()) {
                     out << ",\n";
                 } else {
@@ -486,6 +548,19 @@ class Printer {
                 break;
         }
     }
+    void PrintString(std::string string, std::string value_description = "") {
+        switch (output_type) {
+            case (OutputType::text):
+            case (OutputType::html):
+                PrintElement(string, value_description);
+                break;
+            case (OutputType::json):
+            case (OutputType::vkconfig_output):
+                PrintElement("\"" + string + "\"", value_description);
+            default:
+                break;
+        }
+    }
     void PrintExtension(std::string ext_name, uint32_t revision, int min_width = 0) {
         switch (output_type) {
             case (OutputType::text):
@@ -493,12 +568,16 @@ class Printer {
                     << " : extension revision " << revision << "\n";
                 break;
             case (OutputType::html):
-                out << std::string(static_cast<size_t>(indents), '\t') << "<details><summary><span class='type'>" << ext_name
-                    << "</span>" << std::string(min_width - ext_name.size(), ' ') << " : extension revision <span class='val'>"
-                    << revision << "</span></summary></details>\n";
+                out << std::string(static_cast<size_t>(indents), '\t') << "<details><summary>" << DecorateAsType(ext_name)
+                    << std::string(min_width - ext_name.size(), ' ') << " : extension revision "
+                    << DecorateAsValue(std::to_string(revision)) << "</summary></details>\n";
                 break;
             case (OutputType::json):
-
+                break;
+            case (OutputType::vkconfig_output):
+                ObjectStart(ext_name);
+                PrintKeyValue("specVersion", revision);
+                ObjectEnd();
                 break;
             default:
                 break;
@@ -521,6 +600,20 @@ class Printer {
         }
     }
 
+    std::string DecorateAsType(const std::string &input) {
+        if (output_type == OutputType::html)
+            return "<span class='type'>" + input + "</span>";
+        else
+            return input;
+    }
+
+    std::string DecorateAsValue(const std::string &input) {
+        if (output_type == OutputType::html)
+            return "<span class='val'>" + input + "</span>";
+        else
+            return input;
+    }
+
    protected:
     OutputType output_type;
     std::ostream &out;
@@ -558,3 +651,23 @@ class Printer {
         }
     }
 };
+
+class ObjectWrapper {
+   public:
+    ObjectWrapper(Printer &p, std::string object_name, int32_t count_subobjects = -1) : p(p) {
+        p.ObjectStart(object_name, count_subobjects);
+    }
+    ~ObjectWrapper() { p.ObjectEnd(); }
+
+   private:
+    Printer &p;
+};
+
+class ArrayWrapper {
+   public:
+    ArrayWrapper(Printer &p, std::string array_name, int32_t element_count = 0) : p(p) { p.ArrayStart(array_name, element_count); }
+    ~ArrayWrapper() { p.ArrayEnd(); }
+
+   private:
+    Printer &p;
+};
\ No newline at end of file
index 7068548..a0b843a 100644 (file)
@@ -57,11 +57,10 @@ void DumpExtensions(Printer &p, std::string layer_name, std::vector<VkExtensionP
         }
     }
 
-    p.ArrayStart(layer_name + " Extensions", extensions.size());
+    ObjectWrapper obj(p, layer_name + " Extensions", extensions.size());
     for (auto &ext : extensions) {
         p.PrintExtension(ext.extensionName, ext.specVersion, max_length);
     }
-    p.ArrayEnd();
 }
 
 void DumpLayers(Printer &p, std::vector<LayerExtensionList> layers, const std::vector<std::unique_ptr<AppGpu>> &gpus) {
@@ -70,74 +69,89 @@ void DumpLayers(Printer &p, std::vector<LayerExtensionList> layers, const std::v
         const char *b = right.layer_properties.layerName;
         return a && (!b || std::strcmp(a, b) < 0);
     });
+    switch (p.Type()) {
+        case OutputType::text:
+        case OutputType::html: {
+            p.SetHeader();
+            ArrayWrapper arr(p, "Layers", layers.size());
+            p.IndentDecrease();
+            for (auto &layer : layers) {
+                auto v_str = VkVersionString(layer.layer_properties.specVersion);
+                auto props = layer.layer_properties;
+
+                std::string header = p.DecorateAsType(props.layerName) + " (" + props.description + ") Vulkan version " +
+                                     p.DecorateAsValue(v_str) + ", layer version " +
+                                     p.DecorateAsValue(std::to_string(props.implementationVersion));
+                ObjectWrapper obj(p, header);
+                DumpExtensions(p, "Layer", layer.extension_properties);
+
+                ArrayWrapper arr(p, "Devices", gpus.size());
+                for (auto &gpu : gpus) {
+                    p.PrintKeyValue("GPU id", gpu->id, 0, gpu->props.deviceName);
+                    auto exts = gpu->AppGetPhysicalDeviceLayerExtensions(props.layerName);
+                    DumpExtensions(p, "Layer-Device", exts);
+                    p.AddNewline();
+                }
+            }
+            p.IndentIncrease();
+            break;
+        }
 
-    if (p.Type() == OutputType::text || p.Type() == OutputType::html) {
-        p.SetHeader().ArrayStart("Layers", layers.size());
-        p.IndentDecrease();
-        for (auto &layer : layers) {
-            auto v_str = VkVersionString(layer.layer_properties.specVersion);
-            auto props = layer.layer_properties;
-
-            std::string header;
-            if (p.Type() == OutputType::text)
-                header = std::string(props.layerName) + " (" + props.description + ") Vulkan version " + v_str +
-                         ", layer version " + std::to_string(props.implementationVersion);
-            else if (p.Type() == OutputType::html)
-                header = std::string("<span class='type'>") + props.layerName + "</span> (" + props.description +
-                         ") Vulkan version <span class='val'>" + v_str + "</span>, layer version <span class='val'>" +
-                         std::to_string(props.implementationVersion) + "</span>";
-
-            p.ObjectStart(header);
-            DumpExtensions(p, "Layer", layer.extension_properties);
-
-            p.ArrayStart("Devices", gpus.size());
-            for (auto &gpu : gpus) {
-                p.PrintElement(std::string("GPU id \t: ") + std::to_string(gpu->id), gpu->props.deviceName);
-                auto exts = gpu->AppGetPhysicalDeviceLayerExtensions(props.layerName);
-                DumpExtensions(p, "Layer-Device", exts);
-                p.AddNewline();
+        case OutputType::json: {
+            ArrayWrapper arr(p, "ArrayOfVkLayerProperties", layers.size());
+            int i = 0;
+            for (auto &layer : layers) {
+                p.SetElementIndex(i++);
+                DumpVkLayerProperties(p, "layerProperty", layer.layer_properties);
             }
-            p.ArrayEnd();
-            p.ObjectEnd();
+            break;
         }
-        p.IndentIncrease();
-        p.ArrayEnd();
-    } else if (p.Type() == OutputType::json) {
-        p.ArrayStart("ArrayOfVkLayerProperties", layers.size());
-        int i = 0;
-        for (auto &layer : layers) {
-            p.SetElementIndex(i++);
-            DumpVkLayerProperties(p, "layerProperty", layer.layer_properties);
+        case OutputType::vkconfig_output: {
+            ObjectWrapper obj(p, "Layer Properties");
+            for (auto &layer : layers) {
+                ObjectWrapper obj_name(p, layer.layer_properties.layerName);
+                p.PrintKeyString("layerName", layer.layer_properties.layerName, 21);
+                p.PrintKeyString("version", VkVersionString(layer.layer_properties.specVersion), 21);
+                p.PrintKeyValue("implementation version", layer.layer_properties.implementationVersion, 21);
+                p.PrintKeyString("description", layer.layer_properties.description, 21);
+                DumpExtensions(p, "Layer", layer.extension_properties);
+                ObjectWrapper obj_devices(p, "Devices");
+                for (auto &gpu : gpus) {
+                    ObjectWrapper obj(p, gpu->props.deviceName);
+                    p.PrintKeyValue("GPU id", gpu->id, 0, gpu->props.deviceName);
+                    auto exts = gpu->AppGetPhysicalDeviceLayerExtensions(layer.layer_properties.layerName);
+                    DumpExtensions(p, "Layer-Device", exts);
+                }
+            }
+            break;
         }
-        p.ArrayEnd();
     }
 }
 
 void DumpSurfaceFormats(Printer &p, AppInstance &inst, AppSurface &surface) {
-    int i = 0;
+    std::vector<VkSurfaceFormatKHR> formats;
     if (inst.CheckExtensionEnabled(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME)) {
-        p.ArrayStart("Formats", surface.surf_formats2.size());
         for (auto &format : surface.surf_formats2) {
-            p.SetElementIndex(i++);
-            DumpVkSurfaceFormatKHR(p, "SurfaceFormat", format.surfaceFormat);
+            formats.push_back(format.surfaceFormat);
         }
-        p.ArrayEnd();
     } else {
-        p.ArrayStart("Formats", surface.surf_formats.size());
         for (auto &format : surface.surf_formats) {
-            p.SetElementIndex(i++);
-            DumpVkSurfaceFormatKHR(p, "SurfaceFormat", format);
+            formats.push_back(format);
         }
-        p.ArrayEnd();
+    }
+    ObjectWrapper obj(p, "Formats", formats.size());
+    int i = 0;
+    for (auto &format : formats) {
+        p.SetElementIndex(i++);
+        DumpVkSurfaceFormatKHR(p, "SurfaceFormat", format);
     }
 }
 
 void DumpPresentModes(Printer &p, AppSurface &surface) {
-    p.ArrayStart("Present Modes", surface.surf_present_modes.size());
+    ArrayWrapper arr(p, "Present Modes", surface.surf_present_modes.size());
     for (auto &mode : surface.surf_present_modes) {
-        p.SetAsType().PrintElement(VkPresentModeKHRString(mode));
+        p.SetAsType().PrintString(VkPresentModeKHRString(mode));
     }
-    p.ArrayEnd();
 }
 
 void DumpSurfaceCapabilities(Printer &p, AppInstance &inst, AppGpu &gpu, AppSurface &surface) {
@@ -146,16 +160,15 @@ void DumpSurfaceCapabilities(Printer &p, AppInstance &inst, AppGpu &gpu, AppSurf
     DumpVkSurfaceCapabilitiesKHR(p, "VkSurfaceCapabilitiesKHR", surf_cap);
 
     if (inst.CheckExtensionEnabled(VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME)) {
-        p.SetSubHeader().ObjectStart("VkSurfaceCapabilities2EXT");
+        p.SetSubHeader();
+        ObjectWrapper obj(p, "VkSurfaceCapabilities2EXT");
         {
-            p.ObjectStart("supportedSurfaceCounters");
-            if (surface.surface_capabilities2_ext.supportedSurfaceCounters == 0) p.PrintElement("None");
+            ArrayWrapper arr(p, "supportedSurfaceCounters");
+            if (surface.surface_capabilities2_ext.supportedSurfaceCounters == 0) p.PrintString("None");
             if (surface.surface_capabilities2_ext.supportedSurfaceCounters & VK_SURFACE_COUNTER_VBLANK_EXT) {
-                p.SetAsType().PrintElement("VK_SURFACE_COUNTER_VBLANK_EXT");
+                p.SetAsType().PrintString("VK_SURFACE_COUNTER_VBLANK_EXT");
             }
-            p.ObjectEnd();
         }
-        p.ObjectEnd();  // VkSurfaceCapabilities2EXT
     }
     if (inst.CheckExtensionEnabled(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME)) {
         chain_iterator_surface_capabilities2(p, inst, gpu, surface.surface_capabilities2_khr.pNext, inst.vk_version);
@@ -163,30 +176,23 @@ void DumpSurfaceCapabilities(Printer &p, AppInstance &inst, AppGpu &gpu, AppSurf
 }
 
 void DumpSurface(Printer &p, AppInstance &inst, AppGpu &gpu, AppSurface &surface, std::set<std::string> surface_types) {
-    std::string header;
-    if (p.Type() == OutputType::text)
-        header = std::string("GPU id : ") + std::to_string(gpu.id) + " (" + gpu.props.deviceName + ")";
-    else if (p.Type() == OutputType::html)
-        header = std::string("GPU id : <span class='val'>") + std::to_string(gpu.id) + "</span> (" + gpu.props.deviceName + ")";
-    p.ObjectStart(header);
+    ObjectWrapper obj(p, std::string("GPU id : ") + p.DecorateAsValue(std::to_string(gpu.id)) + " (" + gpu.props.deviceName + ")");
 
     if (surface_types.size() == 0) {
-        p.SetAsType().PrintKeyValue("Surface type", surface.surface_extension.name);
+        p.SetAsType().PrintKeyString("Surface type", "No type found");
+    } else if (surface_types.size() == 1) {
+        p.SetAsType().PrintKeyString("Surface type", surface.surface_extension.name);
     } else {
-        p.ArrayStart("Surface types", surface_types.size());
+        ArrayWrapper arr(p, "Surface types", surface_types.size());
         for (auto &name : surface_types) {
-            p.PrintElement(name);
+            p.PrintString(name);
         }
-        p.ArrayEnd();
     }
 
     DumpSurfaceFormats(p, inst, surface);
-
     DumpPresentModes(p, surface);
-
     DumpSurfaceCapabilities(p, inst, gpu, surface);
 
-    p.ObjectEnd();
     p.AddNewline();
 }
 
@@ -204,8 +210,10 @@ bool operator==(AppSurface const &a, AppSurface const &b) {
 
 void DumpPresentableSurfaces(Printer &p, AppInstance &inst, const std::vector<std::unique_ptr<AppGpu>> &gpus,
                              const std::vector<std::unique_ptr<AppSurface>> &surfaces) {
-    p.SetHeader().ObjectStart("Presentable Surfaces");
+    p.SetHeader();
+    ObjectWrapper obj(p, "Presentable Surfaces");
     p.IndentDecrease();
+
     std::vector<SurfaceTypeGroup> surface_list;
 
     for (auto &surface : surfaces) {
@@ -233,7 +241,6 @@ void DumpPresentableSurfaces(Printer &p, AppInstance &inst, const std::vector<st
         DumpSurface(p, inst, *group.gpu, *group.surface, group.surface_types);
     }
     p.IndentIncrease();
-    p.ObjectEnd();
     p.AddNewline();
 }
 
@@ -241,157 +248,159 @@ void DumpGroups(Printer &p, AppInstance &inst) {
     if (inst.CheckExtensionEnabled(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME)) {
         auto groups = GetGroups(inst);
         if (groups.size() == 0) {
-            p.SetHeader().ObjectStart("Groups");
-            p.PrintElement("No Device Groups Found");
-            p.ObjectEnd();
+            p.SetHeader();
+            ObjectWrapper obj(p, "Groups");
+            p.PrintString("No Device Groups Found");
             p.AddNewline();
             return;
         }
-        p.SetHeader().ObjectStart("Groups");
+
+        p.SetHeader();
+        ObjectWrapper obj(p, "Device Groups");
+        p.IndentDecrease();
         int group_id = 0;
         for (auto &group : groups) {
-            p.ObjectStart("Device Group Properties (Group " + std::to_string(group_id) + ")");
+            ObjectWrapper obj(p, "Group " + std::to_string(group_id));
             auto group_props = GetGroupProps(group);
-            p.ArrayStart("physicalDeviceCount", group.physicalDeviceCount);
-            int id = 0;
-            for (auto &prop : group_props) {
-                std::string device_out = prop.deviceName;
-                if (p.Type() == OutputType::text) {
-                    device_out += " (ID: " + std::to_string(id++) + ")";
-                } else if (p.Type() == OutputType::html) {
-                    device_out += " (ID: <span class='val'>" + std::to_string(id++) + "</span>)";
+            {
+                ObjectWrapper obj(p, "Properties");
+                {
+                    ArrayWrapper arr(p, "physicalDevices", group.physicalDeviceCount);
+                    int id = 0;
+                    for (auto &prop : group_props) {
+                        p.PrintString(std::string(prop.deviceName) + " (ID: " + p.DecorateAsValue(std::to_string(id++)) + ")");
+                    }
                 }
-                p.PrintElement(device_out);
+                p.PrintKeyValue("subsetAllocation", group.subsetAllocation);
             }
-            p.ArrayEnd();
-            p.PrintKeyValue("subsetAllocation", group.subsetAllocation);
-            p.ObjectEnd();
             p.AddNewline();
 
-            p.ObjectStart("Device Group Present Capabilities (Group " + std::to_string(group_id) + ")");
             auto group_capabilities = GetGroupCapabilities(inst, group);
             if (group_capabilities.first == false) {
-                p.PrintElement("Group does not support VK_KHR_device_group, skipping printing capabilities");
+                p.PrintKeyString("Present Capabilities",
+                                 "Group does not support VK_KHR_device_group, skipping printing present capabilities");
             } else {
+                ObjectWrapper obj(p, "Present Capabilities");
                 for (uint32_t i = 0; i < group.physicalDeviceCount; i++) {
-                    std::string device_out;
-                    if (p.Type() == OutputType::text) {
-                        device_out = std::string(group_props[i].deviceName) + " (ID: " + std::to_string(i) + ")";
-                    } else if (p.Type() == OutputType::html) {
-                        device_out =
-                            std::string(group_props[i].deviceName) + " (ID: <span class='val'>" + std::to_string(i) + "</span>)";
-                    }
-                    p.PrintElement(device_out);
-                    p.ObjectStart("Can present images from the following devices");
+                    ObjectWrapper obj(
+                        p, std::string(group_props[i].deviceName) + " (ID: " + p.DecorateAsValue(std::to_string(i)) + ")");
+                    ArrayWrapper arr(p, "Can present images from the following devices", group.physicalDeviceCount);
+
                     for (uint32_t j = 0; j < group.physicalDeviceCount; j++) {
-                        uint32_t mask = 1U << j;
+                        uint32_t mask = 1 << j;
                         if (group_capabilities.second.presentMask[i] & mask) {
-                            if (p.Type() == OutputType::text)
-                                p.PrintElement(std::string(group_props[j].deviceName) + " (ID: " + std::to_string(j) + ")");
-                            if (p.Type() == OutputType::html)
-                                p.PrintElement(std::string(group_props[j].deviceName) + " (ID: <span class='val'>" +
-                                               std::to_string(j) + "</span>)");
+                            p.PrintString(std::string(group_props[j].deviceName) + " (ID: " + p.DecorateAsValue(std::to_string(j)) +
+                                          ")");
                         }
                     }
-                    p.ObjectEnd();
                 }
                 DumpVkDeviceGroupPresentModeFlagsKHR(p, "Present modes", group_capabilities.second.modes);
             }
-            p.ObjectEnd();
             p.AddNewline();
             group_id++;
         }
-        p.ObjectEnd();
+        p.IndentIncrease();
         p.AddNewline();
     }
 }
 
 void GpuDumpProps(Printer &p, AppGpu &gpu) {
     auto props = gpu.GetDeviceProperties();
-    p.SetSubHeader().ObjectStart("VkPhysicalDeviceProperties");
-    p.PrintKeyValue("apiVersion", props.apiVersion, 14, VkVersionString(props.apiVersion));
-    p.PrintKeyValue("driverVersion", props.driverVersion, 14, to_hex_str(props.driverVersion));
-    if (p.Type() == OutputType::json) {
-        p.PrintKeyValue("vendorID", props.vendorID, 14);
-        p.PrintKeyValue("deviceID", props.deviceID, 14);
-        p.PrintKeyValue("deviceType", props.deviceType, 14);
-    } else {
-        p.PrintKeyValue("vendorID", to_hex_str(props.vendorID), 14);
-        p.PrintKeyValue("deviceID", to_hex_str(props.deviceID), 14);
+    p.SetSubHeader();
+    {
+        ObjectWrapper obj(p, "VkPhysicalDeviceProperties");
+        p.PrintKeyValue("apiVersion", props.apiVersion, 14, VkVersionString(props.apiVersion));
+        p.PrintKeyValue("driverVersion", props.driverVersion, 14, to_hex_str(props.driverVersion));
+        p.PrintKeyString("vendorID", to_hex_str(props.vendorID), 14);
+        p.PrintKeyString("deviceID", to_hex_str(props.deviceID), 14);
         p.PrintKeyString("deviceType", VkPhysicalDeviceTypeString(props.deviceType), 14);
-    }
-    p.PrintKeyString("deviceName", props.deviceName, 14);
-    if (p.Type() == OutputType::json) {
-        p.ArrayStart("pipelineCacheUUID");
-        for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) {
-            p.PrintElement(static_cast<uint32_t>(props.pipelineCacheUUID[i]));
+        p.PrintKeyString("deviceName", props.deviceName, 14);
+        if (p.Type() == OutputType::vkconfig_output) {
+            ArrayWrapper arr(p, "pipelineCacheUUID", VK_UUID_SIZE);
+            for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) {
+                p.PrintElement(static_cast<uint32_t>(props.pipelineCacheUUID[i]));
+            }
         }
-        p.ArrayEnd();
     }
     p.AddNewline();
-    if (p.Type() != OutputType::json) {
-        p.ObjectEnd();  // limits and sparse props are not sub objects in the text and html output
-    }
-
-    if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
-        DumpVkPhysicalDeviceLimits(p, "VkPhysicalDeviceLimits", gpu.props2.properties.limits);
-    } else {
-        DumpVkPhysicalDeviceLimits(p, "VkPhysicalDeviceLimits", gpu.props.limits);
-    }
+    DumpVkPhysicalDeviceLimits(p, "VkPhysicalDeviceLimits",
+                               gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)
+                                   ? gpu.props2.properties.limits
+                                   : gpu.props.limits);
     p.AddNewline();
-    if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
-        DumpVkPhysicalDeviceSparseProperties(p, "VkPhysicalDeviceSparseProperties", gpu.props2.properties.sparseProperties);
-    } else {
-        DumpVkPhysicalDeviceSparseProperties(p, "VkPhysicalDeviceSparseProperties", gpu.props.sparseProperties);
-    }
+    DumpVkPhysicalDeviceSparseProperties(p, "VkPhysicalDeviceSparseProperties",
+                                         gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)
+                                             ? gpu.props2.properties.sparseProperties
+                                             : gpu.props.sparseProperties);
     p.AddNewline();
-    if (p.Type() == OutputType::json) {
-        p.ObjectEnd();  // limits and sparse props are sub objects in the json output
+    if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
+        void *place = gpu.props2.pNext;
+        chain_iterator_phys_device_props2(p, gpu.inst, gpu, place, gpu.api_version);
+        p.AddNewline();
     }
-
-    if (p.Type() != OutputType::json) {
-        if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
-            void *place = gpu.props2.pNext;
-            chain_iterator_phys_device_props2(p, gpu.inst, gpu, place, gpu.api_version);
+}
+void GpuDumpPropsJson(Printer &p, AppGpu &gpu) {
+    auto props = gpu.GetDeviceProperties();
+    ObjectWrapper obj(p, "VkPhysicalDeviceProperties");
+    p.PrintKeyValue("apiVersion", props.apiVersion, 14, VkVersionString(props.apiVersion));
+    p.PrintKeyValue("driverVersion", props.driverVersion, 14, to_hex_str(props.driverVersion));
+    p.PrintKeyValue("vendorID", props.vendorID, 14);
+    p.PrintKeyValue("deviceID", props.deviceID, 14);
+    p.PrintKeyValue("deviceType", props.deviceType, 14);
+    p.PrintKeyString("deviceName", props.deviceName, 14);
+    {
+        ArrayWrapper arr(p, "pipelineCacheUUID", VK_UUID_SIZE);
+        for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) {
+            p.PrintElement(static_cast<uint32_t>(props.pipelineCacheUUID[i]));
         }
     }
-    p.AddNewline();
+
+    DumpVkPhysicalDeviceLimits(p, "VkPhysicalDeviceLimits",
+                               gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)
+                                   ? gpu.props2.properties.limits
+                                   : gpu.props.limits);
+    DumpVkPhysicalDeviceSparseProperties(p, "VkPhysicalDeviceSparseProperties",
+                                         gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)
+                                             ? gpu.props2.properties.sparseProperties
+                                             : gpu.props.sparseProperties);
 }
+
 void GpuDumpQueueProps(Printer &p, std::vector<SurfaceExtension> &surfaces, AppQueueFamilyProperties &queue) {
-    p.SetElementIndex(static_cast<int>(queue.queue_index)).SetSubHeader().ObjectStart("queueProperties");
-    if (p.Type() == OutputType::json) {
+    p.SetSubHeader().SetElementIndex(static_cast<int>(queue.queue_index));
+    ObjectWrapper obj(p, "queueProperties");
+    if (p.Type() == OutputType::vkconfig_output) {
         DumpVkExtent3D(p, "minImageTransferGranularity", queue.props.minImageTransferGranularity);
     } else {
         p.PrintKeyValue("minImageTransferGranularity", queue.props.minImageTransferGranularity, 27);
     }
     p.PrintKeyValue("queueCount", queue.props.queueCount, 27);
-    if (p.Type() == OutputType::json) {
-        p.PrintKeyValue("queueFlags", queue.props.queueFlags, 27);
-    } else {
-        p.PrintKeyValue("queueFlags", VkQueueFlagsString(queue.props.queueFlags), 27);
-    }
-
+    p.PrintKeyString("queueFlags", VkQueueFlagsString(queue.props.queueFlags), 27);
     p.PrintKeyValue("timestampValidBits", queue.props.timestampValidBits, 27);
 
-    if (p.Type() != OutputType::json) {
-        if (queue.is_present_platform_agnostic) {
-            p.PrintKeyString("present support", queue.platforms_support_present ? "true" : "false");
-        } else {
-            size_t width = 0;
-            for (auto &surface : surfaces) {
-                if (surface.name.size() > width) width = surface.name.size();
-            }
-            p.ObjectStart("present support");
-            for (auto &surface : surfaces) {
-                p.PrintKeyString(surface.name, surface.supports_present ? "true" : "false", width);
-            }
-            p.ObjectEnd();
+    if (queue.is_present_platform_agnostic) {
+        p.PrintKeyString("present support", queue.platforms_support_present ? "true" : "false");
+    } else {
+        size_t width = 0;
+        for (auto &surface : surfaces) {
+            if (surface.name.size() > width) width = surface.name.size();
+        }
+        ObjectWrapper obj(p, "present support");
+        for (auto &surface : surfaces) {
+            p.PrintKeyString(surface.name, surface.supports_present ? "true" : "false", width);
         }
     }
-    p.ObjectEnd();
+
     p.AddNewline();
 }
 
+void GpuDumpQueuePropsJson(Printer &p, std::vector<SurfaceExtension> &surfaces, AppQueueFamilyProperties &queue) {
+    ObjectWrapper obj(p, "");
+    DumpVkExtent3D(p, "minImageTransferGranularity", queue.props.minImageTransferGranularity);
+    p.PrintKeyValue("queueCount", queue.props.queueCount, 27);
+    p.PrintKeyValue("queueFlags", queue.props.queueFlags, 27);
+    p.PrintKeyValue("timestampValidBits", queue.props.timestampValidBits, 27);
+}
+
 // This prints a number of bytes in a human-readable format according to prefixes of the International System of Quantities (ISQ),
 // defined in ISO/IEC 80000. The prefixes used here are not SI prefixes, but rather the binary prefixes based on powers of 1024
 // (kibi-, mebi-, gibi- etc.).
@@ -419,41 +428,39 @@ std::string NumToNiceStr(const size_t sz) {
     return std::string(buf);
 }
 
+std::string append_human_readible(VkDeviceSize memory) {
+    return std::to_string(memory) + " (" + to_hex_str(memory) + ") (" + NumToNiceStr(static_cast<size_t>(memory)) + ")";
+}
+
 void GpuDumpMemoryProps(Printer &p, AppGpu &gpu) {
-    p.SetHeader().ObjectStart("VkPhysicalDeviceMemoryProperties");
+    p.SetHeader();
+    ObjectWrapper obj(p, "VkPhysicalDeviceMemoryProperties");
     p.IndentDecrease();
-    p.ArrayStart("memoryHeaps", gpu.memory_props.memoryHeapCount);
-    for (uint32_t i = 0; i < gpu.memory_props.memoryHeapCount; ++i) {
-        const VkDeviceSize memSize = gpu.memory_props.memoryHeaps[i].size;
-        std::string mem_size_human_readable = NumToNiceStr(static_cast<size_t>(memSize));
-
-        std::string mem_size_str = std::to_string(memSize) + " (" + to_hex_str(memSize) + ") (" + mem_size_human_readable + ")";
-
-        p.SetElementIndex(static_cast<int>(i)).ObjectStart("memoryHeaps");
-        if (p.Type() != OutputType::json) {
-            p.PrintKeyValue("size", mem_size_str, 6);
-            p.PrintKeyValue("budget", gpu.heapBudget[i], 6);
-            p.PrintKeyValue("usage", gpu.heapUsage[i], 6);
+    {
+        ObjectWrapper obj(p, "memoryHeaps", gpu.memory_props.memoryHeapCount);
+
+        for (uint32_t i = 0; i < gpu.memory_props.memoryHeapCount; ++i) {
+            p.SetElementIndex(static_cast<int>(i));
+            ObjectWrapper obj(p, "memoryHeaps");
+
+            p.PrintKeyString("size", append_human_readible(gpu.memory_props.memoryHeaps[i].size), 6);
+            p.PrintKeyString("budget", append_human_readible(gpu.heapBudget[i]), 6);
+            p.PrintKeyString("usage", append_human_readible(gpu.heapUsage[i]), 6);
             DumpVkMemoryHeapFlags(p, "flags", gpu.memory_props.memoryHeaps[i].flags, 6);
-        } else {
-            p.PrintKeyValue("flags", gpu.memory_props.memoryHeaps[i].flags);
-            p.PrintKeyValue("size", memSize);
         }
-        p.ObjectEnd();
     }
-    p.ArrayEnd();
+    {
+        ObjectWrapper obj(p, "memoryTypes", gpu.memory_props.memoryTypeCount);
+
+        for (uint32_t i = 0; i < gpu.memory_props.memoryTypeCount; ++i) {
+            p.SetElementIndex(static_cast<int>(i));
+            ObjectWrapper obj(p, "memoryTypes");
+            p.PrintKeyValue("heapIndex", gpu.memory_props.memoryTypes[i].heapIndex, 13);
 
-    p.ArrayStart("memoryTypes", gpu.memory_props.memoryTypeCount);
-    for (uint32_t i = 0; i < gpu.memory_props.memoryTypeCount; ++i) {
-        p.SetElementIndex(static_cast<int>(i)).ObjectStart("memoryTypes");
-        p.PrintKeyValue("heapIndex", gpu.memory_props.memoryTypes[i].heapIndex, 13);
-        if (p.Type() == OutputType::json) {
-            p.PrintKeyValue("propertyFlags", gpu.memory_props.memoryTypes[i].propertyFlags, 13);
-        } else {
             auto flags = gpu.memory_props.memoryTypes[i].propertyFlags;
             DumpVkMemoryPropertyFlags(p, "propertyFlags = " + to_hex_str(flags), flags);
 
-            p.ObjectStart("usable for");
+            ArrayWrapper arr(p, "usable for", -1);
             const uint32_t memtype_bit = 1U << i;
 
             // only linear and optimal tiling considered
@@ -504,73 +511,99 @@ void GpuDumpMemoryProps(Printer &p, AppGpu &gpu) {
                 {
                     usable += "None";
                 }
-                p.PrintElement(usable);
+                p.PrintString(usable);
             }
-            p.ObjectEnd();
         }
-
-        p.ObjectEnd();
     }
-    p.ArrayEnd();
+
     p.IndentIncrease();
-    p.ObjectEnd();
     p.AddNewline();
 }
 
+void GpuDumpMemoryPropsJson(Printer &p, AppGpu &gpu) {
+    ObjectWrapper obj(p, "VkPhysicalDeviceMemoryProperties");
+    {
+        ArrayWrapper arr(p, "memoryHeaps", gpu.memory_props.memoryHeapCount);
+        for (uint32_t i = 0; i < gpu.memory_props.memoryHeapCount; ++i) {
+            ObjectWrapper obj(p, "");
+            p.PrintKeyValue("flags", gpu.memory_props.memoryHeaps[i].flags);
+            p.PrintKeyValue("size", gpu.memory_props.memoryHeaps[i].size);
+        }
+    }
+    {
+        ArrayWrapper arr(p, "memoryTypes", gpu.memory_props.memoryTypeCount);
+        for (uint32_t i = 0; i < gpu.memory_props.memoryTypeCount; ++i) {
+            ObjectWrapper obj(p, "");
+            p.PrintKeyValue("heapIndex", gpu.memory_props.memoryTypes[i].heapIndex, 13);
+            p.PrintKeyValue("propertyFlags", gpu.memory_props.memoryTypes[i].propertyFlags, 13);
+        }
+    }
+}
+
 void GpuDumpFeatures(Printer &p, AppGpu &gpu) {
     p.SetHeader();
     DumpVkPhysicalDeviceFeatures(p, "VkPhysicalDeviceFeatures", gpu.features);
     p.AddNewline();
-    if (p.Type() != OutputType::json) {
-        if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
-            void *place = gpu.features2.pNext;
-            chain_iterator_phys_device_features2(p, gpu, place, gpu.api_version);
-        }
+    if (gpu.inst.CheckExtensionEnabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
+        void *place = gpu.features2.pNext;
+        chain_iterator_phys_device_features2(p, gpu, place, gpu.api_version);
     }
 }
 
 void GpuDumpFormatProperty(Printer &p, VkFormat fmt, VkFormatProperties prop) {
-    if (p.Type() == OutputType::text) {
-        p.ObjectStart("Properties");
-    } else if (p.Type() == OutputType::html) {
-        p.SetTitleAsType().ObjectStart(VkFormatString(fmt));
-    } else if (p.Type() == OutputType::json) {
-        p.ObjectStart("");
-    }
-    if (p.Type() == OutputType::html || p.Type() == OutputType::text) {
-        p.SetOpenDetails();
-        DumpVkFormatFeatureFlags(p, "linearTiling", prop.linearTilingFeatures);
-        p.SetOpenDetails();
-        DumpVkFormatFeatureFlags(p, "optimalTiling", prop.optimalTilingFeatures);
-        p.SetOpenDetails();
-        DumpVkFormatFeatureFlags(p, "bufferFeatures", prop.bufferFeatures);
-    } else if (p.Type() == OutputType::json) {
-        p.PrintKeyValue("formatID", fmt);
-        p.PrintKeyValue("linearTilingFeatures", prop.linearTilingFeatures);
-        p.PrintKeyValue("optimalTilingFeatures", prop.optimalTilingFeatures);
-        p.PrintKeyValue("bufferFeatures", prop.bufferFeatures);
-    }
-    p.ObjectEnd();
+    switch (p.Type()) {
+        case OutputType::text: {
+            ObjectWrapper obj(p, "Properties");
+            DumpVkFormatFeatureFlags(p, "linearTiling", prop.linearTilingFeatures);
+            DumpVkFormatFeatureFlags(p, "optimalTiling", prop.optimalTilingFeatures);
+            DumpVkFormatFeatureFlags(p, "bufferFeatures", prop.bufferFeatures);
+            break;
+        }
+        case OutputType::html: {
+            p.SetTitleAsType();
+            ObjectWrapper obj(p, VkFormatString(fmt));
+            p.SetOpenDetails();
+            DumpVkFormatFeatureFlags(p, "linearTiling", prop.linearTilingFeatures);
+            p.SetOpenDetails();
+            DumpVkFormatFeatureFlags(p, "optimalTiling", prop.optimalTilingFeatures);
+            p.SetOpenDetails();
+            DumpVkFormatFeatureFlags(p, "bufferFeatures", prop.bufferFeatures);
+            break;
+        }
+        case OutputType::json: {
+            ObjectWrapper obj(p, "");
+            p.PrintKeyValue("formatID", fmt);
+            p.PrintKeyValue("linearTilingFeatures", prop.linearTilingFeatures);
+            p.PrintKeyValue("optimalTilingFeatures", prop.optimalTilingFeatures);
+            p.PrintKeyValue("bufferFeatures", prop.bufferFeatures);
+            break;
+        }
+        case OutputType::vkconfig_output: {
+            ObjectWrapper obj(p, VkFormatString(fmt));
+            DumpVkFormatFeatureFlags(p, "linearTiling", prop.linearTilingFeatures);
+            DumpVkFormatFeatureFlags(p, "optimalTiling", prop.optimalTilingFeatures);
+            DumpVkFormatFeatureFlags(p, "bufferFeatures", prop.bufferFeatures);
+            break;
+        }
+    }
 }
 
 void GpuDumpToolingInfo(Printer &p, AppGpu &gpu) {
     auto tools = GetToolingInfo(gpu);
     if (tools.size() > 0) {
-        p.SetSubHeader().ObjectStart("Tooling Info");
+        p.SetSubHeader();
+        ObjectWrapper obj(p, "Tooling Info");
         for (auto tool : tools) {
             DumpVkPhysicalDeviceToolPropertiesEXT(p, tool.name, tool);
+            p.AddNewline();
         }
-        p.ObjectEnd();
     }
 }
 
 void GpuDevDump(Printer &p, AppGpu &gpu) {
-    if (p.Type() == OutputType::json) {
-        p.ArrayStart("ArrayOfVkFormatProperties");
-    } else {
-        p.SetHeader().ObjectStart("Format Properties");
-        p.IndentDecrease();
-    }
+    p.SetHeader();
+    ObjectWrapper obj(p, "Format Properties");
+    p.IndentDecrease();
 
     if (p.Type() == OutputType::text) {
         auto fmtPropMap = FormatPropMap(gpu);
@@ -587,27 +620,26 @@ void GpuDevDump(Printer &p, AppGpu &gpu) {
                 continue;
             }
 
-            p.SetElementIndex(counter++).ObjectStart("Common Format Group");
+            p.SetElementIndex(counter++);
+            ObjectWrapper obj(p, "Common Format Group");
             p.IndentDecrease();
-            p.ObjectStart("Formats");
-            for (auto &fmt : prop.second) {
-                p.SetAsType().PrintElement(VkFormatString(fmt));
-            }
-            p.ObjectEnd();
 
+            {
+                ArrayWrapper arr(p, "Formats", prop.second.size());
+                for (auto &fmt : prop.second) {
+                    p.SetAsType().PrintString(VkFormatString(fmt));
+                }
+            }
             GpuDumpFormatProperty(p, VK_FORMAT_UNDEFINED, props);
 
             p.IndentIncrease();
-            p.ObjectEnd();
             p.AddNewline();
         }
 
-        p.ObjectStart("Unsupported Formats");
+        ArrayWrapper arr(p, "Unsupported Formats", unsupported_formats.size());
         for (auto &fmt : unsupported_formats) {
-            p.SetAsType().PrintElement(VkFormatString(fmt));
+            p.SetAsType().PrintString(VkFormatString(fmt));
         }
-        p.ObjectEnd();
-
     } else {
         for (auto &format : gpu.supported_format_ranges) {
             if (gpu.FormatRangeSupported(format)) {
@@ -617,69 +649,78 @@ void GpuDevDump(Printer &p, AppGpu &gpu) {
                     VkFormatProperties props;
                     vkGetPhysicalDeviceFormatProperties(gpu.phys_device, fmt, &props);
 
-                    // if json, don't print format properties that are unsupported
-                    if (p.Type() == OutputType::json &&
-                        (props.linearTilingFeatures || props.optimalTilingFeatures || props.bufferFeatures) == 0)
-                        continue;
-
                     GpuDumpFormatProperty(p, fmt, props);
                 }
             }
         }
     }
 
-    if (p.Type() == OutputType::json) {
-        p.ArrayEnd();
-    } else {
-        p.IndentIncrease();
-        p.ObjectEnd();
-    }
-
+    p.IndentIncrease();
     p.AddNewline();
 }
 
-void DumpGpu(Printer &p, AppGpu &gpu, bool show_formats) {
-    if (p.Type() != OutputType::json) {
-        p.ObjectStart("GPU" + std::to_string(gpu.id));
-        p.IndentDecrease();
-    }
-    GpuDumpProps(p, gpu);
+void GpuDevDumpJson(Printer &p, AppGpu &gpu) {
+    ArrayWrapper arr(p, "ArrayOfVkFormatProperties");
+    for (auto &format : gpu.supported_format_ranges) {
+        if (gpu.FormatRangeSupported(format)) {
+            for (int32_t fmt_counter = format.first_format; fmt_counter <= format.last_format; ++fmt_counter) {
+                VkFormat fmt = static_cast<VkFormat>(fmt_counter);
 
-    if (p.Type() != OutputType::json) {
-        DumpExtensions(p, "Device", gpu.device_extensions);
-        p.AddNewline();
-    }
+                VkFormatProperties props;
+                vkGetPhysicalDeviceFormatProperties(gpu.phys_device, fmt, &props);
 
-    if (p.Type() == OutputType::json) {
-        p.ArrayStart("ArrayOfVkQueueFamilyProperties");
-    } else {
-        p.SetHeader().ObjectStart("VkQueueFamilyProperties");
-    }
-    for (uint32_t i = 0; i < gpu.queue_count; i++) {
-        AppQueueFamilyProperties queue_props = AppQueueFamilyProperties(gpu, i);
-        GpuDumpQueueProps(p, gpu.inst.surface_extensions, queue_props);
+                // don't print format properties that are unsupported
+                if ((props.linearTilingFeatures || props.optimalTilingFeatures || props.bufferFeatures) == 0) continue;
+
+                GpuDumpFormatProperty(p, fmt, props);
+            }
+        }
     }
-    if (p.Type() == OutputType::json) {
-        p.ArrayEnd();
-    } else {
-        p.ObjectEnd();
+}
+// Print gpu info for text, html, & vkconfig_output
+// Uses a seperate function than schema-json for clarity
+void DumpGpu(Printer &p, AppGpu &gpu, bool show_formats) {
+    ObjectWrapper obj(p, "GPU" + std::to_string(gpu.id));
+    p.IndentDecrease();
+
+    GpuDumpProps(p, gpu);
+    DumpExtensions(p, "Device", gpu.device_extensions);
+    p.AddNewline();
+    {
+        p.SetHeader();
+        ObjectWrapper obj(p, "VkQueueFamilyProperties");
+        for (uint32_t i = 0; i < gpu.queue_count; i++) {
+            AppQueueFamilyProperties queue_props = AppQueueFamilyProperties(gpu, i);
+            GpuDumpQueueProps(p, gpu.inst.surface_extensions, queue_props);
+        }
     }
     GpuDumpMemoryProps(p, gpu);
     GpuDumpFeatures(p, gpu);
-
-    if (p.Type() != OutputType::json) GpuDumpToolingInfo(p, gpu);
+    GpuDumpToolingInfo(p, gpu);
 
     if (p.Type() != OutputType::text || show_formats) {
         GpuDevDump(p, gpu);
     }
 
-    if (p.Type() != OutputType::json) {
-        p.IndentIncrease();
-        p.ObjectEnd();
-    }
+    p.IndentIncrease();
     p.AddNewline();
 }
 
+// Print gpu info for json
+void DumpGpuJson(Printer &p, AppGpu &gpu) {
+    GpuDumpPropsJson(p, gpu);
+    {
+        ArrayWrapper arr(p, "ArrayOfVkQueueFamilyProperties");
+        for (uint32_t i = 0; i < gpu.queue_count; i++) {
+            AppQueueFamilyProperties queue_props = AppQueueFamilyProperties(gpu, i);
+            GpuDumpQueuePropsJson(p, gpu.inst.surface_extensions, queue_props);
+        }
+    }
+    GpuDumpMemoryPropsJson(p, gpu);
+    DumpVkPhysicalDeviceFeatures(p, "VkPhysicalDeviceFeatures", gpu.features);
+    GpuDevDumpJson(p, gpu);
+}
+
 // ============ Printing Logic ============= //
 
 #ifdef _WIN32
@@ -738,10 +779,18 @@ int main(int argc, char **argv) {
 
     uint32_t selected_gpu = 0;
     bool show_formats = false;
+    char *output_path = nullptr;
 
     // Combinations of output: html only, html AND json, json only, human readable only
     for (int i = 1; i < argc; ++i) {
-        if (strncmp("--json", argv[i], 6) == 0 || strcmp(argv[i], "-j") == 0) {
+        // A internal-use-only format for communication with the Vulkan Configurator tool
+        // Usage "--vkconfig_output <path>"
+        if (0 == strcmp("--vkconfig_output", argv[i]) && argc > (i + 1)) {
+            human_readable_output = false;
+            vkconfig_output = true;
+            output_path = argv[i + 1];
+            ++i;
+        } else if (strncmp("--json", argv[i], 6) == 0 || strcmp(argv[i], "-j") == 0) {
             if (strlen(argv[i]) > 7 && strncmp("--json=", argv[i], 7) == 0) {
                 selected_gpu = static_cast<uint32_t>(strtol(argv[i] + 7, nullptr, 10));
             }
@@ -799,6 +848,7 @@ int main(int argc, char **argv) {
     buf = std::cout.rdbuf();
     std::ostream out(buf);
     std::ofstream html_out;
+    std::ofstream vkconfig_out;
 
     if (human_readable_output) {
         printers.push_back(std::unique_ptr<Printer>(new Printer(OutputType::text, out, selected_gpu, instance.vk_version)));
@@ -810,36 +860,45 @@ int main(int argc, char **argv) {
     if (json_output) {
         printers.push_back(std::unique_ptr<Printer>(new Printer(OutputType::json, out, selected_gpu, instance.vk_version)));
     }
+    if (vkconfig_output) {
+#ifdef WIN32
+        vkconfig_out = std::ofstream(std::string(output_path) + "\\vulkaninfo.json");
+#else
+        vkconfig_out = std::ofstream(std::string(output_path) + "/vulkaninfo.json");
+#endif
+        printers.push_back(
+            std::unique_ptr<Printer>(new Printer(OutputType::vkconfig_output, vkconfig_out, selected_gpu, instance.vk_version)));
+    }
 
     for (auto &p : printers) {
-        if (p->Type() != OutputType::json) {
+        if (p->Type() == OutputType::json) {
+            DumpLayers(*p.get(), instance.global_layers, gpus);
+            DumpGpuJson(*p.get(), *gpus.at(selected_gpu).get());
+
+        } else {
             p->SetHeader();
             DumpExtensions(*p.get(), "Instance", instance.global_extensions);
             p->AddNewline();
-        }
 
-        DumpLayers(*p.get(), instance.global_layers, gpus);
+            DumpLayers(*p.get(), instance.global_layers, gpus);
 
-        if (p->Type() != OutputType::json) {
 #if defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WIN32_KHR) || \
     defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) || defined(VK_USE_PLATFORM_WAYLAND_KHR)
             DumpPresentableSurfaces(*p.get(), instance, gpus, surfaces);
 #endif
             DumpGroups(*p.get(), instance);
 
-            p->SetHeader().ObjectStart("Device Properties and Extensions");
+            p->SetHeader();
+            ObjectWrapper obj(*p, "Device Properties and Extensions");
             p->IndentDecrease();
-        }
-        for (auto &gpu : gpus) {
-            if ((p->Type() == OutputType::json && gpu->id == selected_gpu) || p->Type() == OutputType::text ||
-                p->Type() == OutputType::html) {
+
+            for (auto &gpu : gpus) {
                 DumpGpu(*p.get(), *gpu.get(), show_formats);
             }
-        }
-        if (p->Type() != OutputType::json) {
+
             p->IndentIncrease();
-            p->ObjectEnd();
         }
+        p.reset();
     }
 
 #if defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WIN32_KHR) || \
index 6ba97d9..d5ea32c 100644 (file)
@@ -74,6 +74,7 @@
 bool human_readable_output = true;
 bool html_output = false;
 bool json_output = false;
+bool vkconfig_output = false;
 
 #ifdef _WIN32