/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2021 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include <QApplication>
#include <QRegularExpression>
#include <QtMath>
#include "QRDUtils.h"

struct StructFormatData
{
  ShaderConstant structDef;
  uint32_t pointerTypeId = 0;
  uint32_t offset = 0;
};

GraphicsAPI BufferFormatter::m_API;

ShaderConstant BufferFormatter::ParseFormatString(const QString &formatString, uint64_t maxLen,
                                                  bool tightPacking, QString &errors)
{
  StructFormatData root;
  StructFormatData *cur = &root;

  QMap<QString, StructFormatData> structelems;
  QString lastStruct;

  // regex doesn't account for trailing or preceeding whitespace, or comments

  QRegularExpression regExpr(
      lit("^"                                     // start of the line
          "(row_major\\s+|column_major\\s+)?"     // row_major matrix
          "(rgb\\s+)?"                            // rgb element colourising
          "("                                     // group the options for the type
          "uintten|unormten"                      // R10G10B10A2 types
          "|floateleven"                          // R11G11B10 special type
          "|unormh|unormb"                        // UNORM 16-bit and 8-bit types
          "|snormh|snormb"                        // SNORM 16-bit and 8-bit types
          "|bool"                                 // bool is stored as 4-byte int
          "|byte|short|int|long"                  // signed ints
          "|ubyte|ushort|uint|ulong"              // unsigned ints
          "|xbyte|xshort|xint|xlong"              // hex ints
          "|half|float|double"                    // float types
          "|vec|uvec|ivec|dvec"                   // OpenGL vector types
          "|mat|umat|imat|dmat"                   // OpenGL matrix types
          ")"                                     // end of the type group
          "([1-9])?"                              // might be a vector
          "(x[1-9])?"                             // or a matrix
          "(\\s+[A-Za-z@_][A-Za-z0-9@_]*)?"       // get identifier name
          "(\\s*\\[[0-9]+\\])?"                   // optional array dimension
          "(\\s*:\\s*[A-Za-z_][A-Za-z0-9_]*)?"    // optional semantic
          "$"));

  bool success = true;
  errors = QString();

  QString text = formatString;

  QRegularExpression c_comments(lit("/\\*[^*]*\\*+(?:[^*/][^*]*\\*+)*/"));
  QRegularExpression cpp_comments(lit("//.*"));
  // remove all comments
  text = text.replace(c_comments, QString()).replace(cpp_comments, QString());
  // ensure braces are forced onto separate lines so we can parse them
  text = text.replace(QLatin1Char('{'), lit("\n{\n")).replace(QLatin1Char('}'), lit("\n}\n"));

  QRegularExpression structDeclRegex(lit("^struct\\s+([A-Za-z_][A-Za-z0-9_]*)$"));
  QRegularExpression structUseRegex(
      lit("^"                                 // start of the line
          "([A-Za-z_][A-Za-z0-9_]*)"          // struct type name
          "(\\*)?"                            // maybe a pointer
          "\\s+([A-Za-z@_][A-Za-z0-9@_]*)"    // variable name
          "(\\s*\\[[0-9]+\\])?"               // optional array dimension
          "$"));

  // get each line and parse it to determine the format the user wanted
  for(QString &l : text.split(QRegularExpression(lit("[;\n\r]"))))
  {
    QString line = l.trimmed();

    if(line.isEmpty())
      continue;

    if(cur == &root)
    {
      // if we're not in a struct, ignore the braces
      if(line == lit("{") || line == lit("}"))
        continue;
    }
    else
    {
      // if we're in a struct, ignore the opening brace and revert back to root elements when we hit
      // the closing brace. No brace nesting is supported
      if(line == lit("{"))
        continue;

      if(line == lit("}"))
      {
        cur->structDef.type.descriptor.arrayByteStride = cur->offset;

        // struct strides are aligned up to float4 boundary
        if(!tightPacking)
          cur->structDef.type.descriptor.arrayByteStride = (cur->offset + 0xFU) & (~0xFU);

        cur->pointerTypeId = PointerTypeRegistry::GetTypeID(cur->structDef.type);

        cur = &root;
        continue;
      }
    }

    if(line.contains(lit("struct")))
    {
      QRegularExpressionMatch match = structDeclRegex.match(line);

      if(match.hasMatch())
      {
        lastStruct = match.captured(1);

        if(structelems.contains(lastStruct))
        {
          errors = tr("Duplicate struct definition: %1\n").arg(lastStruct);
          success = false;
          break;
        }

        cur = &structelems[lastStruct];
        cur->structDef.type.descriptor.name = lastStruct;
        continue;
      }
    }

    ShaderConstant el;

    QRegularExpressionMatch structMatch = structUseRegex.match(line);

    if(structMatch.hasMatch() && structelems.contains(structMatch.captured(1)))
    {
      StructFormatData &structContext = structelems[structMatch.captured(1)];

      bool isPointer = !structMatch.captured(2).trimmed().isEmpty();

      QString varName = structMatch.captured(3);

      QString arrayDim = structMatch.captured(4).trimmed();
      uint32_t arrayCount = 1;
      if(!arrayDim.isEmpty())
      {
        arrayDim = arrayDim.mid(1, arrayDim.count() - 2);
        bool ok = false;
        arrayCount = arrayDim.toUInt(&ok);
        if(!ok)
          arrayCount = 1;
      }

      if(isPointer)
      {
        // if not tight packing, align up to pointer size
        if(!tightPacking)
          cur->offset = (cur->offset + 0x7) & (~0x7);

        el.name = varName;
        el.byteOffset = cur->offset;
        el.type.descriptor.pointerTypeID = structContext.pointerTypeId;
        el.type.descriptor.type = VarType::ULong;
        el.type.descriptor.displayAsHex = true;
        el.type.descriptor.arrayByteStride = 8;
        el.type.descriptor.elements = arrayCount;

        cur->offset += 8;
        cur->structDef.type.members.push_back(el);
      }
      else
      {
        // cbuffer packing rules, structs are always float4 base aligned
        if(!tightPacking)
          cur->offset = (cur->offset + 0xFU) & (~0xFU);

        el = structContext.structDef;
        el.name = varName;
        el.byteOffset = cur->offset;
        el.type.descriptor.elements = arrayCount;

        cur->structDef.type.members.push_back(el);

        // undo the padding after the last struct
        uint32_t padding = el.type.descriptor.arrayByteStride - structContext.offset;

        cur->offset += el.type.descriptor.elements * el.type.descriptor.arrayByteStride - padding;
      }

      continue;
    }

    QRegularExpressionMatch match = regExpr.match(line);

    if(!match.hasMatch())
    {
      errors = tr("Couldn't parse line: %1\n").arg(line);
      success = false;
      break;
    }

    el.name = !match.captured(6).isEmpty() ? match.captured(6).trimmed() : lit("data");

    QString basetype = match.captured(3);
    el.type.descriptor.rowMajorStorage = match.captured(1).trimmed() == lit("row_major");
    el.type.descriptor.displayAsRGB = !match.captured(2).isEmpty();
    QString firstDim = !match.captured(4).isEmpty() ? match.captured(4) : lit("1");
    QString secondDim = !match.captured(5).isEmpty() ? match.captured(5).mid(1) : lit("1");
    QString arrayDim = !match.captured(7).isEmpty() ? match.captured(7).trimmed() : lit("[1]");
    arrayDim = arrayDim.mid(1, arrayDim.count() - 2);

    if(!match.captured(5).isEmpty() && basetype != lit("mat"))
      firstDim.swap(secondDim);

    el.type.descriptor.name = match.captured(1) + match.captured(2) + match.captured(3) +
                              match.captured(4) + match.captured(5);

    ResourceFormatType interpretType = ResourceFormatType::Regular;
    CompType interpretCompType = CompType::Typeless;

    // check for square matrix declarations like 'mat4' and 'mat3'
    if(basetype == lit("mat") && match.captured(5).isEmpty())
      secondDim = firstDim;

    // calculate format
    {
      bool ok = false;

      el.type.descriptor.columns = firstDim.toUInt(&ok);
      if(!ok)
      {
        errors = tr("Invalid vector dimension on line: %1\n").arg(line);
        success = false;
        break;
      }

      el.type.descriptor.elements = qMax(1U, arrayDim.toUInt(&ok));
      if(!ok)
        el.type.descriptor.elements = 1;

      el.type.descriptor.rows = qMax(1U, secondDim.toUInt(&ok));
      if(!ok)
      {
        errors = tr("Invalid matrix second dimension on line: %1\n").arg(line);
        success = false;
        break;
      }

      // vectors are never marked as row major
      if(el.type.descriptor.rows == 1)
        el.type.descriptor.rowMajorStorage = false;

      if(basetype == lit("bool"))
      {
        el.type.descriptor.type = VarType::Bool;
      }
      else if(basetype == lit("byte"))
      {
        el.type.descriptor.type = VarType::SByte;
      }
      else if(basetype == lit("ubyte") || basetype == lit("xbyte"))
      {
        el.type.descriptor.type = VarType::UByte;
      }
      else if(basetype == lit("short"))
      {
        el.type.descriptor.type = VarType::SShort;
      }
      else if(basetype == lit("ushort") || basetype == lit("xshort"))
      {
        el.type.descriptor.type = VarType::UShort;
      }
      else if(basetype == lit("long"))
      {
        el.type.descriptor.type = VarType::SLong;
      }
      else if(basetype == lit("ulong") || basetype == lit("xlong"))
      {
        el.type.descriptor.type = VarType::ULong;
      }
      else if(basetype == lit("int") || basetype == lit("ivec") || basetype == lit("imat"))
      {
        el.type.descriptor.type = VarType::SInt;
      }
      else if(basetype == lit("uint") || basetype == lit("xint") || basetype == lit("uvec") ||
              basetype == lit("umat"))
      {
        el.type.descriptor.type = VarType::UInt;
      }
      else if(basetype == lit("half"))
      {
        el.type.descriptor.type = VarType::Half;
      }
      else if(basetype == lit("float") || basetype == lit("vec") || basetype == lit("mat"))
      {
        el.type.descriptor.type = VarType::Float;
      }
      else if(basetype == lit("double") || basetype == lit("dvec") || basetype == lit("dmat"))
      {
        el.type.descriptor.type = VarType::Double;
      }
      else if(basetype == lit("unormh"))
      {
        el.type.descriptor.type = VarType::UShort;
        interpretCompType = CompType::UNorm;
      }
      else if(basetype == lit("unormb"))
      {
        el.type.descriptor.type = VarType::UByte;
        interpretCompType = CompType::UNorm;
      }
      else if(basetype == lit("snormh"))
      {
        el.type.descriptor.type = VarType::SShort;
        interpretCompType = CompType::SNorm;
      }
      else if(basetype == lit("snormb"))
      {
        el.type.descriptor.type = VarType::SByte;
        interpretCompType = CompType::SNorm;
      }
      else if(basetype == lit("uintten"))
      {
        el.type.descriptor.type = VarType::UInt;
        interpretType = ResourceFormatType::R10G10B10A2;
        el.type.descriptor.columns = 4;
      }
      else if(basetype == lit("unormten"))
      {
        el.type.descriptor.type = VarType::UInt;
        interpretCompType = CompType::UNorm;
        interpretType = ResourceFormatType::R10G10B10A2;
        el.type.descriptor.columns = 4;
      }
      else if(basetype == lit("floateleven"))
      {
        el.type.descriptor.type = VarType::Float;
        interpretCompType = CompType::Float;
        interpretType = ResourceFormatType::R11G11B10;
        el.type.descriptor.columns = 3;
      }
      else
      {
        errors = tr("Unrecognised basic type on line: %1\n").arg(line);
        success = false;
        break;
      }
    }

    if(basetype == lit("xlong") || basetype == lit("xint") || basetype == lit("xshort") ||
       basetype == lit("xbyte"))
      el.type.descriptor.displayAsHex = true;

    SetInterpretedResourceFormat(el, interpretType, interpretCompType);

    ResourceFormat fmt = GetInterpretedResourceFormat(el);

    // normally the array stride is the size of an element
    el.type.descriptor.arrayByteStride = el.type.descriptor.matrixByteStride = fmt.ElementSize();

    uint32_t padding = 0;

    // for matrices, it's the size of an element times the number of rows
    if(el.type.descriptor.rows > 1)
    {
      // if we're cbuffer packing, matrix row/columns are always 16-bytes apart
      if(!tightPacking)
      {
        padding = 16 - el.type.descriptor.matrixByteStride;
        el.type.descriptor.matrixByteStride = 16;
      }

      uint8_t majorDim =
          el.type.descriptor.rowMajorStorage ? el.type.descriptor.rows : el.type.descriptor.columns;

      // total matrix size is
      el.type.descriptor.arrayByteStride = el.type.descriptor.matrixByteStride * majorDim;
    }

    // cbuffer packing rules
    if(!tightPacking)
    {
      if(el.type.descriptor.elements == 1)
      {
        // always float aligned
        el.type.descriptor.arrayByteStride = (el.type.descriptor.arrayByteStride + 3U) & (~3U);

        // elements can't cross float4 boundaries, nudge up if this was the case
        if(cur->offset / 16 != (cur->offset + el.type.descriptor.arrayByteStride - 1) / 16)
        {
          cur->offset = (cur->offset + 0xFU) & (~0xFU);
        }
      }
      else
      {
        // arrays always have elements float4 aligned
        uint32_t paddedStride = (el.type.descriptor.arrayByteStride + 0xFU) & (~0xFU);

        padding += paddedStride - el.type.descriptor.arrayByteStride;

        el.type.descriptor.arrayByteStride = paddedStride;

        // and always aligned at float4 boundary
        if(cur->offset % 16 != 0)
        {
          cur->offset = (cur->offset + 0xFU) & (~0xFU);
        }
      }
    }

    el.byteOffset = cur->offset;

    cur->structDef.type.members.push_back(el);

    cur->offset += el.type.descriptor.arrayByteStride * el.type.descriptor.elements - padding;
  }

  // if we succeeded parsing but didn't get any root elements, use the last defined struct as the
  // definition
  if(success && root.structDef.type.members.isEmpty() && !lastStruct.isEmpty())
    root = structelems[lastStruct];

  root.structDef.type.descriptor.arrayByteStride = root.offset;

  // struct strides are aligned up to float4 boundary
  if(!tightPacking)
    root.structDef.type.descriptor.arrayByteStride = (root.offset + 0xFU) & (~0xFU);

  if(!success || root.structDef.type.members.isEmpty())
  {
    root.structDef.type.members.clear();

    ShaderConstant el;
    el.byteOffset = 0;
    el.type.descriptor.displayAsHex = true;
    el.name = "data";
    el.type.descriptor.type = VarType::UInt;
    el.type.descriptor.columns = 4;

    if(maxLen > 0 && maxLen < 16)
      el.type.descriptor.columns = 1;
    if(maxLen > 0 && maxLen < 4)
      el.type.descriptor.type = VarType::UByte;

    el.type.descriptor.arrayByteStride = el.type.descriptor.matrixByteStride =
        el.type.descriptor.columns * VarTypeByteSize(el.type.descriptor.type);

    SetInterpretedResourceFormat(el, ResourceFormatType::Regular, CompType::Typeless);

    root.structDef.type.members.push_back(el);
  }

  return root.structDef;
}

