#include "output_stream.h"

#include <algorithm>
#include <cassert>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

enum TextLineType {
  TEXT_LINE_TYPE_BLOCK_BEGIN   = 0x01,
  TEXT_LINE_TYPE_BLOCK_END     = 0x02,
  TEXT_LINE_TYPE_STRUCT_BEGIN  = 0x04,
  TEXT_LINE_TYPE_STRUCT_END    = 0x08,
  TEXT_LINE_TYPE_LINES         = 0x10,
};

struct TextLine {
  std::string           indent;
  std::string           modifier;
  std::string           type_name;
  std::string           name;
  uint32_t              absolute_offset;
  uint32_t              relative_offset;
  uint32_t              size;
  uint32_t              padded_size;
  uint32_t              array_stride;
  uint32_t              block_variable_flags;
  // Text Data
  uint32_t              text_line_flags;
  std::vector<TextLine> lines;
  std::string           formatted_line;
  std::string           formatted_absolute_offset;
  std::string           formatted_relative_offset;
  std::string           formatted_size;
  std::string           formatted_padded_size;
  std::string           formatted_array_stride;
  std::string           formatted_block_variable_flags;
};

static std::string AsHexString(uint32_t n) {
  // std::iomanip can die in a fire.
  char out_word[11];
  int len = snprintf(out_word, 11, "0x%08X", n);
  assert(len == 10);
  (void)len;
  return std::string(out_word);
}

std::string ToStringGenerator(SpvReflectGenerator generator)
{
  switch (generator) {
    case SPV_REFLECT_GENERATOR_KHRONOS_LLVM_SPIRV_TRANSLATOR         : return "Khronos LLVM/SPIR-V Translator"; break;
    case SPV_REFLECT_GENERATOR_KHRONOS_SPIRV_TOOLS_ASSEMBLER         : return "Khronos SPIR-V Tools Assembler"; break;
    case SPV_REFLECT_GENERATOR_KHRONOS_GLSLANG_REFERENCE_FRONT_END   : return "Khronos Glslang Reference Front End"; break;
    case SPV_REFLECT_GENERATOR_GOOGLE_SHADERC_OVER_GLSLANG           : return "Google Shaderc over Glslang"; break;
    case SPV_REFLECT_GENERATOR_GOOGLE_SPIREGG                        : return "Google spiregg"; break;
    case SPV_REFLECT_GENERATOR_GOOGLE_RSPIRV                         : return "Google rspirv"; break;
    case SPV_REFLECT_GENERATOR_X_LEGEND_MESA_MESAIR_SPIRV_TRANSLATOR : return "X-LEGEND Mesa-IR/SPIR-V Translator"; break;
    case SPV_REFLECT_GENERATOR_KHRONOS_SPIRV_TOOLS_LINKER            : return "Khronos SPIR-V Tools Linker"; break;
    case SPV_REFLECT_GENERATOR_WINE_VKD3D_SHADER_COMPILER            : return "Wine VKD3D Shader Compiler"; break;
    case SPV_REFLECT_GENERATOR_CLAY_CLAY_SHADER_COMPILER             : return "Clay Clay Shader Compiler"; break;
  }
  // unhandled SpvReflectGenerator enum value
  return "???";
}

std::string ToStringSpvSourceLanguage(SpvSourceLanguage lang) {
  switch(lang) {
    case SpvSourceLanguageUnknown    : return "Unknown";
    case SpvSourceLanguageESSL       : return "ESSL";
    case SpvSourceLanguageGLSL       : return "GLSL";
    case SpvSourceLanguageOpenCL_C   : return "OpenCL_C";
    case SpvSourceLanguageOpenCL_CPP : return "OpenCL_CPP";
    case SpvSourceLanguageHLSL       : return "HLSL";

    case SpvSourceLanguageMax:
      break;
  }
  // unhandled SpvSourceLanguage enum value
  return "???";
}

std::string ToStringSpvExecutionModel(SpvExecutionModel model) {
  switch(model) {
    case SpvExecutionModelVertex                 : return "Vertex";
    case SpvExecutionModelTessellationControl    : return "TessellationControl";
    case SpvExecutionModelTessellationEvaluation : return "TessellationEvaluation";
    case SpvExecutionModelGeometry               : return "Geometry";
    case SpvExecutionModelFragment               : return "Fragment";
    case SpvExecutionModelGLCompute              : return "GLCompute";
    case SpvExecutionModelKernel                 : return "Kernel";
    case SpvExecutionModelTaskNV                 : return "TaskNV";
    case SpvExecutionModelMeshNV                 : return "MeshNV";
    case SpvExecutionModelRayGenerationKHR       : return "RayGenerationKHR";
    case SpvExecutionModelIntersectionKHR        : return "IntersectionKHR";
    case SpvExecutionModelAnyHitKHR              : return "AnyHitKHR";
    case SpvExecutionModelClosestHitKHR          : return "ClosestHitKHR";
    case SpvExecutionModelMissKHR                : return "MissKHR";
    case SpvExecutionModelCallableKHR            : return "CallableKHR";
    
    case SpvExecutionModelMax:
      break;
  }
  // unhandled SpvExecutionModel enum value
  return "???";
}


std::string ToStringShaderStage(SpvReflectShaderStageFlagBits stage) {
  switch (stage) {
    case SPV_REFLECT_SHADER_STAGE_VERTEX_BIT                  : return "VS";
    case SPV_REFLECT_SHADER_STAGE_TESSELLATION_CONTROL_BIT    : return "HS";
    case SPV_REFLECT_SHADER_STAGE_TESSELLATION_EVALUATION_BIT : return "DS";
    case SPV_REFLECT_SHADER_STAGE_GEOMETRY_BIT                : return "GS";
    case SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT                : return "PS";
    case SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT                 : return "CS";
    case SPV_REFLECT_SHADER_STAGE_TASK_BIT_NV                 : return "TASK";
    case SPV_REFLECT_SHADER_STAGE_MESH_BIT_NV                 : return "MESH";
    case SPV_REFLECT_SHADER_STAGE_RAYGEN_BIT_KHR              : return "RAYGEN";
    case SPV_REFLECT_SHADER_STAGE_ANY_HIT_BIT_KHR             : return "ANY_HIT";
    case SPV_REFLECT_SHADER_STAGE_CLOSEST_HIT_BIT_KHR         : return "CLOSEST_HIT";
    case SPV_REFLECT_SHADER_STAGE_MISS_BIT_KHR                : return "MISS";
    case SPV_REFLECT_SHADER_STAGE_INTERSECTION_BIT_KHR        : return "INTERSECTION";
    case SPV_REFLECT_SHADER_STAGE_CALLABLE_BIT_KHR            : return "CALLABLE";
  }
  // Unhandled SpvReflectShaderStageFlagBits enum value
  return "???";
}

std::string ToStringSpvStorageClass(SpvStorageClass storage_class) {
  switch(storage_class) {
    case SpvStorageClassUniformConstant         : return "UniformConstant";
    case SpvStorageClassInput                   : return "Input";
    case SpvStorageClassUniform                 : return "Uniform";
    case SpvStorageClassOutput                  : return "Output";
    case SpvStorageClassWorkgroup               : return "Workgroup";
    case SpvStorageClassCrossWorkgroup          : return "CrossWorkgroup";
    case SpvStorageClassPrivate                 : return "Private";
    case SpvStorageClassFunction                : return "Function";
    case SpvStorageClassGeneric                 : return "Generic";
    case SpvStorageClassPushConstant            : return "PushConstant";
    case SpvStorageClassAtomicCounter           : return "AtomicCounter";
    case SpvStorageClassImage                   : return "Image";
    case SpvStorageClassStorageBuffer           : return "StorageBuffer";
    case SpvStorageClassCallableDataKHR         : return "CallableDataKHR";
    case SpvStorageClassIncomingCallableDataKHR : return "IncomingCallableDataKHR";
    case SpvStorageClassRayPayloadKHR           : return "RayPayloadKHR";
    case SpvStorageClassHitAttributeKHR         : return "HitAttributeKHR";
    case SpvStorageClassIncomingRayPayloadKHR   : return "IncomingRayPayloadKHR";
    case SpvStorageClassShaderRecordBufferKHR   : return "ShaderRecordBufferKHR";
    case SpvStorageClassPhysicalStorageBuffer   : return "PhysicalStorageBuffer";
    case SpvStorageClassCodeSectionINTEL        : return "CodeSectionINTEL";

    case SpvStorageClassMax:
      break;
  }

  // Special case: this specific "unhandled" value does actually seem to show up.
  if (storage_class == (SpvStorageClass)-1) {
    return "NOT APPLICABLE";
  }

  // unhandled SpvStorageClass enum value
  return "???";
}

std::string ToStringSpvDim(SpvDim dim) {
  switch(dim) {
    case SpvDim1D          : return "1D";
    case SpvDim2D          : return "2D";
    case SpvDim3D          : return "3D";
    case SpvDimCube        : return "Cube";
    case SpvDimRect        : return "Rect";
    case SpvDimBuffer      : return "Buffer";
    case SpvDimSubpassData : return "SubpassData";

    case SpvDimMax:
      break;
  }
  // unhandled SpvDim enum value
  return "???";
}


std::string ToStringResourceType(SpvReflectResourceType res_type) {
  switch(res_type) {
    case SPV_REFLECT_RESOURCE_FLAG_UNDEFINED : return "UNDEFINED";
    case SPV_REFLECT_RESOURCE_FLAG_SAMPLER   : return "SAMPLER";
    case SPV_REFLECT_RESOURCE_FLAG_CBV       : return "CBV";
    case SPV_REFLECT_RESOURCE_FLAG_SRV       : return "SRV";
    case SPV_REFLECT_RESOURCE_FLAG_UAV       : return "UAV";
  }
  // unhandled SpvReflectResourceType enum value
  return "???";
}

