/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2022 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 #include #include #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 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.flags |= ShaderVariableFlags::HexDisplay; 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); if(match.captured(1).trimmed() == lit("row_major")) el.type.descriptor.flags |= ShaderVariableFlags::RowMajorMatrix; if(!match.captured(2).isEmpty()) el.type.descriptor.flags |= ShaderVariableFlags::RGBDisplay; 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 marked as row-major by convention if(el.type.descriptor.rows == 1) el.type.descriptor.flags |= ShaderVariableFlags::RowMajorMatrix; 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.flags |= ShaderVariableFlags::HexDisplay; 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.RowMajor() ? 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.flags |= ShaderVariableFlags::HexDisplay; 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()) { QList declaredStructs; if(m_API == GraphicsAPI::Vulkan || m_API == GraphicsAPI::OpenGL) { const rdcarray &members = res.variableType.members; // if there is only one member in the root array, we can just call DeclareStruct directly if(members.count() <= 1) { format = DeclareStruct(declaredStructs, res.name, members, 0, QString()); } else { // otherwise we need to build up the comment indicating which fixed-size members we skipped QString fixedPrefixString = tr(" // members skipped as they are fixed size:\n"); baseByteOffset += members.back().byteOffset; // list each member before the last, commented out. for(int i = 0; i < members.count() - 1; i++) { QString arraySize; if(members[i].type.descriptor.elements > 1 && members[i].type.descriptor.elements != ~0U) arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements); QString varName = members[i].name; if(varName.isEmpty()) varName = QFormatStr("_child%1").arg(i); fixedPrefixString += QFormatStr(" // %1 %2%3;\n") .arg(members[i].type.descriptor.name) .arg(varName) .arg(arraySize); } fixedPrefixString += lit(" // final array struct @ byte offset %1\n").arg(members.back().byteOffset); // construct a fake list of members with only the last arrayed one, to pass to DeclareStruct rdcarray fakeLastMember; fakeLastMember.push_back(members.back()); // rebase offset of this member to 0 so that DeclareStruct doesn't think any padding is // needed fakeLastMember[0].byteOffset = 0; format = DeclareStruct(declaredStructs, res.name, fakeLastMember, fakeLastMember[0].type.descriptor.arrayByteStride, fixedPrefixString); } } else { format = DeclareStruct(declaredStructs, res.variableType.descriptor.name, res.variableType.members, 0, QString()); } } else { const auto &desc = res.variableType.descriptor; if(viewFormat.type == ResourceFormatType::Undefined) { if(desc.type == VarType::Unknown) { format = desc.name; } else { if(desc.RowMajor() && desc.rows > 1 && desc.columns > 1) 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.RowMajor()) 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 && var.type.descriptor.elements != ~0U) size *= var.type.descriptor.elements; return size; } uint32_t BufferFormatter::GetStructVarSize(const rdcarray &members) { uint32_t lastMemberStart = 0; const ShaderConstant *lastChild = &members.back(); lastMemberStart += lastChild->byteOffset; while(!lastChild->type.members.isEmpty()) { if(lastChild->type.descriptor.elements != ~0U) 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 &declaredStructs, const QString &name, const rdcarray &members, uint32_t requiredByteStride, QString innerSkippedPrefixString) { QString ret; ret = lit("struct %1\n{\n").arg(name); ret += innerSkippedPrefixString; 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 && members[i].type.descriptor.elements != ~0U) 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, QString()) + 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, QString()) + 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.RowMajor()) { 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 &members, uint32_t requiredByteStride) { QList declaredStructs; return DeclareStruct(declaredStructs, name, members, requiredByteStride, QString()); } 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.RowMajor() || 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.ColMajor() && 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++; switch(var.type) { case VarType::Float: var.value.f32v[dst] = o.toFloat(); break; case VarType::Double: var.value.f64v[dst] = o.toDouble(); break; case VarType::Half: var.value.f16v[dst] = rdhalf::make(o.toFloat()); break; case VarType::Bool: var.value.u32v[dst] = o.toBool() ? 1 : 0; break; case VarType::ULong: var.value.u64v[dst] = o.toULongLong(); break; case VarType::UInt: var.value.u32v[dst] = o.toUInt(); break; case VarType::UShort: var.value.u16v[dst] = o.toUInt() & 0xffff; break; case VarType::UByte: var.value.u8v[dst] = o.toUInt() & 0xff; break; case VarType::SLong: var.value.s64v[dst] = o.toLongLong(); break; case VarType::SInt: var.value.s32v[dst] = o.toInt(); break; case VarType::SShort: var.value.u16v[dst] = (int16_t)qBound((int)INT16_MIN, o.toInt(), (int)INT16_MAX); break; case VarType::SByte: var.value.u8v[dst] = (int8_t)qBound((int)INT8_MIN, o.toInt(), (int)INT8_MAX); break; case VarType::GPUPointer: // treat this as a 64-bit unsigned integer var.value.u64v[dst] = o.toULongLong(); break; case VarType::ConstantBlock: case VarType::ReadOnlyResource: case VarType::ReadWriteResource: case VarType::Sampler: case VarType::Unknown: case VarType::Struct: qCritical() << "Unexpected variable type" << ToQStr(var.type) << "in variable" << (QString)var.name; break; } } } } 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.flags = elem.type.descriptor.flags; if(!elem.type.members.isEmpty()) { ret.rows = ret.columns = 0; if(elem.type.descriptor.elements > 1 && elem.type.descriptor.elements != ~0U) { rdcarray arrayElements; for(uint32_t a = 0; a < elem.type.descriptor.elements; a++) { rdcarray 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.members = arrayElements; } else { rdcarray 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 && elem.type.descriptor.elements != ~0U) { rdcarray 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 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(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(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(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(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(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.RowMajor() || 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(data, end, ok)); else if(format.compByteWidth == 4) ret.push_back(readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((float)rdhalf::make(readObj(data, end, ok))); } else if(format.compType == CompType::SInt) { if(format.compByteWidth == 8) ret.push_back((qlonglong)readObj(data, end, ok)); else if(format.compByteWidth == 4) ret.push_back((int)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((int)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((int)readObj(data, end, ok)); } else if(format.compType == CompType::UInt) { if(format.compByteWidth == 8) ret.push_back((qulonglong)readObj(data, end, ok)); else if(format.compByteWidth == 4) ret.push_back((uint32_t)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((uint32_t)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((uint32_t)readObj(data, end, ok)); } else if(format.compType == CompType::UScaled) { if(format.compByteWidth == 4) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((float)readObj(data, end, ok)); } else if(format.compType == CompType::SScaled) { if(format.compByteWidth == 4) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((float)readObj(data, end, ok)); } else if(format.compType == CompType::Depth) { if(format.compByteWidth == 4) { // 32-bit depth is native floats ret.push_back(readObj(data, end, ok)); } else if(format.compByteWidth == 3) { // 32-bit depth is normalised, masked against non-stencil bits uint32_t f = readObj(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(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(data, end, ok) / (float)0xffffffff); } else if(format.compByteWidth == 2) { ret.push_back(interpret(format, readObj(data, end, ok))); } else if(format.compByteWidth == 1) { ret.push_back(interpret(format, readObj(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.type == VarType::Struct) { if(v.type == VarType::Struct) { if(!v.members.empty() && v.members[0].name.contains('[')) return lit("struct[%2]").arg(v.members.count()); else 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.type == VarType::ReadOnlyResource) typeStr = lit("Resource"); else if(v.type == VarType::ReadWriteResource) typeStr = lit("RW Resource"); else if(v.type == VarType::Sampler) typeStr = lit("Sampler"); else if(v.type == VarType::ConstantBlock) typeStr = lit("Constant Block"); if(v.flags & ShaderVariableFlags::HexDisplay) { 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 static QString RowValuesToString(int cols, ShaderVariableFlags flags, el x, el y, el z, el w) { const bool hex = bool(flags & ShaderVariableFlags::HexDisplay); 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(v.type == VarType::Struct) return lit("{ ... }"); switch(type) { case VarType::Float: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::Double: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::Half: return RowValuesToString((int)v.columns, v.flags, v.value.f16v[row * v.columns + 0], v.value.f16v[row * v.columns + 1], v.value.f16v[row * v.columns + 2], v.value.f16v[row * v.columns + 3]); case VarType::Bool: return RowValuesToString((int)v.columns, v.flags, 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); case VarType::ULong: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::UInt: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::UShort: return RowValuesToString((int)v.columns, v.flags, v.value.u16v[row * v.columns + 0], v.value.u16v[row * v.columns + 1], v.value.u16v[row * v.columns + 2], v.value.u16v[row * v.columns + 3]); case VarType::UByte: return RowValuesToString((int)v.columns, v.flags, v.value.u8v[row * v.columns + 0], v.value.u8v[row * v.columns + 1], v.value.u8v[row * v.columns + 2], v.value.u8v[row * v.columns + 3]); case VarType::SLong: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::SInt: return RowValuesToString((int)v.columns, v.flags, 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]); case VarType::SShort: return RowValuesToString((int)v.columns, v.flags, v.value.s16v[row * v.columns + 0], v.value.s16v[row * v.columns + 1], v.value.s16v[row * v.columns + 2], v.value.s16v[row * v.columns + 3]); case VarType::SByte: return RowValuesToString((int)v.columns, v.flags, v.value.s8v[row * v.columns + 0], v.value.s8v[row * v.columns + 1], v.value.s8v[row * v.columns + 2], v.value.s8v[row * v.columns + 3]); case VarType::GPUPointer: return ToQStr(v.GetPointer()); case VarType::ConstantBlock: case VarType::ReadOnlyResource: case VarType::ReadWriteResource: case VarType::Sampler: case VarType::Unknown: case VarType::Struct: break; } return lit("???"); } 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.type == VarType::Struct) { if(v.type == VarType::Struct) 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.flags & ShaderVariableFlags::HexDisplay) { 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); }