QString BufferFormatter::GetTextureFormatString(const TextureDescription &tex)
{
  QString baseType;

  QString varName = lit("pixels");

  uint32_t w = tex.width;

  switch(tex.format.type)
  {
    case ResourceFormatType::BC1:
    case ResourceFormatType::BC2:
    case ResourceFormatType::BC3:
    case ResourceFormatType::BC4:
    case ResourceFormatType::BC5:
    case ResourceFormatType::BC6:
    case ResourceFormatType::BC7:
    case ResourceFormatType::ETC2:
    case ResourceFormatType::EAC:
    case ResourceFormatType::ASTC:
    case ResourceFormatType::PVRTC:
      varName = lit("block");
      // display a 4x4 block at a time
      w /= 4;
    default: break;
  }

  switch(tex.format.type)
  {
    case ResourceFormatType::Regular:
    {
      if(tex.format.compByteWidth == 1)
      {
        if(tex.format.compType == CompType::UNorm || tex.format.compType == CompType::UNormSRGB)
          baseType = lit("unormb");
        else if(tex.format.compType == CompType::SNorm)
          baseType = lit("snormb");
        else if(tex.format.compType == CompType::SInt)
          baseType = lit("byte");
        else
          baseType = lit("ubyte");
      }
      else if(tex.format.compByteWidth == 2)
      {
        if(tex.format.compType == CompType::UNorm || tex.format.compType == CompType::UNormSRGB)
          baseType = lit("unormh");
        else if(tex.format.compType == CompType::SNorm)
          baseType = lit("snormh");
        else if(tex.format.compType == CompType::Float)
          baseType = lit("half");
        else if(tex.format.compType == CompType::SInt)
          baseType = lit("short");
        else
          baseType = lit("ushort");
      }
      else if(tex.format.compByteWidth == 4)
      {
        if(tex.format.compType == CompType::Float)
          baseType = lit("float");
        else if(tex.format.compType == CompType::SInt)
          baseType = lit("int");
        else
          baseType = lit("uint");
      }
      else
      {
        if(tex.format.compType == CompType::Float)
          baseType = lit("double");
        else if(tex.format.compType == CompType::SInt)
          baseType = lit("long");
        else
          baseType = lit("ulong");
      }

      baseType = QFormatStr("rgb %1%2").arg(baseType).arg(tex.format.compCount);

      break;
    }
    // 2x4 byte block, for 64-bit block formats
    case ResourceFormatType::BC1:
    case ResourceFormatType::BC4:
    case ResourceFormatType::ETC2:
    case ResourceFormatType::EAC:
    case ResourceFormatType::PVRTC:
      baseType = lit("row_major xint2x1");
      break;
    // 4x4 byte block, for 128-bit block formats
    case ResourceFormatType::BC2:
    case ResourceFormatType::BC3:
    case ResourceFormatType::BC5:
    case ResourceFormatType::BC6:
    case ResourceFormatType::BC7:
    case ResourceFormatType::ASTC: baseType = lit("row_major xint4x1"); break;
    case ResourceFormatType::R10G10B10A2: baseType = lit("uintten"); break;
    case ResourceFormatType::R11G11B10: baseType = lit("rgb floateleven"); break;
    case ResourceFormatType::R5G6B5:
    case ResourceFormatType::R5G5B5A1: baseType = lit("xshort"); break;
    case ResourceFormatType::R9G9B9E5: baseType = lit("xint"); break;
    case ResourceFormatType::R4G4B4A4: baseType = lit("xshort"); break;
    case ResourceFormatType::R4G4: baseType = lit("xbyte"); break;
    case ResourceFormatType::D16S8:
    case ResourceFormatType::D24S8:
    case ResourceFormatType::D32S8:
    case ResourceFormatType::YUV8: baseType = lit("xbyte4"); break;
    case ResourceFormatType::YUV10:
    case ResourceFormatType::YUV12:
    case ResourceFormatType::YUV16: baseType = lit("xshort4"); break;
    case ResourceFormatType::A8:
    case ResourceFormatType::S8:
    case ResourceFormatType::Undefined: baseType = lit("xbyte"); break;
  }

  if(tex.type == TextureType::Buffer)
    return QFormatStr("%1 %2;").arg(baseType).arg(varName);

  return QFormatStr("%1 %2[%3];").arg(baseType).arg(varName).arg(w);
}