std::string ToStringDescriptorType(SpvReflectDescriptorType value) {
  switch (value) {
    case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER                    : return "VK_DESCRIPTOR_TYPE_SAMPLER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER     : return "VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE              : return "VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE";
    case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE              : return "VK_DESCRIPTOR_TYPE_STORAGE_IMAGE";
    case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER       : return "VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER       : return "VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER             : return "VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER             : return "VK_DESCRIPTOR_TYPE_STORAGE_BUFFER";
    case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC     : return "VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC";
    case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC     : return "VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC";
    case SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT           : return "VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT";
    case SPV_REFLECT_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR : return "VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR";
  }
  // unhandled SpvReflectDescriptorType enum value
  return "VK_DESCRIPTOR_TYPE_???";
}

std::string ToStringSpvBuiltIn(SpvBuiltIn built_in) {
  switch (built_in) {
    case SpvBuiltInPosition                    : return "Position";
    case SpvBuiltInPointSize                   : return "PointSize";
    case SpvBuiltInClipDistance                : return "ClipDistance";
    case SpvBuiltInCullDistance                : return "CullDistance";
    case SpvBuiltInVertexId                    : return "VertexId";
    case SpvBuiltInInstanceId                  : return "InstanceId";
    case SpvBuiltInPrimitiveId                 : return "PrimitiveId";
    case SpvBuiltInInvocationId                : return "InvocationId";
    case SpvBuiltInLayer                       : return "Layer";
    case SpvBuiltInViewportIndex               : return "ViewportIndex";
    case SpvBuiltInTessLevelOuter              : return "TessLevelOuter";
    case SpvBuiltInTessLevelInner              : return "TessLevelInner";
    case SpvBuiltInTessCoord                   : return "TessCoord";
    case SpvBuiltInPatchVertices               : return "PatchVertices";
    case SpvBuiltInFragCoord                   : return "FragCoord";
    case SpvBuiltInPointCoord                  : return "PointCoord";
    case SpvBuiltInFrontFacing                 : return "FrontFacing";
    case SpvBuiltInSampleId                    : return "SampleId";
    case SpvBuiltInSamplePosition              : return "SamplePosition";
    case SpvBuiltInSampleMask                  : return "SampleMask";
    case SpvBuiltInFragDepth                   : return "FragDepth";
    case SpvBuiltInHelperInvocation            : return "HelperInvocation";
    case SpvBuiltInNumWorkgroups               : return "NumWorkgroups";
    case SpvBuiltInWorkgroupSize               : return "WorkgroupSize";
    case SpvBuiltInWorkgroupId                 : return "WorkgroupId";
    case SpvBuiltInLocalInvocationId           : return "LocalInvocationId";
    case SpvBuiltInGlobalInvocationId          : return "GlobalInvocationId";
    case SpvBuiltInLocalInvocationIndex        : return "LocalInvocationIndex";
    case SpvBuiltInWorkDim                     : return "WorkDim";
    case SpvBuiltInGlobalSize                  : return "GlobalSize";
    case SpvBuiltInEnqueuedWorkgroupSize       : return "EnqueuedWorkgroupSize";
    case SpvBuiltInGlobalOffset                : return "GlobalOffset";
    case SpvBuiltInGlobalLinearId              : return "GlobalLinearId";
    case SpvBuiltInSubgroupSize                : return "SubgroupSize";
    case SpvBuiltInSubgroupMaxSize             : return "SubgroupMaxSize";
    case SpvBuiltInNumSubgroups                : return "NumSubgroups";
    case SpvBuiltInNumEnqueuedSubgroups        : return "NumEnqueuedSubgroups";
    case SpvBuiltInSubgroupId                  : return "SubgroupId";
    case SpvBuiltInSubgroupLocalInvocationId   : return "SubgroupLocalInvocationId";
    case SpvBuiltInVertexIndex                 : return "VertexIndex";
    case SpvBuiltInInstanceIndex               : return "InstanceIndex";
    case SpvBuiltInSubgroupEqMaskKHR           : return "SubgroupEqMaskKHR";
    case SpvBuiltInSubgroupGeMaskKHR           : return "SubgroupGeMaskKHR";
    case SpvBuiltInSubgroupGtMaskKHR           : return "SubgroupGtMaskKHR";
    case SpvBuiltInSubgroupLeMaskKHR           : return "SubgroupLeMaskKHR";
    case SpvBuiltInSubgroupLtMaskKHR           : return "SubgroupLtMaskKHR";
    case SpvBuiltInBaseVertex                  : return "BaseVertex";
    case SpvBuiltInBaseInstance                : return "BaseInstance";
    case SpvBuiltInDrawIndex                   : return "DrawIndex";
    case SpvBuiltInDeviceIndex                 : return "DeviceIndex";
    case SpvBuiltInViewIndex                   : return "ViewIndex";
    case SpvBuiltInBaryCoordNoPerspAMD         : return "BaryCoordNoPerspAMD";
    case SpvBuiltInBaryCoordNoPerspCentroidAMD : return "BaryCoordNoPerspCentroidAMD";
    case SpvBuiltInBaryCoordNoPerspSampleAMD   : return "BaryCoordNoPerspSampleAMD";
    case SpvBuiltInBaryCoordSmoothAMD          : return "BaryCoordSmoothAMD";
    case SpvBuiltInBaryCoordSmoothCentroidAMD  : return "BaryCoordSmoothCentroidAMD";
    case SpvBuiltInBaryCoordSmoothSampleAMD    : return "BaryCoordSmoothSampleAMD";
    case SpvBuiltInBaryCoordPullModelAMD       : return "BaryCoordPullModelAMD";
    case SpvBuiltInFragStencilRefEXT           : return "FragStencilRefEXT";
    case SpvBuiltInViewportMaskNV              : return "ViewportMaskNV";
    case SpvBuiltInSecondaryPositionNV         : return "SecondaryPositionNV";
    case SpvBuiltInSecondaryViewportMaskNV     : return "SecondaryViewportMaskNV";
    case SpvBuiltInPositionPerViewNV           : return "PositionPerViewNV";
    case SpvBuiltInViewportMaskPerViewNV       : return "ViewportMaskPerViewNV";
    case SpvBuiltInLaunchIdKHR                 : return "InLaunchIdKHR";
    case SpvBuiltInLaunchSizeKHR               : return "InLaunchSizeKHR";
    case SpvBuiltInWorldRayOriginKHR           : return "InWorldRayOriginKHR";
    case SpvBuiltInWorldRayDirectionKHR        : return "InWorldRayDirectionKHR";
    case SpvBuiltInObjectRayOriginKHR          : return "InObjectRayOriginKHR";
    case SpvBuiltInObjectRayDirectionKHR       : return "InObjectRayDirectionKHR";
    case SpvBuiltInRayTminKHR                  : return "InRayTminKHR";
    case SpvBuiltInRayTmaxKHR                  : return "InRayTmaxKHR";
    case SpvBuiltInInstanceCustomIndexKHR      : return "InInstanceCustomIndexKHR";
    case SpvBuiltInObjectToWorldKHR            : return "InObjectToWorldKHR";
    case SpvBuiltInWorldToObjectKHR            : return "InWorldToObjectKHR";
    case SpvBuiltInHitTNV                      : return "InHitTNV";
    case SpvBuiltInHitKindKHR                  : return "InHitKindKHR";
    case SpvBuiltInIncomingRayFlagsKHR         : return "InIncomingRayFlagsKHR";
    case SpvBuiltInRayGeometryIndexKHR         : return "InRayGeometryIndexKHR";

    case SpvBuiltInMax:
    default:
      break;
  }
  // unhandled SpvBuiltIn enum value
  std::stringstream ss;
  ss << "??? (" << built_in << ")";
  return ss.str();
}

std::string ToStringSpvImageFormat(SpvImageFormat fmt) {
  switch(fmt) {
    case SpvImageFormatUnknown      : return "Unknown";
    case SpvImageFormatRgba32f      : return "Rgba32f";
    case SpvImageFormatRgba16f      : return "Rgba16f";
    case SpvImageFormatR32f         : return "R32f";
    case SpvImageFormatRgba8        : return "Rgba8";
    case SpvImageFormatRgba8Snorm   : return "Rgba8Snorm";
    case SpvImageFormatRg32f        : return "Rg32f";
    case SpvImageFormatRg16f        : return "Rg16f";
    case SpvImageFormatR11fG11fB10f : return "R11fG11fB10f";
    case SpvImageFormatR16f         : return "R16f";
    case SpvImageFormatRgba16       : return "Rgba16";
    case SpvImageFormatRgb10A2      : return "Rgb10A2";
    case SpvImageFormatRg16         : return "Rg16";
    case SpvImageFormatRg8          : return "Rg8";
    case SpvImageFormatR16          : return "R16";
    case SpvImageFormatR8           : return "R8";
    case SpvImageFormatRgba16Snorm  : return "Rgba16Snorm";
    case SpvImageFormatRg16Snorm    : return "Rg16Snorm";
    case SpvImageFormatRg8Snorm     : return "Rg8Snorm";
    case SpvImageFormatR16Snorm     : return "R16Snorm";
    case SpvImageFormatR8Snorm      : return "R8Snorm";
    case SpvImageFormatRgba32i      : return "Rgba32i";
    case SpvImageFormatRgba16i      : return "Rgba16i";
    case SpvImageFormatRgba8i       : return "Rgba8i";
    case SpvImageFormatR32i         : return "R32i";
    case SpvImageFormatRg32i        : return "Rg32i";
    case SpvImageFormatRg16i        : return "Rg16i";
    case SpvImageFormatRg8i         : return "Rg8i";
    case SpvImageFormatR16i         : return "R16i";
    case SpvImageFormatR8i          : return "R8i";
    case SpvImageFormatRgba32ui     : return "Rgba32ui";
    case SpvImageFormatRgba16ui     : return "Rgba16ui";
    case SpvImageFormatRgba8ui      : return "Rgba8ui";
    case SpvImageFormatR32ui        : return "R32ui";
    case SpvImageFormatRgb10a2ui    : return "Rgb10a2ui";
    case SpvImageFormatRg32ui       : return "Rg32ui";
    case SpvImageFormatRg16ui       : return "Rg16ui";
    case SpvImageFormatRg8ui        : return "Rg8ui";
    case SpvImageFormatR16ui        : return "R16ui";
    case SpvImageFormatR8ui         : return "R8ui";
    case SpvImageFormatR64ui        : return "R64ui";
    case SpvImageFormatR64i         : return "R64i";

    case SpvImageFormatMax:
      break;
  }
  // unhandled SpvImageFormat enum value
  return "???";
}

std::string ToStringTypeFlags(SpvReflectTypeFlags type_flags) {
  if (type_flags == SPV_REFLECT_TYPE_FLAG_UNDEFINED) {
    return "UNDEFINED";
  }

#define PRINT_AND_CLEAR_TYPE_FLAG(stream, flags, bit) \
  if (( (flags) & (SPV_REFLECT_TYPE_FLAG_##bit) ) == (SPV_REFLECT_TYPE_FLAG_##bit)) { \
    stream << #bit << " "; \
    flags ^= SPV_REFLECT_TYPE_FLAG_##bit; \
  }
  std::stringstream sstream;
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, ARRAY);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, STRUCT);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, EXTERNAL_MASK);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, EXTERNAL_BLOCK);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, EXTERNAL_SAMPLED_IMAGE);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, EXTERNAL_SAMPLER);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, EXTERNAL_IMAGE);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, MATRIX);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, VECTOR);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, FLOAT);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, INT);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, BOOL);
  PRINT_AND_CLEAR_TYPE_FLAG(sstream, type_flags, VOID);
#undef PRINT_AND_CLEAR_TYPE_FLAG
  if (type_flags != 0) {
    // Unhandled SpvReflectTypeFlags bit
    sstream << "???";
  }
  return sstream.str();
}

std::string ToStringDecorationFlags(SpvReflectDecorationFlags decoration_flags) {
  if (decoration_flags == SPV_REFLECT_DECORATION_NONE) {
    return "NONE";
  }

#define PRINT_AND_CLEAR_DECORATION_FLAG(stream, flags, bit) \
  if (( (flags) & (SPV_REFLECT_DECORATION_##bit) ) == (SPV_REFLECT_DECORATION_##bit)) { \
    stream << #bit << " "; \
    flags ^= SPV_REFLECT_DECORATION_##bit; \
  }
  std::stringstream sstream;
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, NON_WRITABLE);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, FLAT);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, NOPERSPECTIVE);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, BUILT_IN);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, COLUMN_MAJOR);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, ROW_MAJOR);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, BUFFER_BLOCK);
  PRINT_AND_CLEAR_DECORATION_FLAG(sstream, decoration_flags, BLOCK);
#undef PRINT_AND_CLEAR_DECORATION_FLAG
  if (decoration_flags != 0) {
    // Unhandled SpvReflectDecorationFlags bit
    sstream << "???";
  }
  return sstream.str();
}

std::string ToStringFormat(SpvReflectFormat fmt) {
  switch(fmt) {
    case SPV_REFLECT_FORMAT_UNDEFINED           : return "VK_FORMAT_UNDEFINED";
    case SPV_REFLECT_FORMAT_R32_UINT            : return "VK_FORMAT_R32_UINT";
    case SPV_REFLECT_FORMAT_R32_SINT            : return "VK_FORMAT_R32_SINT";
    case SPV_REFLECT_FORMAT_R32_SFLOAT          : return "VK_FORMAT_R32_SFLOAT";
    case SPV_REFLECT_FORMAT_R32G32_UINT         : return "VK_FORMAT_R32G32_UINT";
    case SPV_REFLECT_FORMAT_R32G32_SINT         : return "VK_FORMAT_R32G32_SINT";
    case SPV_REFLECT_FORMAT_R32G32_SFLOAT       : return "VK_FORMAT_R32G32_SFLOAT";
    case SPV_REFLECT_FORMAT_R32G32B32_UINT      : return "VK_FORMAT_R32G32B32_UINT";
    case SPV_REFLECT_FORMAT_R32G32B32_SINT      : return "VK_FORMAT_R32G32B32_SINT";
    case SPV_REFLECT_FORMAT_R32G32B32_SFLOAT    : return "VK_FORMAT_R32G32B32_SFLOAT";
    case SPV_REFLECT_FORMAT_R32G32B32A32_UINT   : return "VK_FORMAT_R32G32B32A32_UINT";
    case SPV_REFLECT_FORMAT_R32G32B32A32_SINT   : return "VK_FORMAT_R32G32B32A32_SINT";
    case SPV_REFLECT_FORMAT_R32G32B32A32_SFLOAT : return "VK_FORMAT_R32G32B32A32_SFLOAT";
    case SPV_REFLECT_FORMAT_R64_UINT            : return "VK_FORMAT_R64_UINT";
    case SPV_REFLECT_FORMAT_R64_SINT            : return "VK_FORMAT_R64_SINT";
    case SPV_REFLECT_FORMAT_R64_SFLOAT          : return "VK_FORMAT_R64_SFLOAT";
    case SPV_REFLECT_FORMAT_R64G64_UINT         : return "VK_FORMAT_R64G64_UINT";
    case SPV_REFLECT_FORMAT_R64G64_SINT         : return "VK_FORMAT_R64G64_SINT";
    case SPV_REFLECT_FORMAT_R64G64_SFLOAT       : return "VK_FORMAT_R64G64_SFLOAT";
    case SPV_REFLECT_FORMAT_R64G64B64_UINT      : return "VK_FORMAT_R64G64B64_UINT";
    case SPV_REFLECT_FORMAT_R64G64B64_SINT      : return "VK_FORMAT_R64G64B64_SINT";
    case SPV_REFLECT_FORMAT_R64G64B64_SFLOAT    : return "VK_FORMAT_R64G64B64_SFLOAT";
    case SPV_REFLECT_FORMAT_R64G64B64A64_UINT   : return "VK_FORMAT_R64G64B64A64_UINT";
    case SPV_REFLECT_FORMAT_R64G64B64A64_SINT   : return "VK_FORMAT_R64G64B64A64_SINT";
    case SPV_REFLECT_FORMAT_R64G64B64A64_SFLOAT : return "VK_FORMAT_R64G64B64A64_SFLOAT";
  }
  // unhandled SpvReflectFormat enum value
  return "VK_FORMAT_???";
}

static std::string ToStringScalarType(const SpvReflectTypeDescription& type)
{
  switch(type.op) {
    case SpvOpTypeVoid: {
      return "void";
      break;
    }
    case SpvOpTypeBool: {
      return "bool";
      break;
    }
    case SpvOpTypeInt: {
      if (type.traits.numeric.scalar.signedness)
        return "int";
      else
        return "uint";
    }
    case SpvOpTypeFloat: {
      switch (type.traits.numeric.scalar.width) {
        case 32:
          return "float";
        case 64:
          return "double";
        default:
          break;
      }
    }
    case SpvOpTypeStruct: {
      return "struct";
    }
    default: {
      break;
    }
  }
  return "";
}

static std::string ToStringGlslType(const SpvReflectTypeDescription& type)
{
  switch (type.op) {
    case SpvOpTypeVector: {
      switch (type.traits.numeric.scalar.width) {
        case 32: {
          switch (type.traits.numeric.vector.component_count) {
          case 2: return "vec2";
          case 3: return "vec3";
          case 4: return "vec4";
          }
        }
        break;

        case 64: {
          switch (type.traits.numeric.vector.component_count) {
          case 2: return "dvec2";
          case 3: return "dvec3";
          case 4: return "dvec4";
          }
        }
        break;
      }
    }
    break;
    default:
      break;
  }
  return ToStringScalarType(type);
}

static std::string ToStringHlslType(const SpvReflectTypeDescription& type)
{
  switch (type.op) {
    case SpvOpTypeVector: {
      switch (type.traits.numeric.scalar.width) {
        case 32: {
          switch (type.traits.numeric.vector.component_count) {
          case 2: return "float2";
          case 3: return "float3";
          case 4: return "float4";
          }
        }
        break;

        case 64: {
          switch (type.traits.numeric.vector.component_count) {
          case 2: return "double2";
          case 3: return "double3";
          case 4: return "double4";
          }
        }
        break;
      }
    }
    break;

    default:
      break;
  }
  return ToStringScalarType(type);
}

std::string ToStringType(SpvSourceLanguage src_lang, const SpvReflectTypeDescription& type)
{
  if (src_lang == SpvSourceLanguageHLSL) {
    return ToStringHlslType(type);
  }

  return ToStringGlslType(type);
}

std::string ToStringComponentType(const SpvReflectTypeDescription& type, uint32_t member_decoration_flags)
{
  uint32_t masked_type = type.type_flags & 0xF;
  if (masked_type == 0) {
    return "";
  }

  std::stringstream ss;

  if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) {
    if (member_decoration_flags & SPV_REFLECT_DECORATION_COLUMN_MAJOR) {
      ss << "column_major" << " ";
    }
    else if (member_decoration_flags & SPV_REFLECT_DECORATION_ROW_MAJOR) {
      ss << "row_major" << " ";
    }
  }

  switch (masked_type) {
    default: assert(false && "unsupported component type"); break;
    case SPV_REFLECT_TYPE_FLAG_BOOL  : ss << "bool"; break;
    case SPV_REFLECT_TYPE_FLAG_INT   : ss << (type.traits.numeric.scalar.signedness ? "int" : "uint"); break;
    case SPV_REFLECT_TYPE_FLAG_FLOAT : ss << "float"; break;
  }

  if (type.type_flags & SPV_REFLECT_TYPE_FLAG_MATRIX) {
    ss << type.traits.numeric.matrix.row_count;
    ss << "x";
    ss << type.traits.numeric.matrix.column_count;
  }
  else if (type.type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) {
    ss << type.traits.numeric.vector.component_count;
  }

  return ss.str();
}