QString BufferFormatter::GetBufferFormatString(const ShaderResource &res,
                                               const ResourceFormat &viewFormat,
                                               uint64_t &baseByteOffset)
{
  QString format;

  if(!res.variableType.members.empty())
  {
    if(m_API == GraphicsAPI::Vulkan || m_API == GraphicsAPI::OpenGL)
    {
      const rdcarray<ShaderConstant> &members = res.variableType.members;

      format += QFormatStr("struct %1\n{\n").arg(res.name);

      // GL/Vulkan allow fixed-sized members before the array-of-structs. This can't be
      // represented in a buffer format so we skip it
      if(members.count() > 1)
      {
        format += tr("    // members skipped as they are fixed size:\n");
        baseByteOffset += members.back().byteOffset;
      }

      QString varTypeName;
      QString comment = lit("// ");
      for(int i = 0; i < members.count(); i++)
      {
        QString arraySize;
        if(members[i].type.descriptor.elements > 1)
          arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements);

        varTypeName = members[i].type.descriptor.name;

        if(i + 1 == members.count())
        {
          comment.clear();
          arraySize.clear();

          if(members.count() > 1)
            format +=
                lit("    // final array struct @ byte offset %1\n").arg(members.back().byteOffset);

          // give GL nameless structs a better name
          if(varTypeName.isEmpty() || varTypeName == lit("struct"))
            varTypeName = lit("root_struct");
        }

        QString varName = members[i].name;

        if(varName.isEmpty())
          varName = QFormatStr("_child%1").arg(i);

        format +=
            QFormatStr("    %1%2 %3%4;\n").arg(comment).arg(varTypeName).arg(varName).arg(arraySize);
      }

      format += lit("}");

      // if the last member is a struct, declare it
      if(!members.back().type.members.isEmpty())
      {
        format = DeclareStruct(varTypeName, members.back().type.members,
                               members.back().type.descriptor.arrayByteStride) +
                 lit("\n") + format;
      }
    }
    else
    {
      format = DeclareStruct(res.variableType.descriptor.name, res.variableType.members, 0);
    }
  }
  else
  {
    const auto &desc = res.variableType.descriptor;

    if(viewFormat.type == ResourceFormatType::Undefined)
    {
      if(desc.type == VarType::Unknown)
      {
        format = desc.name;
      }
      else
      {
        if(desc.rowMajorStorage)
          format += lit("row_major ");

        format += ToQStr(desc.type);
        if(desc.rows > 1 && desc.columns > 1)
          format += QFormatStr("%1x%2").arg(desc.rows).arg(desc.columns);
        else if(desc.columns > 1)
          format += QString::number(desc.columns);

        if(!desc.name.empty())
          format += lit(" ") + desc.name;

        if(desc.elements > 1)
          format += QFormatStr("[%1]").arg(desc.elements);
      }
    }
    else
    {
      if(viewFormat.type == ResourceFormatType::R10G10B10A2)
      {
        if(viewFormat.compType == CompType::UInt)
          format = lit("uintten");
        if(viewFormat.compType == CompType::UNorm)
          format = lit("unormten");
      }
      else if(viewFormat.type == ResourceFormatType::R11G11B10)
      {
        format = lit("floateleven");
      }
      else
      {
        switch(viewFormat.compByteWidth)
        {
          case 1:
          {
            if(viewFormat.compType == CompType::UNorm || viewFormat.compType == CompType::UNormSRGB)
              format = lit("unormb");
            if(viewFormat.compType == CompType::SNorm)
              format = lit("snormb");
            if(viewFormat.compType == CompType::UInt)
              format = lit("ubyte");
            if(viewFormat.compType == CompType::SInt)
              format = lit("byte");
            break;
          }
          case 2:
          {
            if(viewFormat.compType == CompType::UNorm || viewFormat.compType == CompType::UNormSRGB)
              format = lit("unormh");
            if(viewFormat.compType == CompType::SNorm)
              format = lit("snormh");
            if(viewFormat.compType == CompType::UInt)
              format = lit("ushort");
            if(viewFormat.compType == CompType::SInt)
              format = lit("short");
            if(viewFormat.compType == CompType::Float)
              format = lit("half");
            break;
          }
          case 4:
          {
            if(viewFormat.compType == CompType::UNorm || viewFormat.compType == CompType::UNormSRGB)
              format = lit("unormf");
            if(viewFormat.compType == CompType::SNorm)
              format = lit("snormf");
            if(viewFormat.compType == CompType::UInt)
              format = lit("uint");
            if(viewFormat.compType == CompType::SInt)
              format = lit("int");
            if(viewFormat.compType == CompType::Float)
              format = lit("float");
            break;
          }
        }

        format += QString::number(viewFormat.compCount);
      }
    }
  }

  return format;
}