void ParseBlockMembersToTextLines(const char* indent, int indent_depth, bool flatten_cbuffers, const std::string& parent_name, uint32_t member_count, const SpvReflectBlockVariable* p_members, std::vector<TextLine>* p_text_lines)
{
  const char* t = indent;
  for (uint32_t member_index = 0; member_index < member_count; ++member_index) {
    indent_depth = flatten_cbuffers ? 2 : indent_depth;
    std::stringstream ss_indent;
    for (int indent_count = 0; indent_count < indent_depth; ++indent_count) {
      ss_indent << t;
    }
    std::string expanded_indent = ss_indent.str(); 

    const auto& member = p_members[member_index];
    bool is_struct = ((member.type_description->type_flags & static_cast<SpvReflectTypeFlags>(SPV_REFLECT_TYPE_FLAG_STRUCT)) != 0);
    if (is_struct) {
      const std::string name = (member.name == nullptr ? "" : member.name);

      // Begin struct
      TextLine tl = {};
      tl.indent = expanded_indent;
      tl.type_name = (member.type_description->type_name == nullptr ? "" : member.type_description->type_name);
      tl.absolute_offset = member.absolute_offset;
      tl.relative_offset = member.offset;
      tl.size = member.size;
      tl.padded_size = member.padded_size;
      tl.array_stride = member.array.stride;
      tl.block_variable_flags = member.flags;
      tl.text_line_flags = TEXT_LINE_TYPE_STRUCT_BEGIN;
      if (!flatten_cbuffers) {
        p_text_lines->push_back(tl);
      }

      // Members
      tl = {};
      std::string current_parent_name;
      if (flatten_cbuffers) {
        current_parent_name = parent_name.empty() ? name : (parent_name + "." + name);
      }
      std::vector<TextLine>* p_target_text_line = flatten_cbuffers ? p_text_lines : &tl.lines;
      ParseBlockMembersToTextLines(t, indent_depth + 1, flatten_cbuffers, current_parent_name, member.member_count, member.members, p_target_text_line);
      tl.text_line_flags = TEXT_LINE_TYPE_LINES;
      p_text_lines->push_back(tl);      

      // End struct
      tl = {};
      tl.indent = expanded_indent;
      tl.name = name;
      if (member.array.dims_count > 0) {
        std::stringstream ss_array;
        for (uint32_t array_dim_index = 0; array_dim_index < member.array.dims_count; ++array_dim_index) {
          uint32_t dim = member.array.dims[array_dim_index];
          ss_array << "[" << dim << "]";
        }
        tl.name += ss_array.str();
      }
      tl.absolute_offset = member.absolute_offset;
      tl.relative_offset = member.offset;
      tl.size = member.size;
      tl.padded_size = member.padded_size;
      tl.array_stride = member.array.stride;
      tl.block_variable_flags = member.flags;
      tl.text_line_flags = TEXT_LINE_TYPE_STRUCT_END;
      if (!flatten_cbuffers) {
        p_text_lines->push_back(tl);
      }
    }
    else {
      std::string name = (member.name == nullptr ? "" : member.name);
      if (flatten_cbuffers) {
        if (!parent_name.empty()) {
          name = parent_name + "." + name;
        }
      }

      TextLine tl = {};
      tl.indent = expanded_indent;
      tl.type_name = ToStringComponentType(*member.type_description, member.decoration_flags);
      tl.name = name;
      if (member.array.dims_count > 0) {
        std::stringstream ss_array;
        for (uint32_t array_dim_index = 0; array_dim_index < member.array.dims_count; ++array_dim_index) {
          uint32_t dim = member.array.dims[array_dim_index];
          ss_array << "[" << dim << "]";
        }
        tl.name += ss_array.str();
      }
      tl.absolute_offset = member.absolute_offset;
      tl.relative_offset = member.offset;
      tl.size = member.size;
      tl.padded_size = member.padded_size;
      tl.array_stride = member.array.stride;
      tl.block_variable_flags = member.flags;
      p_text_lines->push_back(tl);      
    }
  }
}

void ParseBlockVariableToTextLines(const char* indent, bool flatten_cbuffers, const SpvReflectBlockVariable& block_var, std::vector<TextLine>* p_text_lines)
{
  // Begin block
  TextLine tl = {};
  tl.indent = indent;
  tl.type_name = (block_var.type_description->type_name != nullptr) ? block_var.type_description->type_name : "<unnamed>";
  tl.size = block_var.size;
  tl.padded_size = block_var.padded_size;
  tl.text_line_flags = TEXT_LINE_TYPE_BLOCK_BEGIN;
  p_text_lines->push_back(tl);

  // Members
  tl = {};
  ParseBlockMembersToTextLines(indent, 2, flatten_cbuffers, "", block_var.member_count, block_var.members, &tl.lines);
  tl.text_line_flags = TEXT_LINE_TYPE_LINES;
  p_text_lines->push_back(tl);    

  // End block
  tl = {};
  tl.indent = indent;
  tl.name = (block_var.name != nullptr) ? block_var.name : "<unnamed>";
  tl.absolute_offset = 0;
  tl.relative_offset = 0;
  tl.size = block_var.size;
  tl.padded_size = block_var.padded_size;
  tl.text_line_flags = TEXT_LINE_TYPE_BLOCK_END;
  p_text_lines->push_back(tl);
}

void FormatTextLines(const std::vector<TextLine>& text_lines, const char* indent, std::vector<TextLine>* p_formatted_lines)
{
  size_t modifier_width = 0;
  size_t type_name_width = 0;
  size_t name_width = 0;

  // Widths
  for (auto& tl : text_lines) {
    if (tl.text_line_flags != 0) {
      continue;
    }
    modifier_width = std::max<size_t>(modifier_width, tl.modifier.length());
    type_name_width = std::max<size_t>(type_name_width, tl.type_name.length());
    name_width = std::max<size_t>(name_width, tl.name.length());
  }

  // Output
  size_t n = text_lines.size();
  for (size_t i = 0; i < n; ++i) {
    auto& tl = text_lines[i];

    std::stringstream ss;
    if ((tl.text_line_flags == TEXT_LINE_TYPE_BLOCK_BEGIN) || (tl.text_line_flags == TEXT_LINE_TYPE_STRUCT_BEGIN)) {
      ss << indent;
      ss << tl.indent;
      ss << "struct ";
      ss << tl.type_name;
      ss << " {";
    }
    else if ((tl.text_line_flags == TEXT_LINE_TYPE_BLOCK_END) || (tl.text_line_flags == TEXT_LINE_TYPE_STRUCT_END)) {
      ss << indent;
      ss << tl.indent;
      ss << "} ";
      ss << tl.name;
      ss << ";";
    }
    else if (tl.text_line_flags == TEXT_LINE_TYPE_LINES) {
      FormatTextLines(tl.lines, indent, p_formatted_lines);
    }
    else {
      ss << indent;
      ss << tl.indent;
      if (modifier_width > 0) {
        ss << std::setw(modifier_width) << std::left << tl.modifier;
        ss << " ";
      }     
      ss << std::setw(type_name_width) << std::left << tl.type_name;
      ss << " ";
      ss << std::setw(name_width) << (tl.name + ";");
    }

    // Reuse the various strings to store the formatted texts
    TextLine out_tl = {};
    out_tl.formatted_line = ss.str();
    if (out_tl.formatted_line.length() > 0) {
      out_tl.array_stride = tl.array_stride;
      out_tl.text_line_flags = tl.text_line_flags;
      out_tl.formatted_absolute_offset = std::to_string(tl.absolute_offset);
      out_tl.formatted_relative_offset = std::to_string(tl.relative_offset);
      out_tl.formatted_size = std::to_string(tl.size);
      out_tl.formatted_padded_size = std::to_string(tl.padded_size);
      out_tl.formatted_array_stride = std::to_string(tl.array_stride);
      // Block variable flags
      if (tl.block_variable_flags != 0) {
        std::stringstream ss_flags;
        if (tl.block_variable_flags & SPV_REFLECT_VARIABLE_FLAGS_UNUSED) {
          ss_flags << "UNUSED";
        }
        out_tl.formatted_block_variable_flags = ss_flags.str();
      }
      p_formatted_lines->push_back(out_tl);
    }
  }
}

void StreamWriteTextLines(std::ostream& os, const char* indent, bool flatten_cbuffers, const std::vector<TextLine>& text_lines)
{
  std::vector<TextLine> formatted_lines;
  FormatTextLines(text_lines, indent, &formatted_lines);

  size_t line_width = 0;
  size_t offset_width = 0;
  size_t absolute_offset_width = 0;
  size_t size_width = 0;
  size_t padded_size_width = 0;
  size_t array_stride_width = 0;

  // Width
  for (auto& tl : formatted_lines) {
    if (tl.text_line_flags != 0) {
      continue;
    }
    line_width = std::max<size_t>(line_width, tl.formatted_line.length());
    absolute_offset_width = std::max<size_t>(absolute_offset_width, tl.formatted_absolute_offset.length());
    offset_width = std::max<size_t>(offset_width, tl.formatted_relative_offset.length());
    size_width = std::max<size_t>(size_width, tl.formatted_size.length());
    padded_size_width = std::max<size_t>(padded_size_width, tl.formatted_padded_size.length());
    array_stride_width = std::max<size_t>(array_stride_width, tl.formatted_array_stride.length());
  }

  size_t n = formatted_lines.size();
  for (size_t i = 0; i < n; ++i) {
    auto& tl = formatted_lines[i];

    if (tl.text_line_flags == TEXT_LINE_TYPE_BLOCK_BEGIN) {
      if (i > 0) {
        os << "\n";
      }

      size_t pos = tl.formatted_line.find_first_not_of(' ');
      if (pos != std::string::npos) {
        std::string s(pos, ' ');
        os << s << "//" << " ";
        os << "size = " << tl.formatted_size << ", ";
        os << "padded size = " << tl.formatted_padded_size;
        os << "\n";
      }

      os << std::setw(line_width) << std::left << tl.formatted_line;
    }
    else if (tl.text_line_flags == TEXT_LINE_TYPE_BLOCK_END) {
      os << std::setw(line_width) << std::left << tl.formatted_line;
      if (i < (n - 1)) {
        os << "\n";
      }
    }
    else if (tl.text_line_flags == TEXT_LINE_TYPE_STRUCT_BEGIN) {
      if (!flatten_cbuffers) {
        if (i > 0) {
          os << "\n";
        }

        size_t pos = tl.formatted_line.find_first_not_of(' ');
        if (pos != std::string::npos) {
          std::string s(pos, ' ');
          os << s << "//" << " ";
          os << "abs offset = " << tl.formatted_absolute_offset << ", ";
          os << "rel offset = " << tl.formatted_relative_offset << ", ";
          os << "size = " << tl.formatted_size << ", ";
          os << "padded size = " << tl.formatted_padded_size;
          if (tl.array_stride > 0) {
            os << ", ";
            os << "array stride = " << tl.formatted_array_stride;
          }
          if (!tl.formatted_block_variable_flags.empty()) {
            os << " ";
            os << tl.formatted_block_variable_flags;
          }
          os << "\n";
        }

        os << std::setw(line_width) << std::left << tl.formatted_line;
      }
    }
    else if (tl.text_line_flags == TEXT_LINE_TYPE_STRUCT_END) {
      if (!flatten_cbuffers) {
        os << std::setw(line_width) << std::left << tl.formatted_line;
        if (i < (n - 1)) {
          os << "\n";
        }
      }
    }
    else {
      os << std::setw(line_width) << std::left << tl.formatted_line;
      os << " " << "//" << " ";
      os << "abs offset = " << std::setw(absolute_offset_width) << std::right << tl.formatted_absolute_offset << ", ";
      if (!flatten_cbuffers) {
        os << "rel offset = " << std::setw(offset_width) << std::right << tl.formatted_relative_offset << ", ";
      }
      os << "size = " << std::setw(size_width) << std::right << tl.formatted_size << ", ";
      os << "padded size = " << std::setw(padded_size_width) << std::right << tl.formatted_padded_size;
      if (tl.array_stride > 0) {
        os << ", ";
        os << "array stride = " << std::setw(array_stride_width) << tl.formatted_array_stride;
      }
      if (!tl.formatted_block_variable_flags.empty()) {
        os << " ";
        os << tl.formatted_block_variable_flags;
      }
    }

    if (i < (n - 1)) {
      os << "\n";
    }
  }
}

void StreamWriteDescriptorBinding(std::ostream& os, const SpvReflectDescriptorBinding& obj, bool write_set, bool flatten_cbuffers, const char* indent)
{
  const char* t = indent;
  os << t << "spirv id : " << obj.spirv_id << "\n";
  if (write_set) {
    os << t << "set      : " << obj.set << "\n";
  }
  os << t << "binding  : " << obj.binding << "\n";
  os << t << "type     : " << ToStringDescriptorType(obj.descriptor_type);
  os << " " << "(" << ToStringResourceType(obj.resource_type) << ")" << "\n";

  // count
  os << t << "count    : " << obj.count << "\n";

  // array
  if (obj.array.dims_count > 0) {  
    os << t << "array    : ";
    for (uint32_t dim_index = 0; dim_index < obj.array.dims_count; ++dim_index) {
      os << "[" << obj.array.dims[dim_index] << "]";
    }
    os << "\n";
  }

  // counter
  if (obj.uav_counter_binding != nullptr) {
    os << t << "counter  : ";
    os << "(";
    os << "set=" << obj.uav_counter_binding->set << ", ";
    os << "binding=" << obj.uav_counter_binding->binding << ", ";
    os << "name=" << obj.uav_counter_binding->name;
    os << ");";
    os << "\n";
  }

  // accessed
  os << t << "accessed : " << (obj.accessed? "true" : "false") << "\n";

  os << t << "name     : " << ((obj.name != nullptr) ? obj.name : "<unnamed>");
  if ((obj.type_description->type_name != nullptr) && (strlen(obj.type_description->type_name) > 0)) {
    os << " " << "(" << obj.type_description->type_name << ")";
  }

  if (obj.descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
      obj.descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) {
    std::vector<TextLine> text_lines;
    ParseBlockVariableToTextLines("    ",  flatten_cbuffers, obj.block, &text_lines);
    if (!text_lines.empty()) {
      os << "\n";
      StreamWriteTextLines(os, t, flatten_cbuffers, text_lines);
      os << "\n";
    }
  }
}

void StreamWriteInterfaceVariable(std::ostream& os, const SpvReflectInterfaceVariable& obj, const char* indent)
{
  const char* t = indent;
  os << t << "spirv id  : " << obj.spirv_id << "\n";
  os << t << "location  : ";
  if (obj.decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) {
    os << "(built-in)" << " ";
    os << ToStringSpvBuiltIn(obj.built_in);
  }
  else {
    os << obj.location;
  }
  os << "\n";
  os << t << "type      : " << ToStringComponentType(*obj.type_description, 0) << "\n";

  // array
  if (obj.array.dims_count > 0) {  
    os << t << "array     : ";
    for (uint32_t dim_index = 0; dim_index < obj.array.dims_count; ++dim_index) {
      os << "[" << obj.array.dims[dim_index] << "]";
    }
    os << "\n";
  }

  os << t << "semantic  : " << (obj.semantic != NULL ? obj.semantic : "") << "\n";
  os << t << "name      : " << (obj.name != NULL ? obj.name : "") << "\n";
  os << t << "qualifier : ";
  if (obj.decoration_flags & SPV_REFLECT_DECORATION_FLAT) {
    os << "flat";
  }
  else   if (obj.decoration_flags & SPV_REFLECT_DECORATION_NOPERSPECTIVE) {
    os << "noperspective";
  }
}

void StreamWriteEntryPoint(std::ostream& os, const SpvReflectEntryPoint& obj, const char* indent)
{
  os << indent << "entry point     : " << obj.name;
  os << " (stage=" << ToStringShaderStage(obj.shader_stage) << ")";
  if (obj.shader_stage == SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT) {
    os << "\n";
    os << "local size      : " << "(" << obj.local_size.x << ", " << obj.local_size.y << ", " << obj.local_size.z << ")";
  }
}

void StreamWriteShaderModule(std::ostream& os, const SpvReflectShaderModule& obj, const char* indent)
{
  (void)indent;
  os << "generator       : " << ToStringGenerator(obj.generator) << "\n";
  os << "source lang     : " << spvReflectSourceLanguage(obj.source_language) << "\n";
  os << "source lang ver : " << obj.source_language_version << "\n";
  os << "source file     : " << (obj.source_file != NULL ? obj.source_file : "") << "\n";
  //os << "shader stage    : " << ToStringShaderStage(obj.shader_stage) << "\n";

  for (uint32_t i = 0; i < obj.entry_point_count; ++i) {
    StreamWriteEntryPoint(os, obj.entry_points[i], "");
    if (i < (obj.entry_point_count - 1)) {
        os << "\n";
    }
  }
}


// Avoid unused variable warning/error on Linux
#ifndef NDEBUG
#define USE_ASSERT(x) assert(x)
#else
#define USE_ASSERT(x) ((void)(x))
#endif

void WriteReflection(const spv_reflect::ShaderModule& obj, bool flatten_cbuffers, std::ostream& os)
{
  const char* t     = "  ";
  const char* tt    = "    ";
  const char* ttt   = "      ";

  StreamWriteShaderModule(os, obj.GetShaderModule(), "");

  uint32_t count = 0;
  std::vector<SpvReflectInterfaceVariable*> variables;
  std::vector<SpvReflectDescriptorBinding*> bindings;
  std::vector<SpvReflectDescriptorSet*> sets;

  count = 0;
  SpvReflectResult result = obj.EnumerateInputVariables(&count, nullptr);
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  variables.resize(count);
  result = obj.EnumerateInputVariables(&count, variables.data());
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  if (count > 0) {
    os << "\n";
    os << "\n";
    os << "\n";
    os << t << "Input variables: " << count << "\n\n";
    for (size_t i = 0; i < variables.size(); ++i) {
      auto p_var = variables[i];
      USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
      os << tt << i << ":" << "\n";
      StreamWriteInterfaceVariable(os, *p_var, ttt);
      if (i < (count - 1)) {
        os << "\n";
      }  
    }
  }

  count = 0;
  result = obj.EnumerateOutputVariables(&count, nullptr);
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  variables.resize(count);
  result = obj.EnumerateOutputVariables(&count, variables.data());
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  if (count > 0) {
    os << "\n";
    os << "\n";
    os << "\n";
    os << t << "Output variables: " << count << "\n\n";
    for (size_t i = 0; i < variables.size(); ++i) {
      auto p_var = variables[i];
      USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
      os << tt << i << ":" << "\n";
      StreamWriteInterfaceVariable(os, *p_var, ttt);
      if (i < (count - 1)) {
        os << "\n";
      }  
    }
  }

  count = 0;
  result = obj.EnumerateDescriptorBindings(&count, nullptr);
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  bindings.resize(count);
  result = obj.EnumerateDescriptorBindings(&count, bindings.data());
  USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
  std::sort(std::begin(bindings), std::end(bindings),
            [](SpvReflectDescriptorBinding* a, SpvReflectDescriptorBinding* b) -> bool {
              if (a->set != b->set) {
                return a->set < b->set;
              }
              return a->binding < b->binding;
            });
  if (count > 0) {
    os << "\n";
    os << "\n";
    os << "\n";
    os << t << "Descriptor bindings: " << count << "\n\n";
    for (size_t i = 0; i < bindings.size(); ++i) {
      auto p_binding = bindings[i];
      USE_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS);
      os << tt << "Binding" << " " << p_binding->set << "." << p_binding->binding << "" << "\n";
      StreamWriteDescriptorBinding(os, *p_binding, true, flatten_cbuffers, ttt);
      if (i < (count - 1)) {
        os << "\n\n";
      }  
    }
  }
}

//////////////////////////////////

SpvReflectToYaml::SpvReflectToYaml(const SpvReflectShaderModule& shader_module, uint32_t verbosity) :
  sm_(shader_module), verbosity_(verbosity)
{
}