uint32_t BufferFormatter::GetVarSize(const ShaderConstant &var)
{
  uint32_t size = var.type.descriptor.rows * var.type.descriptor.columns;
  uint32_t typeSize = VarTypeByteSize(var.type.descriptor.type);
  if(typeSize > 1)
    size *= typeSize;

  if(var.type.descriptor.rows > 1)
  {
    if(var.type.descriptor.rowMajorStorage)
      size = var.type.descriptor.matrixByteStride * var.type.descriptor.rows;
    else
      size = var.type.descriptor.matrixByteStride * var.type.descriptor.columns;
  }

  if(!var.type.members.empty())
    size = GetStructVarSize(var.type.members);

  if(var.type.descriptor.elements > 1)
    size *= var.type.descriptor.elements;

  return size;
}

uint32_t BufferFormatter::GetStructVarSize(const rdcarray<ShaderConstant> &members)
{
  uint32_t lastMemberStart = 0;

  const ShaderConstant *lastChild = &members.back();

  lastMemberStart += lastChild->byteOffset;
  while(!lastChild->type.members.isEmpty())
  {
    lastMemberStart += (qMax(lastChild->type.descriptor.elements, 1U) - 1) *
                       lastChild->type.descriptor.arrayByteStride;
    lastChild = &lastChild->type.members.back();
    lastMemberStart += lastChild->byteOffset;
  }

  return lastMemberStart + GetVarSize(*lastChild);
}

QString BufferFormatter::DeclarePaddingBytes(uint32_t bytes)
{
  if(bytes == 0)
    return QString();

  QString ret;

  if(bytes > 4)
  {
    ret += lit("xint pad[%1];").arg(bytes / 4);

    bytes = bytes % 4;
  }

  if(bytes == 4)
    ret += lit("xint pad;");
  else if(bytes == 3)
    ret += lit("xshort pad; xbyte pad;");
  else if(bytes == 2)
    ret += lit("xshort pad;");
  else if(bytes == 1)
    ret += lit("xbyte pad;");

  return ret + lit("\n");
}