void SpvReflectToYaml::WriteTypeDescription(std::ostream& os, const SpvReflectTypeDescription& td, uint32_t indent_level) {
  // YAML anchors can only refer to points earlier in the doc, so child type descriptions must
  // be processed before the parent.
  for(uint32_t i=0; i<td.member_count; ++i) {
    WriteTypeDescription(os, td.members[i], indent_level);
  }
  const std::string t0 = Indent(indent_level);
  const std::string t1 = Indent(indent_level+1);
  const std::string t2 = Indent(indent_level+2);
  const std::string t3 = Indent(indent_level+3);
  const std::string t4 = Indent(indent_level+4);

  // Determine the index of this type within the shader module's list.
  assert(type_description_to_index_.find(&td) == type_description_to_index_.end());
  uint32_t type_description_index = static_cast<uint32_t>(type_description_to_index_.size());
  type_description_to_index_[&td] = type_description_index;

  os << t0 << "- &td" << type_description_index << std::endl;
  // typedef struct SpvReflectTypeDescription {
  //   uint32_t                          id;
  os << t1 << "id: " << td.id << std::endl;
  //   SpvOp                             op;
  os << t1 << "op: " << td.op << std::endl;
  //   const char*                       type_name;
  os << t1 << "type_name: " << SafeString(td.type_name) << std::endl;
  //   const char*                       struct_member_name;
  os << t1 << "struct_member_name: " << SafeString(td.struct_member_name) << std::endl;
  //   SpvStorageClass                   storage_class;
  os << t1 << "storage_class: " << td.storage_class << " # " << ToStringSpvStorageClass(td.storage_class) << std::endl;
  //   SpvReflectTypeFlags               type_flags;
  os << t1 << "type_flags: " << AsHexString(td.type_flags) << " # " << ToStringTypeFlags(td.type_flags) << std::endl;
  //   SpvReflectDecorationFlags         decoration_flags;
  os << t1 << "decoration_flags: " << AsHexString(td.decoration_flags) << " # " << ToStringDecorationFlags(td.decoration_flags) << std::endl;
  //   struct Traits {
  os << t1 << "traits:" << std::endl;
  //     SpvReflectNumericTraits         numeric;
  // typedef struct SpvReflectNumericTraits {
  os << t2 << "numeric:" << std::endl;
  //   struct Scalar {
  //     uint32_t                        width;
  //     uint32_t                        signedness;
  //   } scalar;
  os << t3 << "scalar: { ";
  os << "width: " << td.traits.numeric.scalar.width << ", ";
  os << "signedness: " << td.traits.numeric.scalar.signedness;
  os << " }" << std::endl;
  //   struct Vector {
  //     uint32_t                        component_count;
  //   } vector;
  os << t3 << "vector: { ";
  os << "component_count: " << td.traits.numeric.vector.component_count;
  os << " }" << std::endl;
  //   struct Matrix {
  //     uint32_t                        column_count;
  //     uint32_t                        row_count;
  //     uint32_t                        stride; // Measured in bytes
  //   } matrix;
  os << t3 << "matrix: { ";
  os << "column_count: " << td.traits.numeric.matrix.column_count << ", ";
  os << "row_count: " << td.traits.numeric.matrix.row_count << ", ";;
  os << "stride: " << td.traits.numeric.matrix.stride;
  os << " }" << std::endl;
  // } SpvReflectNumericTraits;

  //     SpvReflectImageTraits           image;
  os << t2 << "image: { ";
  // typedef struct SpvReflectImageTraits {
  //   SpvDim                            dim;
  os << "dim: " << td.traits.image.dim << ", ";
  //   uint32_t                          depth;
  os << "depth: " << td.traits.image.depth << ", ";
  //   uint32_t                          arrayed;
  os << "arrayed: " << td.traits.image.arrayed << ", ";
  //   uint32_t                          ms;
  os << "ms: " << td.traits.image.ms << ", ";
  //   uint32_t                          sampled;
  os << "sampled: " << td.traits.image.sampled << ", ";
  //   SpvImageFormat                    image_format;
  os << "image_format: " << td.traits.image.image_format;
  // } SpvReflectImageTraits;
  os << " }" << " # dim=" << ToStringSpvDim(td.traits.image.dim) << " image_format=" << ToStringSpvImageFormat(td.traits.image.image_format) << std::endl;

  //     SpvReflectArrayTraits           array;
  os << t2 << "array: { ";
  // typedef struct SpvReflectArrayTraits {
  //   uint32_t                          dims_count;
  os << "dims_count: " << td.traits.array.dims_count << ", ";
  //   uint32_t                          dims[SPV_REFLECT_MAX_ARRAY_DIMS];
  os << "dims: [";
  for(uint32_t i_dim=0; i_dim < td.traits.array.dims_count; ++i_dim) {
    os << td.traits.array.dims[i_dim] << ",";
  }
  os << "], ";
  //   uint32_t                          stride; // Measured in bytes
  os << "stride: " << td.traits.array.stride;
  // } SpvReflectArrayTraits;
  os << " }" << std::endl;
  //   } traits;

  //   uint32_t                          member_count;
  os << t1 << "member_count: " << td.member_count << std::endl;
  //   struct SpvReflectTypeDescription* members;
  os << t1 << "members:" << std::endl;
  for(uint32_t i_member=0; i_member < td.member_count; ++i_member) {
    os << t2 << "- *td" << type_description_to_index_[&(td.members[i_member])] << std::endl;
  }
  // } SpvReflectTypeDescription;
}

void SpvReflectToYaml::WriteBlockVariable(std::ostream& os, const SpvReflectBlockVariable& bv, uint32_t indent_level) {
  for(uint32_t i=0; i<bv.member_count; ++i) {
    WriteBlockVariable(os, bv.members[i], indent_level);
  }

  const std::string t0 = Indent(indent_level);
  const std::string t1 = Indent(indent_level+1);
  const std::string t2 = Indent(indent_level+2);
  const std::string t3 = Indent(indent_level+3);

  assert(block_variable_to_index_.find(&bv) == block_variable_to_index_.end());
  uint32_t block_variable_index = static_cast<uint32_t>(block_variable_to_index_.size());
  block_variable_to_index_[&bv] = block_variable_index;

  os << t0 << "- &bv" << block_variable_index << std::endl;
  // typedef struct SpvReflectBlockVariable {
  //   const char*                       name;
  os << t1 << "name: " << SafeString(bv.name) << std::endl;
  //   uint32_t                          offset;           // Measured in bytes
  os << t1 << "offset: " << bv.offset << std::endl;
  //   uint32_t                          absolute_offset;  // Measured in bytes
  os << t1 << "absolute_offset: " << bv.absolute_offset << std::endl;
  //   uint32_t                          size;             // Measured in bytes
  os << t1 << "size: " << bv.size << std::endl;
  //   uint32_t                          padded_size;      // Measured in bytes
  os << t1 << "padded_size: " << bv.padded_size << std::endl;
  //   SpvReflectDecorationFlags         decoration_flags;
  os << t1 << "decorations: " << AsHexString(bv.decoration_flags) << " # " << ToStringDecorationFlags(bv.decoration_flags) << std::endl;
  //   SpvReflectNumericTraits           numeric;
  // typedef struct SpvReflectNumericTraits {
  os << t1 << "numeric:" << std::endl;
  //   struct Scalar {
  //     uint32_t                        width;
  //     uint32_t                        signedness;
  //   } scalar;
  os << t2 << "scalar: { ";
  os << "width: " << bv.numeric.scalar.width << ", ";
  os << "signedness: " << bv.numeric.scalar.signedness << " }" << std::endl;
  //   struct Vector {
  //     uint32_t                        component_count;
  //   } vector;
  os << t2 << "vector: { ";
  os << "component_count: " << bv.numeric.vector.component_count << " }" << std::endl;
  //   struct Matrix {
  //     uint32_t                        column_count;
  //     uint32_t                        row_count;
  //     uint32_t                        stride; // Measured in bytes
  //   } matrix;
  os << t2 << "matrix: { ";
  os << "column_count: " << bv.numeric.matrix.column_count << ", ";
  os << "row_count: " << bv.numeric.matrix.row_count << ", ";;
  os << "stride: " << bv.numeric.matrix.stride << " }" << std::endl;
  // } SpvReflectNumericTraits;

  //     SpvReflectArrayTraits           array;
  os << t1 << "array: { ";
  // typedef struct SpvReflectArrayTraits {
  //   uint32_t                          dims_count;
  os << "dims_count: " << bv.array.dims_count << ", ";
  //   uint32_t                          dims[SPV_REFLECT_MAX_ARRAY_DIMS];
  os << "dims: [";
  for(uint32_t i_dim=0; i_dim < bv.array.dims_count; ++i_dim) {
    os << bv.array.dims[i_dim] << ",";
  }
  os << "], ";
  //   uint32_t                          stride; // Measured in bytes
  os << "stride: " << bv.array.stride;
  // } SpvReflectArrayTraits;
  os << " }" << std::endl;

  //   uint32_t                          member_count;
  os << t1 << "member_count: " << bv.member_count << std::endl;
  //   struct SpvReflectBlockVariable*   members;
  os << t1 << "members:" << std::endl;
  for(uint32_t i=0; i<bv.member_count; ++i) {
    auto itor = block_variable_to_index_.find(&bv.members[i]);
    assert(itor != block_variable_to_index_.end());
    os << t2 << "- *bv" << itor->second << std::endl;
  }
  if (verbosity_ >= 1) {
    //   SpvReflectTypeDescription*        type_description;
    if (bv.type_description == nullptr) {
      os << t1 << "type_description:" << std::endl;
    } else {
      auto itor = type_description_to_index_.find(bv.type_description);
      assert(itor != type_description_to_index_.end());
      os << t1 << "type_description: *td" << itor->second << std::endl;
    }
  }
  // } SpvReflectBlockVariable;
}

void SpvReflectToYaml::WriteDescriptorBinding(std::ostream& os, const SpvReflectDescriptorBinding& db, uint32_t indent_level) {
  if (db.uav_counter_binding != nullptr) {
    auto itor = descriptor_binding_to_index_.find(db.uav_counter_binding);
    if (itor == descriptor_binding_to_index_.end()) {
      WriteDescriptorBinding(os, *(db.uav_counter_binding), indent_level);
    }
  }

  const std::string t0 = Indent(indent_level);
  const std::string t1 = Indent(indent_level+1);
  const std::string t2 = Indent(indent_level+2);
  const std::string t3 = Indent(indent_level+3);

  // A binding's UAV binding later may appear later in the table than the binding itself,
  // in which case we've already output entries for both bindings, and can just write another
  // reference here.
  {
    auto itor = descriptor_binding_to_index_.find(&db);
    if (itor != descriptor_binding_to_index_.end()) {
      os << t0 << "- *db" << itor->second << std::endl;
      return;
    }
  }

  uint32_t descriptor_binding_index = static_cast<uint32_t>(descriptor_binding_to_index_.size());
  descriptor_binding_to_index_[&db] = descriptor_binding_index;

  os << t0 << "- &db" << descriptor_binding_index << std::endl;
  // typedef struct SpvReflectDescriptorBinding {
  //   uint32_t                            spirv_id;
  os << t1 << "spirv_id: " << db.spirv_id << std::endl;
  //   const char*                         name;
  os << t1 << "name: " << SafeString(db.name) << std::endl;
  //   uint32_t                            binding;
  os << t1 << "binding: " << db.binding << std::endl;
  //   uint32_t                            input_attachment_index;
  os << t1 << "input_attachment_index: " << db.input_attachment_index << std::endl;
  //   uint32_t                            set;
  os << t1 << "set: " << db.set << std::endl;
  //   SpvReflectDescriptorType            descriptor_type;
  os << t1 << "descriptor_type: " << db.descriptor_type << " # " << ToStringDescriptorType(db.descriptor_type) << std::endl;
  //   SpvReflectResourceType              resource_type;
  os << t1 << "resource_type: " << db.resource_type << " # " << ToStringResourceType(db.resource_type) << std::endl;
  //   SpvReflectImageTraits           image;
  os << t1 << "image: { ";
  // typedef struct SpvReflectImageTraits {
  //   SpvDim                            dim;
  os << "dim: " << db.image.dim << ", ";
  //   uint32_t                          depth;
  os << "depth: " << db.image.depth << ", ";
  //   uint32_t                          arrayed;
  os << "arrayed: " << db.image.arrayed << ", ";
  //   uint32_t                          ms;
  os << "ms: " << db.image.ms << ", ";
  //   uint32_t                          sampled;
  os << "sampled: " << db.image.sampled << ", ";
  //   SpvImageFormat                    image_format;
  os << "image_format: " << db.image.image_format;
  // } SpvReflectImageTraits;
  os << " }" << " # dim=" << ToStringSpvDim(db.image.dim) << " image_format=" << ToStringSpvImageFormat(db.image.image_format) << std::endl;

  //   SpvReflectBlockVariable             block;
  {
    auto itor = block_variable_to_index_.find(&db.block);
    assert(itor != block_variable_to_index_.end());
    os << t1 << "block: *bv" << itor->second << " # " << SafeString(db.block.name) << std::endl;
  }
  //   SpvReflectBindingArrayTraits        array;
  os << t1 << "array: { ";
  // typedef struct SpvReflectBindingArrayTraits {
  //   uint32_t                          dims_count;
  os << "dims_count: " << db.array.dims_count << ", ";
  //   uint32_t                          dims[SPV_REFLECT_MAX_ARRAY_DIMS];
  os << "dims: [";
  for(uint32_t i_dim=0; i_dim < db.array.dims_count; ++i_dim) {
    os << db.array.dims[i_dim] << ",";
  }
  // } SpvReflectBindingArrayTraits;
  os << "] }" << std::endl;

  //   uint32_t                            accessed;
  os << t1 << "accessed: " << db.accessed << std::endl;

  //   uint32_t                            uav_counter_id;
  os << t1 << "uav_counter_id: " << db.uav_counter_id << std::endl;
  //   struct SpvReflectDescriptorBinding* uav_counter_binding;
  if (db.uav_counter_binding == nullptr) {
    os << t1 << "uav_counter_binding:" << std::endl;
  } else {
    auto itor = descriptor_binding_to_index_.find(db.uav_counter_binding);
    assert(itor != descriptor_binding_to_index_.end());
    os << t1 << "uav_counter_binding: *db" << itor->second << " # " << SafeString(db.uav_counter_binding->name) << std::endl;
  }
  if (verbosity_ >= 1) {
    //   SpvReflectTypeDescription*        type_description;
    if (db.type_description == nullptr) {
      os << t1 << "type_description:" << std::endl;
    } else {
      auto itor = type_description_to_index_.find(db.type_description);
      assert(itor != type_description_to_index_.end());
      os << t1 << "type_description: *td" << itor->second << std::endl;
    }
  }
  //   struct {
  //     uint32_t                        binding;
  //     uint32_t                        set;
  //   } word_offset;
  os << t1 << "word_offset: { binding: " << db.word_offset.binding;
  os << ", set: " << db.word_offset.set << " }" << std::endl;
  // } SpvReflectDescriptorBinding;
}

void SpvReflectToYaml::WriteInterfaceVariable(std::ostream& os, const SpvReflectInterfaceVariable& iv, uint32_t indent_level) {
  for(uint32_t i=0; i<iv.member_count; ++i) {
    assert(interface_variable_to_index_.find(&iv.members[i]) == interface_variable_to_index_.end());
    WriteInterfaceVariable(os, iv.members[i], indent_level);
  }

  const std::string t0 = Indent(indent_level);
  const std::string t1 = Indent(indent_level+1);
  const std::string t2 = Indent(indent_level+2);
  const std::string t3 = Indent(indent_level+3);

  uint32_t interface_variable_index = static_cast<uint32_t>(interface_variable_to_index_.size());
  interface_variable_to_index_[&iv] = interface_variable_index;

  // typedef struct SpvReflectInterfaceVariable {
  os << t0 << "- &iv" << interface_variable_index << std::endl;
  //   uint32_t                            spirv_id;
  os << t1 << "spirv_id: " << iv.spirv_id << std::endl;
  //   const char*                         name;
  os << t1 << "name: " << SafeString(iv.name) << std::endl;
  //   uint32_t                            location;
  os << t1 << "location: " << iv.location << std::endl;
  //   SpvStorageClass                     storage_class;
  os << t1 << "storage_class: " << iv.storage_class << " # " << ToStringSpvStorageClass(iv.storage_class) << std::endl;
  //   const char*                         semantic;
  os << t1 << "semantic: " << SafeString(iv.semantic) << std::endl;
  //   SpvReflectDecorationFlags           decoration_flags;
  os << t1 << "decoration_flags: " << AsHexString(iv.decoration_flags) << " # " << ToStringDecorationFlags(iv.decoration_flags)  << std::endl;
  //   SpvBuiltIn                          built_in;
  os << t1 << "built_in: " << iv.built_in << " # " << ToStringSpvBuiltIn(iv.built_in) << std::endl;
  //   SpvReflectNumericTraits             numeric;
  // typedef struct SpvReflectNumericTraits {
  os << t1 << "numeric:" << std::endl;
  //   struct Scalar {
  //     uint32_t                        width;
  //     uint32_t                        signedness;
  //   } scalar;
  os << t2 << "scalar: { ";
  os << "width: " << iv.numeric.scalar.width << ", ";
  os << "signedness: " << iv.numeric.scalar.signedness << " }" << std::endl;
  //   struct Vector {
  //     uint32_t                        component_count;
  //   } vector;
  os << t2 << "vector: { ";
  os << "component_count: " << iv.numeric.vector.component_count << " }" << std::endl;
  //   struct Matrix {
  //     uint32_t                        column_count;
  //     uint32_t                        row_count;
  //     uint32_t                        stride; // Measured in bytes
  //   } matrix;
  os << t2 << "matrix: { ";
  os << "column_count: " << iv.numeric.matrix.column_count << ", ";
  os << "row_count: " << iv.numeric.matrix.row_count << ", ";;
  os << "stride: " << iv.numeric.matrix.stride << " }" << std::endl;
  // } SpvReflectNumericTraits;

  //     SpvReflectArrayTraits           array;
  os << t1 << "array: { ";
  // typedef struct SpvReflectArrayTraits {
  //   uint32_t                          dims_count;
  os << "dims_count: " << iv.array.dims_count << ", ";
  //   uint32_t                          dims[SPV_REFLECT_MAX_ARRAY_DIMS];
  os << "dims: [";
  for(uint32_t i_dim=0; i_dim < iv.array.dims_count; ++i_dim) {
    os << iv.array.dims[i_dim] << ",";
  }
  os << "], ";
  //   uint32_t                          stride; // Measured in bytes
  os << "stride: " << iv.array.stride;
  // } SpvReflectArrayTraits;
  os << " }" << std::endl;

  //   uint32_t                            member_count;
  os << t1 << "member_count: " << iv.member_count << std::endl;
  //   struct SpvReflectInterfaceVariable* members;
  os << t1 << "members:" << std::endl;
  for(uint32_t i=0; i<iv.member_count; ++i) {
    auto itor = interface_variable_to_index_.find(&iv.members[i]);
    assert(itor != interface_variable_to_index_.end());
    os << t2 << "- *iv" << itor->second << " # " << SafeString(iv.members[i].name) << std::endl;
  }

  //   SpvReflectFormat                    format;
  os << t1 << "format: " << iv.format << " # " << ToStringFormat(iv.format) << std::endl;

  if (verbosity_ >= 1) {
    //   SpvReflectTypeDescription*        type_description;
    if (!iv.type_description) {
      os << t1 << "type_description:" << std::endl;
    } else {
      auto itor = type_description_to_index_.find(iv.type_description);
      assert(itor != type_description_to_index_.end());
      os << t1 << "type_description: *td" << itor->second << std::endl;
    }
  }

  //   struct {
  //     uint32_t                        location;
  //   } word_offset;
  os << t1 << "word_offset: { location: " << iv.word_offset.location << " }" << std::endl;

  // } SpvReflectInterfaceVariable;
}