QString BufferFormatter::DeclareStruct(QList<QString> &declaredStructs, const QString &name,
                                       const rdcarray<ShaderConstant> &members,
                                       uint32_t requiredByteStride)
{
  QString ret;

  ret = lit("struct %1\n{\n").arg(name);

  uint32_t offset = 0;

  for(int i = 0; i < members.count(); i++)
  {
    if(offset < members[i].byteOffset)
      ret += lit("    ") + DeclarePaddingBytes(members[i].byteOffset - offset);
    else if(offset > members[i].byteOffset)
      qCritical() << "Unexpected offset overlow at" << QString(members[i].name) << "in"
                  << QString(name);

    offset = members[i].byteOffset + GetVarSize(members[i]);

    QString arraySize;
    if(members[i].type.descriptor.elements > 1)
      arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements);

    QString varTypeName = members[i].type.descriptor.name;

    if(members[i].type.descriptor.pointerTypeID != ~0U)
    {
      const ShaderConstantType &pointeeType =
          PointerTypeRegistry::GetTypeDescriptor(members[i].type.descriptor.pointerTypeID);

      varTypeName = pointeeType.descriptor.name;

      if(!declaredStructs.contains(varTypeName))
      {
        declaredStructs.push_back(varTypeName);
        ret = DeclareStruct(declaredStructs, varTypeName, pointeeType.members,
                            pointeeType.descriptor.arrayByteStride) +
              lit("\n") + ret;
      }

      varTypeName += lit("*");
    }
    else if(!members[i].type.members.isEmpty())
    {
      // GL structs don't give us typenames (boo!) so give them unique names. This will mean some
      // structs get duplicated if they're used in multiple places, but not much we can do about
      // that.
      if(varTypeName.isEmpty() || varTypeName == lit("struct"))
        varTypeName = lit("anon%1").arg(declaredStructs.size());

      if(!declaredStructs.contains(varTypeName))
      {
        declaredStructs.push_back(varTypeName);
        ret = DeclareStruct(declaredStructs, varTypeName, members[i].type.members,
                            members[i].type.descriptor.arrayByteStride) +
              lit("\n") + ret;
      }
    }

    QString varName = members[i].name;

    if(varName.isEmpty())
      varName = QFormatStr("_child%1").arg(i);

    if(members[i].type.descriptor.rows > 1)
    {
      if(members[i].type.descriptor.rowMajorStorage)
      {
        varTypeName = lit("row_major ") + varTypeName;

        uint32_t tightStride =
            VarTypeByteSize(members[i].type.descriptor.type) * members[i].type.descriptor.columns;

        if(tightStride < members[i].type.descriptor.matrixByteStride)
        {
          uint32_t padSize = members[i].type.descriptor.matrixByteStride - tightStride;
          for(uint32_t r = 0; r < members[i].type.descriptor.rows; r++)
          {
            ret += QFormatStr("    %1%2 %3_row%4; %5")
                       .arg(ToQStr(members[i].type.descriptor.type))
                       .arg(members[i].type.descriptor.columns)
                       .arg(varName)
                       .arg(r)
                       .arg(DeclarePaddingBytes(padSize));
          }

          continue;
        }
      }
      else
      {
        uint32_t tightStride =
            VarTypeByteSize(members[i].type.descriptor.type) * members[i].type.descriptor.rows;

        if(tightStride < members[i].type.descriptor.matrixByteStride)
        {
          uint32_t padSize = members[i].type.descriptor.matrixByteStride - tightStride;
          for(uint32_t c = 0; c < members[i].type.descriptor.columns; c++)
          {
            ret += QFormatStr("    %1%2 %3_col%4; %5")
                       .arg(ToQStr(members[i].type.descriptor.type))
                       .arg(members[i].type.descriptor.rows)
                       .arg(varName)
                       .arg(c)
                       .arg(DeclarePaddingBytes(padSize));
          }

          continue;
        }
      }
    }

    ret += QFormatStr("    %1 %2%3;\n").arg(varTypeName).arg(varName).arg(arraySize);
  }

  if(requiredByteStride > 0)
  {
    const uint32_t structEnd = GetStructVarSize(members);

    if(requiredByteStride > structEnd)
      ret += lit("    ") + DeclarePaddingBytes(requiredByteStride - structEnd);
    else if(requiredByteStride != structEnd)
      qCritical() << "Unexpected stride overlow at struct" << name;
  }

  ret += lit("}\n");

  return ret;
}

QString BufferFormatter::DeclareStruct(const QString &name, const rdcarray<ShaderConstant> &members,
                                       uint32_t requiredByteStride)
{
  QList<QString> declaredStructs;
  return DeclareStruct(declaredStructs, name, members, requiredByteStride);
}

void SetInterpretedResourceFormat(ShaderConstant &elem, ResourceFormatType interpretType,
                                  CompType interpretCompType)
{
  static_assert(sizeof(elem.defaultValue) > 2,
                "ShaderConstant::defaultValue has changed, check packing");

  // packing must match GetInterpretedResourceFormat below
  elem.defaultValue = (uint64_t(interpretType) << 8) | (uint64_t(interpretCompType) << 0);
}

ResourceFormat GetInterpretedResourceFormat(const ShaderConstant &elem)
{
  // packing must match SetInterpretedResourceFormat above
  ResourceFormatType interpretType = ResourceFormatType((elem.defaultValue >> 8) & 0xff);
  CompType interpretCompType = CompType((elem.defaultValue >> 0) & 0xff);

  ResourceFormat format;
  format.type = interpretType;

  format.compType = VarTypeCompType(elem.type.descriptor.type);

  if(interpretCompType != CompType::Typeless)
    format.compType = interpretCompType;

  format.compByteWidth = VarTypeByteSize(elem.type.descriptor.type);

  if(elem.type.descriptor.rowMajorStorage || elem.type.descriptor.rows == 1)
    format.compCount = elem.type.descriptor.columns;
  else
    format.compCount = elem.type.descriptor.rows;

  // packed formats with fixed component counts multiply up the component count
  switch(format.type)
  {
    case ResourceFormatType::R10G10B10A2:
    case ResourceFormatType::R5G5B5A1:
    case ResourceFormatType::R4G4B4A4: format.compCount *= 4; break;
    case ResourceFormatType::R11G11B10:
    case ResourceFormatType::R9G9B9E5:
    case ResourceFormatType::R5G6B5: format.compCount *= 3; break;
    case ResourceFormatType::R4G4: format.compCount *= 2; break;
    default: break;
  }

  return format;
}

static void FillShaderVarData(ShaderVariable &var, const ShaderConstant &elem, const byte *data,
                              const byte *end)
{
  int src = 0;

  uint32_t outerCount = elem.type.descriptor.rows;
  uint32_t innerCount = elem.type.descriptor.columns;

  bool colMajor = false;

  if(!elem.type.descriptor.rowMajorStorage && outerCount > 1)
  {
    colMajor = true;
    std::swap(outerCount, innerCount);
  }

  QVariantList objs =
      GetVariants(GetInterpretedResourceFormat(elem), elem.type.descriptor, data, end);

  if(objs.isEmpty())
  {
    var.name = "-";
    var.value = ShaderValue();
    return;
  }

  for(uint32_t outer = 0; outer < outerCount; outer++)
  {
    for(uint32_t inner = 0; inner < innerCount; inner++)
    {
      uint32_t dst = outer * elem.type.descriptor.columns + inner;

      if(colMajor)
        dst = inner * elem.type.descriptor.columns + outer;

      const QVariant &o = objs[src];

      src++;

      if(var.type == VarType::Double)
        var.value.f64v[dst] = o.toDouble();
      if(var.type == VarType::Float || var.type == VarType::Half)
        var.value.f32v[dst] = o.toFloat();
      else if(var.type == VarType::ULong)
        var.value.u64v[dst] = o.toULongLong();
      else if(var.type == VarType::SLong)
        var.value.s64v[dst] = o.toLongLong();
      else if(var.type == VarType::Bool)
        var.value.u32v[dst] = o.toBool() ? 1 : 0;
      else if(var.type == VarType::UInt || var.type == VarType::UShort || var.type == VarType::UByte)
        var.value.u32v[dst] = o.toUInt();
      else if(var.type == VarType::SInt || var.type == VarType::SShort || var.type == VarType::SByte)
        var.value.s32v[dst] = o.toInt();
      else
        var.value.f32v[dst] = o.toFloat();
    }
  }
}

ShaderVariable InterpretShaderVar(const ShaderConstant &elem, const byte *data, const byte *end)
{
  ShaderVariable ret;

  ret.name = elem.name;
  ret.type = elem.type.descriptor.type;
  ret.columns = qMin(elem.type.descriptor.columns, uint8_t(4));
  ret.rows = qMin(elem.type.descriptor.rows, uint8_t(4));
  ret.isStruct = !elem.type.members.isEmpty();

  ret.displayAsHex = elem.type.descriptor.displayAsHex;
  ret.rowMajor = elem.type.descriptor.rowMajorStorage;

  if(!elem.type.members.isEmpty())
  {
    ret.rows = ret.columns = 0;

    if(elem.type.descriptor.elements > 1)
    {
      rdcarray<ShaderVariable> arrayElements;

      for(uint32_t a = 0; a < elem.type.descriptor.elements; a++)
      {
        rdcarray<ShaderVariable> members;

        for(size_t m = 0; m < elem.type.members.size(); m++)
        {
          const ShaderConstant &member = elem.type.members[m];

          members.push_back(InterpretShaderVar(member, data + member.byteOffset, end));
        }

        arrayElements.push_back(ret);
        arrayElements.back().name = QFormatStr("%1[%2]").arg(ret.name).arg(a);
        arrayElements.back().members = members;

        data += elem.type.descriptor.arrayByteStride;
      }

      ret.isStruct = false;
      ret.members = arrayElements;
    }
    else
    {
      rdcarray<ShaderVariable> members;

      for(size_t m = 0; m < elem.type.members.size(); m++)
      {
        const ShaderConstant &member = elem.type.members[m];

        members.push_back(InterpretShaderVar(member, data + member.byteOffset, end));
      }

      ret.members = members;
    }
  }
  else if(elem.type.descriptor.elements > 1)
  {
    rdcarray<ShaderVariable> arrayElements;

    for(uint32_t a = 0; a < elem.type.descriptor.elements; a++)
    {
      arrayElements.push_back(ret);
      arrayElements.back().name = QFormatStr("%1[%2]").arg(ret.name).arg(a);
      FillShaderVarData(arrayElements.back(), elem, data, end);
      data += elem.type.descriptor.arrayByteStride;
    }

    ret.rows = ret.columns = 0;
    ret.members = arrayElements;
  }
  else
  {
    FillShaderVarData(ret, elem, data, end);
  }

  return ret;
}

static QVariant interpret(const ResourceFormat &f, uint16_t comp)
{
  if(f.compByteWidth != 2 || f.compType == CompType::Float)
    return QVariant();

  if(f.compType == CompType::SInt)
  {
    return (int16_t)comp;
  }
  else if(f.compType == CompType::UInt)
  {
    return comp;
  }
  else if(f.compType == CompType::SScaled)
  {
    return (float)((int16_t)comp);
  }
  else if(f.compType == CompType::UScaled)
  {
    return (float)comp;
  }
  else if(f.compType == CompType::UNorm || f.compType == CompType::UNormSRGB)
  {
    return (float)comp / (float)0xffff;
  }
  else if(f.compType == CompType::SNorm)
  {
    int16_t cast = (int16_t)comp;

    float ret = -1.0f;

    if(cast == -32768)
      ret = -1.0f;
    else
      ret = ((float)cast) / 32767.0f;

    return ret;
  }

  return QVariant();
}

static QVariant interpret(const ResourceFormat &f, byte comp)
{
  if(f.compByteWidth != 1 || f.compType == CompType::Float)
    return QVariant();

  if(f.compType == CompType::SInt)
  {
    return (int8_t)comp;
  }
  else if(f.compType == CompType::UInt)
  {
    return comp;
  }
  else if(f.compType == CompType::SScaled)
  {
    return (float)((int8_t)comp);
  }
  else if(f.compType == CompType::UScaled)
  {
    return (float)comp;
  }
  else if(f.compType == CompType::UNorm || f.compType == CompType::UNormSRGB)
  {
    return ((float)comp) / 255.0f;
  }
  else if(f.compType == CompType::SNorm)
  {
    int8_t cast = (int8_t)comp;

    float ret = -1.0f;

    if(cast == -128)
      ret = -1.0f;
    else
      ret = ((float)cast) / 127.0f;

    return ret;
  }

  return QVariant();
}

template <typename T>
inline T readObj(const byte *&data, const byte *end, bool &ok)
{
  if(data + sizeof(T) > end)
  {
    ok = false;
    return T();
  }

  T ret = *(T *)data;

  data += sizeof(T);

  return ret;
}