void SpvReflectToYaml::WriteBlockVariableTypes(std::ostream& os, const SpvReflectBlockVariable& bv, uint32_t indent_level) {
  const auto* td = bv.type_description;
  if (td && type_description_to_index_.find(td) == type_description_to_index_.end()) {
    WriteTypeDescription(os, *td, indent_level);
  }

  for(uint32_t i=0; i<bv.member_count; ++i) {
    WriteBlockVariableTypes(os, bv.members[i], indent_level);
  }
}
void SpvReflectToYaml::WriteDescriptorBindingTypes(std::ostream& os, const SpvReflectDescriptorBinding& db, uint32_t indent_level) {
  WriteBlockVariableTypes(os, db.block, indent_level);

  if (db.uav_counter_binding) {
    WriteDescriptorBindingTypes(os, *(db.uav_counter_binding), indent_level);
  }

  const auto* td = db.type_description;
  if (td && type_description_to_index_.find(td) == type_description_to_index_.end()) {
    WriteTypeDescription(os, *td, indent_level);
  }
}
void SpvReflectToYaml::WriteInterfaceVariableTypes(std::ostream& os, const SpvReflectInterfaceVariable& iv, uint32_t indent_level) {
  const auto* td = iv.type_description;
  if (td && type_description_to_index_.find(td) == type_description_to_index_.end()) {
    WriteTypeDescription(os, *td, indent_level);
  }

  for(uint32_t i=0; i<iv.member_count; ++i) {
    WriteInterfaceVariableTypes(os, iv.members[i], indent_level);
  }
}


void SpvReflectToYaml::Write(std::ostream& os)
{
  if (!sm_._internal) {
    return;
  }

  uint32_t indent_level = 0;
  const std::string t0 = Indent(indent_level);
  const std::string t1 = Indent(indent_level+1);
  const std::string t2 = Indent(indent_level+2);
  const std::string t3 = Indent(indent_level+3);

  os << "%YAML 1.0" << std::endl;
  os << "---" << std::endl;

  type_description_to_index_.clear();
  if (verbosity_ >= 2) {
    os << t0 << "all_type_descriptions:" << std::endl;
    // Write the entire internal type_description table; all type descriptions are
    // reachable from there, though most of them are purely internal & not referenced
    // by any of the public-facing structures.
    for(size_t i=0; i<sm_._internal->type_description_count; ++i) {
      WriteTypeDescription(os, sm_._internal->type_descriptions[i], indent_level+1);
    }
  } else if (verbosity_ >= 1) {
    os << t0 << "all_type_descriptions:" << std::endl;
    // Iterate through all public-facing structures and write any type descriptions
    // we find (and their children).
    for(uint32_t i=0; i<sm_.descriptor_binding_count; ++i) {
      WriteDescriptorBindingTypes(os, sm_.descriptor_bindings[i], indent_level+1);
    }
    for(uint32_t i=0; i<sm_.push_constant_block_count; ++i) {
      WriteBlockVariableTypes(os, sm_.push_constant_blocks[i], indent_level+1);
    }
    for(uint32_t i=0; i<sm_.input_variable_count; ++i) {
      WriteInterfaceVariableTypes(os, *sm_.input_variables[i], indent_level+1);
    }
    for(uint32_t i=0; i<sm_.output_variable_count; ++i) {
      WriteInterfaceVariableTypes(os, *sm_.output_variables[i], indent_level+1);
    }
  }

  block_variable_to_index_.clear();
  os << t0 << "all_block_variables:" << std::endl;
  for(uint32_t i=0; i<sm_.descriptor_binding_count; ++i) {
    WriteBlockVariable(os, sm_.descriptor_bindings[i].block, indent_level+1);
  }
  for(uint32_t i=0; i<sm_.push_constant_block_count; ++i) {
    WriteBlockVariable(os, sm_.push_constant_blocks[i], indent_level+1);
  }

  descriptor_binding_to_index_.clear();
  os << t0 << "all_descriptor_bindings:" << std::endl;
  for(uint32_t i=0; i<sm_.descriptor_binding_count; ++i) {
    WriteDescriptorBinding(os, sm_.descriptor_bindings[i], indent_level+1);
  }

  interface_variable_to_index_.clear();
  os << t0 << "all_interface_variables:" << std::endl;
  for(uint32_t i=0; i<sm_.input_variable_count; ++i) {
    WriteInterfaceVariable(os, *sm_.input_variables[i], indent_level+1);
  }
  for(uint32_t i=0; i<sm_.output_variable_count; ++i) {
    WriteInterfaceVariable(os, *sm_.output_variables[i], indent_level+1);
  }

  // struct SpvReflectShaderModule {
  os << t0 << "module:" << std::endl;
  // uint16_t                          generator;
  os << t1 << "generator: " << sm_.generator << " # " << ToStringGenerator(sm_.generator) << std::endl;
  // const char*                       entry_point_name;
  os << t1 << "entry_point_name: " << SafeString(sm_.entry_point_name) << std::endl;
  // uint32_t                          entry_point_id;
  os << t1 << "entry_point_id: " << sm_.entry_point_id << std::endl;
  // SpvSourceLanguage                 source_language;
  os << t1 << "source_language: " << sm_.source_language << " # " << ToStringSpvSourceLanguage(sm_.source_language) << std::endl;
  // uint32_t                          source_language_version;
  os << t1 << "source_language_version: " << sm_.source_language_version << std::endl;
  // SpvExecutionModel                 spirv_execution_model;
  os << t1 << "spirv_execution_model: " << sm_.spirv_execution_model << " # " << ToStringSpvExecutionModel(sm_.spirv_execution_model) << std::endl;
  // SpvShaderStageFlagBits             shader_stage;
  os << t1 << "shader_stage: " << AsHexString(sm_.shader_stage) << " # " << ToStringShaderStage(sm_.shader_stage) << std::endl;
  // uint32_t                          descriptor_binding_count;
  os << t1 << "descriptor_binding_count: " << sm_.descriptor_binding_count << std::endl;
  // SpvReflectDescriptorBinding*      descriptor_bindings;
  os << t1 << "descriptor_bindings:" << std::endl;
  for(uint32_t i=0; i<sm_.descriptor_binding_count; ++i) {
    auto itor = descriptor_binding_to_index_.find(&sm_.descriptor_bindings[i]);
    assert(itor != descriptor_binding_to_index_.end());
    os << t2 << "- *db" << itor->second << " # " << SafeString(sm_.descriptor_bindings[i].name) << std::endl;
  }
  // uint32_t                          descriptor_set_count;
  os << t1 << "descriptor_set_count: " << sm_.descriptor_set_count << std::endl;
  // SpvReflectDescriptorSet descriptor_sets[SPV_REFLECT_MAX_DESCRIPTOR_SETS];
  os << t1 << "descriptor_sets:" << std::endl;
  for(uint32_t i_set=0; i_set<sm_.descriptor_set_count; ++i_set) {
    // typedef struct SpvReflectDescriptorSet {
    const auto& dset = sm_.descriptor_sets[i_set];
    //   uint32_t                          set;
    os << t1 << "- " << "set: " << dset.set << std::endl;
    //   uint32_t                          binding_count;
    os << t2 << "binding_count: " << dset.binding_count << std::endl;
    //   SpvReflectDescriptorBinding**     bindings;
    os << t2 << "bindings:" << std::endl;
    for(uint32_t i_binding=0; i_binding < dset.binding_count; ++i_binding) {
      auto itor = descriptor_binding_to_index_.find(dset.bindings[i_binding]);
      assert(itor != descriptor_binding_to_index_.end());
      os << t3 << "- *db" << itor->second << " # " << SafeString(dset.bindings[i_binding]->name) << std::endl;
    }
    // } SpvReflectDescriptorSet;

  }
  // uint32_t                          input_variable_count;
  os << t1 << "input_variable_count: " << sm_.input_variable_count << ",\n";
  // SpvReflectInterfaceVariable*      input_variables;
  os << t1 << "input_variables:" << std::endl;
  for(uint32_t i=0; i < sm_.input_variable_count; ++i) {
    auto itor = interface_variable_to_index_.find(sm_.input_variables[i]);
    assert(itor != interface_variable_to_index_.end());
    os << t2 << "- *iv" << itor->second << " # " << SafeString(sm_.input_variables[i]->name) << std::endl;
  }
  // uint32_t                          output_variable_count;
  os << t1 << "output_variable_count: " << sm_.output_variable_count << ",\n";
  // SpvReflectInterfaceVariable*      output_variables;
  os << t1 << "output_variables:" << std::endl;
  for(uint32_t i=0; i < sm_.output_variable_count; ++i) {
    auto itor = interface_variable_to_index_.find(sm_.output_variables[i]);
    assert(itor != interface_variable_to_index_.end());
    os << t2 << "- *iv" << itor->second << " # " << SafeString(sm_.output_variables[i]->name) << std::endl;
  }
  // uint32_t                          push_constant_count;
  os << t1 << "push_constant_count: " << sm_.push_constant_block_count << ",\n";
  // SpvReflectBlockVariable*          push_constants;
  os << t1 << "push_constants:" << std::endl;
  for(uint32_t i=0; i<sm_.push_constant_block_count; ++i) {
    auto itor = block_variable_to_index_.find(&sm_.push_constant_blocks[i]);
    assert(itor != block_variable_to_index_.end());
    os << t2 << "- *bv" << itor->second << " # " << SafeString(sm_.push_constant_blocks[i].name) << std::endl;
  }

  if (verbosity_ >= 2) {
    // struct Internal {
    os << t1 << "_internal:" << std::endl;
    if (sm_._internal) {
      //   size_t                          spirv_size;
      os << t2 << "spirv_size: " << sm_._internal->spirv_size << std::endl;
      //   uint32_t*                       spirv_code;
      os << t2 << "spirv_code: [";
      for(size_t i=0; i < sm_._internal->spirv_word_count; ++i) {
        if ((i % 6) == 0) {
          os << std::endl << t3;
        }
        os << AsHexString(sm_._internal->spirv_code[i]) << ",";
      }
      os << "]" << std::endl;
      //   uint32_t                        spirv_word_count;
      os << t2 << "spirv_word_count: " << sm_._internal->spirv_word_count << std::endl;
      //   size_t                          type_description_count;
      os << t2 << "type_description_count: " << sm_._internal->type_description_count << std::endl;
      //   SpvReflectTypeDescription*      type_descriptions;
      os << t2 << "type_descriptions:" << std::endl;
      for(uint32_t i=0; i<sm_._internal->type_description_count; ++i) {
        auto itor = type_description_to_index_.find(&sm_._internal->type_descriptions[i]);
        assert(itor != type_description_to_index_.end());
        os << t3 << "- *td" << itor->second << std::endl;
      }
    }
    // } * _internal;
  }

  os << "..." << std::endl;
}