QVariantList GetVariants(ResourceFormat format, const ShaderConstantDescriptor &varDesc,
                         const byte *&data, const byte *end)
{
  QVariantList ret;

  bool ok = true;

  if(format.type == ResourceFormatType::R5G5B5A1)
  {
    uint16_t packed = readObj<uint16_t>(data, end, ok);

    ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f);
    ret.push_back((float)((packed >> 5) & 0x1f) / 31.0f);
    ret.push_back((float)((packed >> 10) & 0x1f) / 31.0f);
    ret.push_back(((packed & 0x8000) > 0) ? 1.0f : 0.0f);

    if(format.BGRAOrder())
    {
      QVariant tmp = ret[2];
      ret[2] = ret[0];
      ret[0] = tmp;
    }
  }
  else if(format.type == ResourceFormatType::R5G6B5)
  {
    uint16_t packed = readObj<uint16_t>(data, end, ok);

    ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f);
    ret.push_back((float)((packed >> 5) & 0x3f) / 63.0f);
    ret.push_back((float)((packed >> 11) & 0x1f) / 31.0f);

    if(format.BGRAOrder())
    {
      QVariant tmp = ret[2];
      ret[2] = ret[0];
      ret[0] = tmp;
    }
  }
  else if(format.type == ResourceFormatType::R4G4B4A4)
  {
    uint16_t packed = readObj<uint16_t>(data, end, ok);

    ret.push_back((float)((packed >> 0) & 0xf) / 15.0f);
    ret.push_back((float)((packed >> 4) & 0xf) / 15.0f);
    ret.push_back((float)((packed >> 8) & 0xf) / 15.0f);
    ret.push_back((float)((packed >> 12) & 0xf) / 15.0f);

    if(format.BGRAOrder())
    {
      QVariant tmp = ret[2];
      ret[2] = ret[0];
      ret[0] = tmp;
    }
  }
  else if(format.type == ResourceFormatType::R10G10B10A2)
  {
    // allow for vectors of this format - for raw buffer viewer
    for(int i = 0; i < int(format.compCount / 4); i++)
    {
      uint32_t packed = readObj<uint32_t>(data, end, ok);

      uint32_t r = (packed >> 0) & 0x3ff;
      uint32_t g = (packed >> 10) & 0x3ff;
      uint32_t b = (packed >> 20) & 0x3ff;
      uint32_t a = (packed >> 30) & 0x003;

      if(format.BGRAOrder())
      {
        uint32_t tmp = b;
        b = r;
        r = tmp;
      }

      if(format.compType == CompType::UInt)
      {
        ret.push_back(r);
        ret.push_back(g);
        ret.push_back(b);
        ret.push_back(a);
      }
      else if(format.compType == CompType::UScaled)
      {
        ret.push_back((float)r);
        ret.push_back((float)g);
        ret.push_back((float)b);
        ret.push_back((float)a);
      }
      else if(format.compType == CompType::SInt || format.compType == CompType::SScaled ||
              format.compType == CompType::SNorm)
      {
        int ir, ig, ib, ia;

        // interpret RGB as 10-bit signed integers
        if(r <= 511)
          ir = (int)r;
        else
          ir = ((int)r) - 1024;

        if(g <= 511)
          ig = (int)g;
        else
          ig = ((int)g) - 1024;

        if(b <= 511)
          ib = (int)b;
        else
          ib = ((int)b) - 1024;

        // 2-bit signed integer
        if(a <= 1)
          ia = (int)a;
        else
          ia = ((int)a) - 4;

        if(format.compType == CompType::SInt)
        {
          ret.push_back(ir);
          ret.push_back(ig);
          ret.push_back(ib);
          ret.push_back(ia);
        }
        else if(format.compType == CompType::SScaled)
        {
          ret.push_back((float)ir);
          ret.push_back((float)ig);
          ret.push_back((float)ib);
          ret.push_back((float)ia);
        }
        else if(format.compType == CompType::SNorm)
        {
          if(ir == -512)
            ir = -511;
          if(ig == -512)
            ig = -511;
          if(ib == -512)
            ib = -511;
          if(ia == -2)
            ia = -1;

          ret.push_back((float)ir / 511.0f);
          ret.push_back((float)ig / 511.0f);
          ret.push_back((float)ib / 511.0f);
          ret.push_back((float)ia / 1.0f);
        }
      }
      else
      {
        ret.push_back((float)r / 1023.0f);
        ret.push_back((float)g / 1023.0f);
        ret.push_back((float)b / 1023.0f);
        ret.push_back((float)a / 3.0f);
      }
    }
  }
  else if(format.type == ResourceFormatType::R11G11B10)
  {
    uint32_t packed = readObj<uint32_t>(data, end, ok);

    uint32_t mantissas[] = {
        (packed >> 0) & 0x3f, (packed >> 11) & 0x3f, (packed >> 22) & 0x1f,
    };
    int32_t exponents[] = {
        int32_t(packed >> 6) & 0x1f, int32_t(packed >> 17) & 0x1f, int32_t(packed >> 27) & 0x1f,
    };
    static const uint32_t leadbit[] = {
        0x40, 0x40, 0x20,
    };

    for(int i = 0; i < 3; i++)
    {
      if(mantissas[i] == 0 && exponents[i] == 0)
      {
        ret.push_back((float)0.0f);
      }
      else
      {
        if(exponents[i] == 0x1f)
        {
          // no sign bit, can't be negative infinity
          if(mantissas[i] == 0)
            ret.push_back((float)qInf());
          else
            ret.push_back((float)qQNaN());
        }
        else if(exponents[i] != 0)
        {
          // normal value, add leading bit
          uint32_t combined = leadbit[i] | mantissas[i];

          // calculate value
          ret.push_back(((float)combined / (float)leadbit[i]) *
                        qPow(2.0f, (float)exponents[i] - 15.0f));
        }
        else if(exponents[i] == 0)
        {
          // we know xMantissa isn't 0 also, or it would have been caught above so
          // this is a subnormal value, pretend exponent is 1 and don't add leading bit

          ret.push_back(((float)mantissas[i] / (float)leadbit[i]) * qPow(2.0f, (float)1.0f - 15.0f));
        }
      }
    }
  }
  else
  {
    const byte *base = data;

    uint32_t rowCount = varDesc.rows;
    uint32_t colCount = varDesc.columns;

    for(uint32_t row = 0; row < qMax(rowCount, 1U); row++)
    {
      for(uint32_t col = 0; col < qMax(colCount, 1U); col++)
      {
        if(varDesc.rowMajorStorage || rowCount == 1)
          data = base + row * varDesc.matrixByteStride + col * format.compByteWidth;
        else
          data = base + col * varDesc.matrixByteStride + row * format.compByteWidth;

        if(format.compType == CompType::Float)
        {
          if(format.compByteWidth == 8)
            ret.push_back(readObj<double>(data, end, ok));
          else if(format.compByteWidth == 4)
            ret.push_back(readObj<float>(data, end, ok));
          else if(format.compByteWidth == 2)
            ret.push_back(RENDERDOC_HalfToFloat(readObj<uint16_t>(data, end, ok)));
        }
        else if(format.compType == CompType::SInt)
        {
          if(format.compByteWidth == 8)
            ret.push_back((qlonglong)readObj<int64_t>(data, end, ok));
          else if(format.compByteWidth == 4)
            ret.push_back((int)readObj<int32_t>(data, end, ok));
          else if(format.compByteWidth == 2)
            ret.push_back((int)readObj<int16_t>(data, end, ok));
          else if(format.compByteWidth == 1)
            ret.push_back((int)readObj<int8_t>(data, end, ok));
        }
        else if(format.compType == CompType::UInt)
        {
          if(format.compByteWidth == 8)
            ret.push_back((qulonglong)readObj<uint64_t>(data, end, ok));
          else if(format.compByteWidth == 4)
            ret.push_back((uint32_t)readObj<uint32_t>(data, end, ok));
          else if(format.compByteWidth == 2)
            ret.push_back((uint32_t)readObj<uint16_t>(data, end, ok));
          else if(format.compByteWidth == 1)
            ret.push_back((uint32_t)readObj<uint8_t>(data, end, ok));
        }
        else if(format.compType == CompType::UScaled)
        {
          if(format.compByteWidth == 4)
            ret.push_back((float)readObj<uint32_t>(data, end, ok));
          else if(format.compByteWidth == 2)
            ret.push_back((float)readObj<uint16_t>(data, end, ok));
          else if(format.compByteWidth == 1)
            ret.push_back((float)readObj<uint8_t>(data, end, ok));
        }
        else if(format.compType == CompType::SScaled)
        {
          if(format.compByteWidth == 4)
            ret.push_back((float)readObj<int32_t>(data, end, ok));
          else if(format.compByteWidth == 2)
            ret.push_back((float)readObj<int16_t>(data, end, ok));
          else if(format.compByteWidth == 1)
            ret.push_back((float)readObj<int8_t>(data, end, ok));
        }
        else if(format.compType == CompType::Depth)
        {
          if(format.compByteWidth == 4)
          {
            // 32-bit depth is native floats
            ret.push_back(readObj<float>(data, end, ok));
          }
          else if(format.compByteWidth == 3)
          {
            // 32-bit depth is normalised, masked against non-stencil bits
            uint32_t f = readObj<uint32_t>(data, end, ok);
            f &= 0x00ffffff;
            ret.push_back((float)f / (float)0x00ffffff);
          }
          else if(format.compByteWidth == 2)
          {
            // 16-bit depth is normalised
            float f = (float)readObj<uint16_t>(data, end, ok);
            ret.push_back(f / (float)0x0000ffff);
          }
        }
        else
        {
          // unorm/snorm

          if(format.compByteWidth == 4)
          {
            // should never hit this - no 32bit unorm/snorm type
            qCritical() << "Unexpected 4-byte unorm/snorm value";
            ret.push_back((float)readObj<uint32_t>(data, end, ok) / (float)0xffffffff);
          }
          else if(format.compByteWidth == 2)
          {
            ret.push_back(interpret(format, readObj<uint16_t>(data, end, ok)));
          }
          else if(format.compByteWidth == 1)
          {
            ret.push_back(interpret(format, readObj<uint8_t>(data, end, ok)));
          }
        }
      }
    }

    if(format.BGRAOrder())
    {
      QVariant tmp = ret[2];
      ret[2] = ret[0];
      ret[0] = tmp;
    }
  }

  // we read off the end, return empty set
  if(!ok)
    ret.clear();

  return ret;
}

QString TypeString(const ShaderVariable &v)
{
  if(!v.members.isEmpty() || v.isStruct)
  {
    if(v.isStruct)
      return lit("struct");
    else
      return QFormatStr("%1[%2]").arg(TypeString(v.members[0])).arg(v.members.count());
  }

  if(v.type == VarType::GPUPointer)
    return PointerTypeRegistry::GetTypeDescriptor(v.GetPointer()).descriptor.name + "*";

  QString typeStr = ToQStr(v.type);

  if(v.displayAsHex)
  {
    if(v.type == VarType::ULong)
      typeStr = lit("xlong");
    else if(v.type == VarType::UInt)
      typeStr = lit("xint");
    else if(v.type == VarType::UShort)
      typeStr = lit("xshort");
    else if(v.type == VarType::UByte)
      typeStr = lit("xbyte");
  }

  if(v.type == VarType::Unknown)
    return lit("Typeless");
  if(v.rows == 1 && v.columns == 1)
    return typeStr;
  if(v.rows == 1)
    return QFormatStr("%1%2").arg(typeStr).arg(v.columns);
  else
    return QFormatStr("%1%2x%3 (%4)")
        .arg(typeStr)
        .arg(v.rows)
        .arg(v.columns)
        .arg(v.rowMajor ? lit("row_major") : lit("column_major"));
}

template <typename el>
static QString RowValuesToString(int cols, bool hex, el x, el y, el z, el w)
{
  if(cols == 1)
    return Formatter::Format(x, hex);
  else if(cols == 2)
    return QFormatStr("%1, %2").arg(Formatter::Format(x, hex)).arg(Formatter::Format(y, hex));
  else if(cols == 3)
    return QFormatStr("%1, %2, %3")
        .arg(Formatter::Format(x, hex))
        .arg(Formatter::Format(y, hex))
        .arg(Formatter::Format(z, hex));
  else
    return QFormatStr("%1, %2, %3, %4")
        .arg(Formatter::Format(x, hex))
        .arg(Formatter::Format(y, hex))
        .arg(Formatter::Format(z, hex))
        .arg(Formatter::Format(w, hex));
}

QString RowString(const ShaderVariable &v, uint32_t row, VarType type)
{
  if(type == VarType::Unknown)
    type = v.type;

  if(v.type == VarType::GPUPointer)
    return ToQStr(v.GetPointer());

  if(type == VarType::Double)
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.f64v[row * v.columns + 0],
                             v.value.f64v[row * v.columns + 1], v.value.f64v[row * v.columns + 2],
                             v.value.f64v[row * v.columns + 3]);
  else if(type == VarType::SLong)
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.s64v[row * v.columns + 0],
                             v.value.s64v[row * v.columns + 1], v.value.s64v[row * v.columns + 2],
                             v.value.s64v[row * v.columns + 3]);
  else if(type == VarType::ULong)
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.u64v[row * v.columns + 0],
                             v.value.u64v[row * v.columns + 1], v.value.u64v[row * v.columns + 2],
                             v.value.u64v[row * v.columns + 3]);
  else if(type == VarType::SInt || type == VarType::SShort || type == VarType::SByte)
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.s32v[row * v.columns + 0],
                             v.value.s32v[row * v.columns + 1], v.value.s32v[row * v.columns + 2],
                             v.value.s32v[row * v.columns + 3]);
  else if(type == VarType::UInt || type == VarType::UShort || type == VarType::UByte)
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.u32v[row * v.columns + 0],
                             v.value.u32v[row * v.columns + 1], v.value.u32v[row * v.columns + 2],
                             v.value.u32v[row * v.columns + 3]);
  else if(type == VarType::Bool)
    return RowValuesToString((int)v.columns, v.displayAsHex,
                             v.value.u32v[row * v.columns + 0] ? true : false,
                             v.value.u32v[row * v.columns + 1] ? true : false,
                             v.value.u32v[row * v.columns + 2] ? true : false,
                             v.value.u32v[row * v.columns + 3] ? true : false);
  else
    return RowValuesToString((int)v.columns, v.displayAsHex, v.value.f32v[row * v.columns + 0],
                             v.value.f32v[row * v.columns + 1], v.value.f32v[row * v.columns + 2],
                             v.value.f32v[row * v.columns + 3]);
}

QString VarString(const ShaderVariable &v)
{
  if(!v.members.isEmpty())
    return QString();

  if(v.rows == 1)
    return RowString(v, 0);

  QString ret;
  for(int i = 0; i < (int)v.rows; i++)
  {
    if(i > 0)
      ret += lit("\n");
    ret += lit("{") + RowString(v, i) + lit("}");
  }

  return ret;
}

QString RowTypeString(const ShaderVariable &v)
{
  if(!v.members.isEmpty() || v.isStruct)
  {
    if(v.isStruct)
      return lit("struct");
    else
      return lit("flibbertygibbet");
  }

  if(v.rows == 0 && v.columns == 0)
    return lit("-");

  if(v.type == VarType::GPUPointer)
    return PointerTypeRegistry::GetTypeDescriptor(v.GetPointer()).descriptor.name + "*";

  QString typeStr = ToQStr(v.type);

  if(v.displayAsHex)
  {
    if(v.type == VarType::ULong)
      typeStr = lit("xlong");
    else if(v.type == VarType::UInt)
      typeStr = lit("xint");
    else if(v.type == VarType::UShort)
      typeStr = lit("xshort");
    else if(v.type == VarType::UByte)
      typeStr = lit("xbyte");
  }

  if(v.columns == 1)
    return typeStr;

  return QFormatStr("%1%2").arg(typeStr).arg(v.columns);
}
